Refactoring 2 before EIP - 1193
This commit is contained in:
parent
e24a9d2b51
commit
e48397c3da
|
@ -1,6 +1,6 @@
|
|||
const ObservableStore = require('obs-store')
|
||||
const PendingBalanceCalculator = require('../lib/pending-balance-calculator')
|
||||
const BN = require('ethereumjs-util').BN
|
||||
import ObservableStore from 'obs-store'
|
||||
import PendingBalanceCalculator from '../lib/pending-balance-calculator'
|
||||
import { BN } from 'ethereumjs-util'
|
||||
|
||||
class BalanceController {
|
||||
|
||||
|
@ -8,7 +8,7 @@ class BalanceController {
|
|||
* Controller responsible for storing and updating an account's balance.
|
||||
*
|
||||
* @typedef {Object} BalanceController
|
||||
* @param {Object} opts Initialize various properties of the class.
|
||||
* @param {Object} opts - Initialize various properties of the class.
|
||||
* @property {string} address A base 16 hex string. The account address which has the balance managed by this
|
||||
* BalanceController.
|
||||
* @property {AccountTracker} accountTracker Stores and updates the users accounts
|
||||
|
@ -47,7 +47,7 @@ class BalanceController {
|
|||
/**
|
||||
* Updates the ethBalance property to the current pending balance
|
||||
*
|
||||
* @returns {Promise<void>} Promises undefined
|
||||
* @returns {Promise<void>} - Promises undefined
|
||||
*/
|
||||
async updateBalance () {
|
||||
const balance = await this.balanceCalc.getBalance()
|
||||
|
@ -68,7 +68,7 @@ class BalanceController {
|
|||
_registerUpdates () {
|
||||
const update = this.updateBalance.bind(this)
|
||||
|
||||
this.txController.on('tx:status-update', (txId, status) => {
|
||||
this.txController.on('tx:status-update', (_, status) => {
|
||||
switch (status) {
|
||||
case 'submitted':
|
||||
case 'confirmed':
|
||||
|
@ -87,7 +87,7 @@ class BalanceController {
|
|||
* Gets the balance, as a base 16 hex string, of the account at this BalanceController's current address.
|
||||
* If the current account has no balance, returns undefined.
|
||||
*
|
||||
* @returns {Promise<BN|void>} Promises a BN with a value equal to the balance of the current account, or undefined
|
||||
* @returns {Promise<BN|void>} - Promises a BN with a value equal to the balance of the current account, or undefined
|
||||
* if the current account has no balance
|
||||
*
|
||||
*/
|
||||
|
@ -103,7 +103,7 @@ class BalanceController {
|
|||
* TransactionController passed to this BalanceController during construction.
|
||||
*
|
||||
* @private
|
||||
* @returns {Promise<array>} Promises an array of transaction objects.
|
||||
* @returns {Promise<array>} - Promises an array of transaction objects.
|
||||
*
|
||||
*/
|
||||
async _getPendingTransactions () {
|
||||
|
@ -118,7 +118,7 @@ class BalanceController {
|
|||
/**
|
||||
* Validates that the passed options have all required properties.
|
||||
*
|
||||
* @param {Object} opts The options object to validate
|
||||
* @param {Object} opts - The options object to validate
|
||||
* @throws {string} Throw a custom error indicating that address, accountTracker, txController and blockTracker are
|
||||
* missing and at least one is required
|
||||
*
|
||||
|
|
|
@ -1,21 +1,23 @@
|
|||
const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware')
|
||||
const createBlockReRefMiddleware = require('eth-json-rpc-middleware/block-ref')
|
||||
const createRetryOnEmptyMiddleware = require('eth-json-rpc-middleware/retryOnEmpty')
|
||||
const createBlockCacheMiddleware = require('eth-json-rpc-middleware/block-cache')
|
||||
const createInflightMiddleware = require('eth-json-rpc-middleware/inflight-cache')
|
||||
const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector')
|
||||
const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware')
|
||||
const createInfuraMiddleware = require('eth-json-rpc-infura')
|
||||
const BlockTracker = require('eth-block-tracker')
|
||||
import mergeMiddleware from 'json-rpc-engine/src/mergeMiddleware'
|
||||
import createScaffoldMiddleware from 'json-rpc-engine/src/createScaffoldMiddleware'
|
||||
import createBlockReRefMiddleware from 'eth-json-rpc-middleware/block-ref'
|
||||
import createRetryOnEmptyMiddleware from 'eth-json-rpc-middleware/retryOnEmpty'
|
||||
import createBlockCacheMiddleware from 'eth-json-rpc-middleware/block-cache'
|
||||
import createInflightMiddleware from 'eth-json-rpc-middleware/inflight-cache'
|
||||
import createBlockTrackerInspectorMiddleware from 'eth-json-rpc-middleware/block-tracker-inspector'
|
||||
import providerFromMiddleware from 'eth-json-rpc-middleware/providerFromMiddleware'
|
||||
import createInfuraMiddleware from 'eth-json-rpc-infura'
|
||||
import BlockTracker from 'eth-block-tracker'
|
||||
|
||||
module.exports = createInfuraClient
|
||||
export default createInfuraClient
|
||||
|
||||
function createInfuraClient ({ network }) {
|
||||
const infuraMiddleware = createInfuraMiddleware({ network })
|
||||
const infuraMiddleware = createInfuraMiddleware({ network, maxAttempts: 5, source: 'metamask' })
|
||||
const infuraProvider = providerFromMiddleware(infuraMiddleware)
|
||||
const blockTracker = new BlockTracker({ provider: infuraProvider })
|
||||
|
||||
const networkMiddleware = mergeMiddleware([
|
||||
createNetworkAndChainIdMiddleware({ network }),
|
||||
createBlockCacheMiddleware({ blockTracker }),
|
||||
createInflightMiddleware(),
|
||||
createBlockReRefMiddleware({ blockTracker, provider: infuraProvider }),
|
||||
|
@ -25,3 +27,38 @@ function createInfuraClient ({ network }) {
|
|||
])
|
||||
return { networkMiddleware, blockTracker }
|
||||
}
|
||||
|
||||
function createNetworkAndChainIdMiddleware ({ network }) {
|
||||
let chainId
|
||||
let netId
|
||||
|
||||
switch (network) {
|
||||
case 'mainnet':
|
||||
netId = '1'
|
||||
chainId = '0x01'
|
||||
break
|
||||
case 'ropsten':
|
||||
netId = '3'
|
||||
chainId = '0x03'
|
||||
break
|
||||
case 'rinkeby':
|
||||
netId = '4'
|
||||
chainId = '0x04'
|
||||
break
|
||||
case 'kovan':
|
||||
netId = '42'
|
||||
chainId = '0x2a'
|
||||
break
|
||||
case 'goerli':
|
||||
netId = '5'
|
||||
chainId = '0x05'
|
||||
break
|
||||
default:
|
||||
throw new Error(`createInfuraClient - unknown network "${network}"`)
|
||||
}
|
||||
|
||||
return createScaffoldMiddleware({
|
||||
eth_chainId: chainId,
|
||||
net_version: netId,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware')
|
||||
const createFetchMiddleware = require('eth-json-rpc-middleware/fetch')
|
||||
const createBlockRefRewriteMiddleware = require('eth-json-rpc-middleware/block-ref-rewrite')
|
||||
const createBlockCacheMiddleware = require('eth-json-rpc-middleware/block-cache')
|
||||
const createInflightMiddleware = require('eth-json-rpc-middleware/inflight-cache')
|
||||
const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector')
|
||||
const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware')
|
||||
const BlockTracker = require('eth-block-tracker')
|
||||
import mergeMiddleware from 'json-rpc-engine/src/mergeMiddleware'
|
||||
import createFetchMiddleware from 'eth-json-rpc-middleware/fetch'
|
||||
import createBlockRefRewriteMiddleware from 'eth-json-rpc-middleware/block-ref-rewrite'
|
||||
import createBlockCacheMiddleware from 'eth-json-rpc-middleware/block-cache'
|
||||
import createInflightMiddleware from 'eth-json-rpc-middleware/inflight-cache'
|
||||
import createBlockTrackerInspectorMiddleware from 'eth-json-rpc-middleware/block-tracker-inspector'
|
||||
import providerFromMiddleware from 'eth-json-rpc-middleware/providerFromMiddleware'
|
||||
import BlockTracker from 'eth-block-tracker'
|
||||
|
||||
module.exports = createJsonRpcClient
|
||||
export default createJsonRpcClient
|
||||
|
||||
function createJsonRpcClient ({ rpcUrl }) {
|
||||
const fetchMiddleware = createFetchMiddleware({ rpcUrl })
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware')
|
||||
const createFetchMiddleware = require('eth-json-rpc-middleware/fetch')
|
||||
const createBlockRefRewriteMiddleware = require('eth-json-rpc-middleware/block-ref-rewrite')
|
||||
const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector')
|
||||
const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware')
|
||||
const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware')
|
||||
const BlockTracker = require('eth-block-tracker')
|
||||
import mergeMiddleware from 'json-rpc-engine/src/mergeMiddleware'
|
||||
import createFetchMiddleware from 'eth-json-rpc-middleware/fetch'
|
||||
import createBlockRefRewriteMiddleware from 'eth-json-rpc-middleware/block-ref-rewrite'
|
||||
import createBlockTrackerInspectorMiddleware from 'eth-json-rpc-middleware/block-tracker-inspector'
|
||||
import createAsyncMiddleware from 'json-rpc-engine/src/createAsyncMiddleware'
|
||||
import providerFromMiddleware from 'eth-json-rpc-middleware/providerFromMiddleware'
|
||||
import BlockTracker from 'eth-block-tracker'
|
||||
|
||||
const inTest = process.env.IN_TEST === 'true'
|
||||
|
||||
module.exports = createLocalhostClient
|
||||
export default createLocalhostClient
|
||||
|
||||
function createLocalhostClient () {
|
||||
const fetchMiddleware = createFetchMiddleware({ rpcUrl: 'http://localhost:8545/' })
|
||||
|
@ -25,7 +25,7 @@ function createLocalhostClient () {
|
|||
}
|
||||
|
||||
function delay (time) {
|
||||
return new Promise(resolve => setTimeout(resolve, time))
|
||||
return new Promise((resolve) => setTimeout(resolve, time))
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware')
|
||||
const createScaffoldMiddleware = require('json-rpc-engine/src/createScaffoldMiddleware')
|
||||
const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware')
|
||||
const createWalletSubprovider = require('eth-json-rpc-middleware/wallet')
|
||||
import mergeMiddleware from 'json-rpc-engine/src/mergeMiddleware'
|
||||
import createScaffoldMiddleware from 'json-rpc-engine/src/createScaffoldMiddleware'
|
||||
import createWalletSubprovider from 'eth-json-rpc-middleware/wallet'
|
||||
import { createPendingNonceMiddleware } from './middleware/pending'
|
||||
|
||||
module.exports = createMetamaskMiddleware
|
||||
export default createMetamaskMiddleware
|
||||
|
||||
function createMetamaskMiddleware ({
|
||||
version,
|
||||
|
@ -11,7 +11,11 @@ function createMetamaskMiddleware ({
|
|||
processTransaction,
|
||||
processEthSignMessage,
|
||||
processTypedMessage,
|
||||
processTypedMessageV3,
|
||||
processTypedMessageV4,
|
||||
processPersonalMessage,
|
||||
processDecryptMessage,
|
||||
processEncryptionPublicKey,
|
||||
getPendingNonce,
|
||||
}) {
|
||||
const metamaskMiddleware = mergeMiddleware([
|
||||
|
@ -25,19 +29,13 @@ function createMetamaskMiddleware ({
|
|||
processTransaction,
|
||||
processEthSignMessage,
|
||||
processTypedMessage,
|
||||
processTypedMessageV3,
|
||||
processTypedMessageV4,
|
||||
processPersonalMessage,
|
||||
processDecryptMessage,
|
||||
processEncryptionPublicKey,
|
||||
}),
|
||||
createPendingNonceMiddleware({ getPendingNonce }),
|
||||
])
|
||||
return metamaskMiddleware
|
||||
}
|
||||
|
||||
function createPendingNonceMiddleware ({ getPendingNonce }) {
|
||||
return createAsyncMiddleware(async (req, res, next) => {
|
||||
if (req.method !== 'eth_getTransactionCount') return next()
|
||||
const address = req.params[0]
|
||||
const blockRef = req.params[1]
|
||||
if (blockRef !== 'pending') return next()
|
||||
res.result = await getPendingNonce(address)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
const { formatTxMetaForRpcResult } = require('../util')
|
||||
import createAsyncMiddleware from 'json-rpc-engine/src/createAsyncMiddleware'
|
||||
|
||||
export function createPendingNonceMiddleware ({ getPendingNonce }) {
|
||||
return createAsyncMiddleware(async (req, res, next) => {
|
||||
const { method, params } = req
|
||||
if (method !== 'eth_getTransactionCount') {
|
||||
return next()
|
||||
}
|
||||
const [param, blockRef] = params
|
||||
if (blockRef !== 'pending') {
|
||||
return next()
|
||||
}
|
||||
res.result = await getPendingNonce(param)
|
||||
})
|
||||
}
|
||||
|
||||
export function createPendingTxMiddleware ({ getPendingTransactionByHash }) {
|
||||
return createAsyncMiddleware(async (req, res, next) => {
|
||||
const { method, params } = req
|
||||
if (method !== 'eth_getTransactionByHash') {
|
||||
return next()
|
||||
}
|
||||
const [hash] = params
|
||||
const txMeta = getPendingTransactionByHash(hash)
|
||||
if (!txMeta) {
|
||||
return next()
|
||||
}
|
||||
res.result = formatTxMetaForRpcResult(txMeta)
|
||||
})
|
||||
}
|
|
@ -1,20 +1,19 @@
|
|||
const assert = require('assert')
|
||||
const EventEmitter = require('events')
|
||||
const ObservableStore = require('obs-store')
|
||||
const ComposedStore = require('obs-store/lib/composed')
|
||||
const EthQuery = require('eth-query')
|
||||
const JsonRpcEngine = require('json-rpc-engine')
|
||||
const providerFromEngine = require('eth-json-rpc-middleware/providerFromEngine')
|
||||
const log = require('loglevel')
|
||||
const createMetamaskMiddleware = require('./createMetamaskMiddleware')
|
||||
const createInfuraClient = require('./createInfuraClient')
|
||||
const createJsonRpcClient = require('./createJsonRpcClient')
|
||||
const createLocalhostClient = require('./createLocalhostClient')
|
||||
import assert from 'assert'
|
||||
import EventEmitter from 'events'
|
||||
import ObservableStore from 'obs-store'
|
||||
import ComposedStore from 'obs-store/lib/composed'
|
||||
import EthQuery from 'eth-query'
|
||||
import JsonRpcEngine from 'json-rpc-engine'
|
||||
import providerFromEngine from 'eth-json-rpc-middleware/providerFromEngine'
|
||||
import log from 'loglevel'
|
||||
import createMetamaskMiddleware from './createMetamaskMiddleware'
|
||||
import createInfuraClient from './createInfuraClient'
|
||||
import createJsonRpcClient from './createJsonRpcClient'
|
||||
import createLocalhostClient from './createLocalhostClient'
|
||||
const createPocketClient = require('./createPocketClient')
|
||||
const { createSwappableProxy, createEventEmitterProxy } = require('swappable-obj-proxy')
|
||||
const ethNetProps = require('eth-net-props')
|
||||
const parse = require('url-parse')
|
||||
const extend = require('extend')
|
||||
import parse from 'url-parse'
|
||||
const networks = { networkList: {} }
|
||||
const { isKnownProvider, getDPath } = require('../../../../old-ui/app/util')
|
||||
|
||||
|
@ -75,7 +74,7 @@ module.exports = class NetworkController extends EventEmitter {
|
|||
this.networkStore = new ObservableStore('loading')
|
||||
this.dProviderStore = new ObservableStore({dProvider: false})
|
||||
this.networkConfig = new ObservableStore(defaultNetworkConfig)
|
||||
this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore, dProviderStore: this.dProviderStore })
|
||||
this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore, settings: this.networkConfig, dProviderStore: this.dProviderStore })
|
||||
this.on('networkDidChange', this.lookupNetwork)
|
||||
// provider and block tracker
|
||||
this._provider = null
|
||||
|
@ -101,7 +100,9 @@ module.exports = class NetworkController extends EventEmitter {
|
|||
|
||||
verifyNetwork () {
|
||||
// Check network when restoring connectivity:
|
||||
if (this.isNetworkLoading()) this.lookupNetwork()
|
||||
if (this.isNetworkLoading()) {
|
||||
this.lookupNetwork()
|
||||
}
|
||||
}
|
||||
|
||||
getNetworkState () {
|
||||
|
@ -253,10 +254,12 @@ module.exports = class NetworkController extends EventEmitter {
|
|||
|
||||
_configureInfuraProvider ({ type }) {
|
||||
log.info('NetworkController - configureInfuraProvider', type)
|
||||
const networkClient = createInfuraClient({ network: type })
|
||||
const networkClient = createInfuraClient({
|
||||
network: type,
|
||||
})
|
||||
this._setNetworkClient(networkClient)
|
||||
// setup networkConfig
|
||||
var settings = {
|
||||
const settings = {
|
||||
ticker: 'ETH',
|
||||
}
|
||||
this.networkConfig.putState(settings)
|
||||
|
@ -285,10 +288,10 @@ module.exports = class NetworkController extends EventEmitter {
|
|||
nickname,
|
||||
}
|
||||
// setup networkConfig
|
||||
var settings = {
|
||||
let settings = {
|
||||
network: chainId,
|
||||
}
|
||||
settings = extend(settings, networks.networkList['rpc'])
|
||||
settings = Object.assign(settings, networks.networkList['rpc'])
|
||||
this.networkConfig.putState(settings)
|
||||
this._setNetworkClient(networkClient)
|
||||
}
|
||||
|
@ -318,9 +321,4 @@ module.exports = class NetworkController extends EventEmitter {
|
|||
this._provider = provider
|
||||
this._blockTracker = blockTracker
|
||||
}
|
||||
|
||||
_logBlock (block) {
|
||||
log.info(`BLOCK CHANGED: #${block.number.toString('hex')} 0x${block.hash.toString('hex')}`)
|
||||
this.verifyNetwork()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -169,7 +169,27 @@ networks[RSK_TESTNET] = RSK_TESTNET_OBJ
|
|||
|
||||
const getNetworkDisplayName = key => networks[key].displayName
|
||||
|
||||
function formatTxMetaForRpcResult (txMeta) {
|
||||
return {
|
||||
'blockHash': txMeta.txReceipt ? txMeta.txReceipt.blockHash : null,
|
||||
'blockNumber': txMeta.txReceipt ? txMeta.txReceipt.blockNumber : null,
|
||||
'from': txMeta.txParams.from,
|
||||
'gas': txMeta.txParams.gas,
|
||||
'gasPrice': txMeta.txParams.gasPrice,
|
||||
'hash': txMeta.hash,
|
||||
'input': txMeta.txParams.data || '0x',
|
||||
'nonce': txMeta.txParams.nonce,
|
||||
'to': txMeta.txParams.to,
|
||||
'transactionIndex': txMeta.txReceipt ? txMeta.txReceipt.transactionIndex : null,
|
||||
'value': txMeta.txParams.value || '0x0',
|
||||
'v': txMeta.v,
|
||||
'r': txMeta.r,
|
||||
's': txMeta.s,
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
networks,
|
||||
getNetworkDisplayName,
|
||||
formatTxMetaForRpcResult,
|
||||
}
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
|
||||
export const WALLET_PREFIX = 'wallet_'
|
||||
|
||||
export const HISTORY_STORE_KEY = 'permissionsHistory'
|
||||
|
||||
export const LOG_STORE_KEY = 'permissionsLog'
|
||||
|
||||
export const METADATA_STORE_KEY = 'domainMetadata'
|
||||
|
||||
export const CAVEAT_NAMES = {
|
||||
exposedAccounts: 'exposedAccounts',
|
||||
}
|
||||
|
||||
export const NOTIFICATION_NAMES = {
|
||||
accountsChanged: 'wallet_accountsChanged',
|
||||
}
|
||||
|
||||
export const LOG_IGNORE_METHODS = [
|
||||
'wallet_sendDomainMetadata',
|
||||
]
|
||||
|
||||
export const LOG_METHOD_TYPES = {
|
||||
restricted: 'restricted',
|
||||
internal: 'internal',
|
||||
}
|
||||
|
||||
export const LOG_LIMIT = 100
|
||||
|
||||
export const SAFE_METHODS = [
|
||||
'web3_sha3',
|
||||
'net_listening',
|
||||
'net_peerCount',
|
||||
'net_version',
|
||||
'eth_blockNumber',
|
||||
'eth_call',
|
||||
'eth_chainId',
|
||||
'eth_coinbase',
|
||||
'eth_estimateGas',
|
||||
'eth_gasPrice',
|
||||
'eth_getBalance',
|
||||
'eth_getBlockByHash',
|
||||
'eth_getBlockByNumber',
|
||||
'eth_getBlockTransactionCountByHash',
|
||||
'eth_getBlockTransactionCountByNumber',
|
||||
'eth_getCode',
|
||||
'eth_getFilterChanges',
|
||||
'eth_getFilterLogs',
|
||||
'eth_getLogs',
|
||||
'eth_getStorageAt',
|
||||
'eth_getTransactionByBlockHashAndIndex',
|
||||
'eth_getTransactionByBlockNumberAndIndex',
|
||||
'eth_getTransactionByHash',
|
||||
'eth_getTransactionCount',
|
||||
'eth_getTransactionReceipt',
|
||||
'eth_getUncleByBlockHashAndIndex',
|
||||
'eth_getUncleByBlockNumberAndIndex',
|
||||
'eth_getUncleCountByBlockHash',
|
||||
'eth_getUncleCountByBlockNumber',
|
||||
'eth_getWork',
|
||||
'eth_hashrate',
|
||||
'eth_mining',
|
||||
'eth_newBlockFilter',
|
||||
'eth_newFilter',
|
||||
'eth_newPendingTransactionFilter',
|
||||
'eth_protocolVersion',
|
||||
'eth_sendRawTransaction',
|
||||
'eth_sendTransaction',
|
||||
'eth_sign',
|
||||
'personal_sign',
|
||||
'eth_signTypedData',
|
||||
'eth_signTypedData_v1',
|
||||
'eth_signTypedData_v3',
|
||||
'eth_submitHashrate',
|
||||
'eth_submitWork',
|
||||
'eth_syncing',
|
||||
'eth_uninstallFilter',
|
||||
'metamask_watchAsset',
|
||||
'wallet_watchAsset',
|
||||
'eth_getEncryptionPublicKey',
|
||||
'eth_decrypt',
|
||||
]
|
|
@ -0,0 +1,560 @@
|
|||
import JsonRpcEngine from 'json-rpc-engine'
|
||||
import asMiddleware from 'json-rpc-engine/src/asMiddleware'
|
||||
import ObservableStore from 'obs-store'
|
||||
import log from 'loglevel'
|
||||
import { CapabilitiesController as RpcCap } from 'rpc-cap'
|
||||
import { ethErrors } from 'eth-json-rpc-errors'
|
||||
import { cloneDeep } from 'lodash'
|
||||
|
||||
import createMethodMiddleware from './methodMiddleware'
|
||||
import PermissionsLogController from './permissionsLog'
|
||||
|
||||
// Methods that do not require any permissions to use:
|
||||
import {
|
||||
SAFE_METHODS, // methods that do not require any permissions to use
|
||||
WALLET_PREFIX,
|
||||
METADATA_STORE_KEY,
|
||||
LOG_STORE_KEY,
|
||||
HISTORY_STORE_KEY,
|
||||
CAVEAT_NAMES,
|
||||
NOTIFICATION_NAMES,
|
||||
} from './enums'
|
||||
|
||||
export class PermissionsController {
|
||||
|
||||
constructor (
|
||||
{
|
||||
getKeyringAccounts,
|
||||
getRestrictedMethods,
|
||||
getUnlockPromise,
|
||||
notifyDomain,
|
||||
notifyAllDomains,
|
||||
platform,
|
||||
} = {},
|
||||
restoredPermissions = {},
|
||||
restoredState = {}) {
|
||||
|
||||
this.store = new ObservableStore({
|
||||
[METADATA_STORE_KEY]: restoredState[METADATA_STORE_KEY] || {},
|
||||
[LOG_STORE_KEY]: restoredState[LOG_STORE_KEY] || [],
|
||||
[HISTORY_STORE_KEY]: restoredState[HISTORY_STORE_KEY] || {},
|
||||
})
|
||||
|
||||
this.getKeyringAccounts = getKeyringAccounts
|
||||
this.getUnlockPromise = getUnlockPromise
|
||||
this._notifyDomain = notifyDomain
|
||||
this.notifyAllDomains = notifyAllDomains
|
||||
this._platform = platform
|
||||
|
||||
this._restrictedMethods = getRestrictedMethods(this)
|
||||
this.permissionsLog = new PermissionsLogController({
|
||||
restrictedMethods: Object.keys(this._restrictedMethods),
|
||||
store: this.store,
|
||||
})
|
||||
this.pendingApprovals = new Map()
|
||||
this.pendingApprovalOrigins = new Set()
|
||||
this._initializePermissions(restoredPermissions)
|
||||
}
|
||||
|
||||
createMiddleware ({ origin, extensionId }) {
|
||||
|
||||
if (typeof origin !== 'string' || !origin.length) {
|
||||
throw new Error('Must provide non-empty string origin.')
|
||||
}
|
||||
|
||||
if (extensionId) {
|
||||
this.store.updateState({
|
||||
[METADATA_STORE_KEY]: {
|
||||
...this.store.getState()[METADATA_STORE_KEY],
|
||||
[origin]: { extensionId },
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const engine = new JsonRpcEngine()
|
||||
|
||||
engine.push(this.permissionsLog.createMiddleware())
|
||||
|
||||
engine.push(createMethodMiddleware({
|
||||
store: this.store,
|
||||
storeKey: METADATA_STORE_KEY,
|
||||
getAccounts: this.getAccounts.bind(this, origin),
|
||||
getUnlockPromise: this.getUnlockPromise,
|
||||
hasPermission: this.hasPermission.bind(this, origin),
|
||||
requestAccountsPermission: this._requestPermissions.bind(
|
||||
this, origin, { eth_accounts: {} },
|
||||
),
|
||||
}))
|
||||
|
||||
engine.push(this.permissions.providerMiddlewareFunction.bind(
|
||||
this.permissions, { origin },
|
||||
))
|
||||
|
||||
return asMiddleware(engine)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the accounts that should be exposed for the given origin domain,
|
||||
* if any. This method exists for when a trusted context needs to know
|
||||
* which accounts are exposed to a given domain.
|
||||
*
|
||||
* @param {string} origin - The origin string.
|
||||
*/
|
||||
getAccounts (origin) {
|
||||
// return new Promise((resolve, _) => {
|
||||
|
||||
// const req = { method: 'eth_accounts' }
|
||||
// const res = {}
|
||||
// this.permissions.providerMiddlewareFunction(
|
||||
// { origin }, req, res, () => {}, _end
|
||||
// )
|
||||
|
||||
// function _end () {
|
||||
// if (res.error || !Array.isArray(res.result)) {
|
||||
// resolve([])
|
||||
// } else {
|
||||
// resolve(res.result)
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
return this.getKeyringAccounts()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given origin has the given permission.
|
||||
*
|
||||
* @param {string} origin - The origin to check.
|
||||
* @param {string} permission - The permission to check for.
|
||||
* @returns {boolean} Whether the origin has the permission.
|
||||
*/
|
||||
hasPermission (origin, permission) {
|
||||
return Boolean(this.permissions.getPermission(origin, permission))
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits a permissions request to rpc-cap. Internal, background use only.
|
||||
*
|
||||
* @param {string} origin - The origin string.
|
||||
* @param {IRequestedPermissions} permissions - The requested permissions.
|
||||
*/
|
||||
_requestPermissions (origin, permissions) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
// rpc-cap assigns an id to the request if there is none, as expected by
|
||||
// requestUserApproval below
|
||||
const req = { method: 'wallet_requestPermissions', params: [permissions] }
|
||||
const res = {}
|
||||
this.permissions.providerMiddlewareFunction(
|
||||
{ origin }, req, res, () => {}, _end,
|
||||
)
|
||||
|
||||
function _end (_err) {
|
||||
const err = _err || res.error
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve(res.result)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* User approval callback. Resolves the Promise for the permissions request
|
||||
* waited upon by rpc-cap, see requestUserApproval in _initializePermissions.
|
||||
* The request will be rejected if finalizePermissionsRequest fails.
|
||||
*
|
||||
* @param {Object} approved - The request object approved by the user
|
||||
* @param {Array} accounts - The accounts to expose, if any
|
||||
*/
|
||||
async approvePermissionsRequest (approved, accounts) {
|
||||
|
||||
const { id } = approved.metadata
|
||||
const approval = this.pendingApprovals.get(id)
|
||||
|
||||
if (!approval) {
|
||||
log.error(`Permissions request with id '${id}' not found`)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
if (Object.keys(approved.permissions).length === 0) {
|
||||
|
||||
approval.reject(ethErrors.rpc.invalidRequest({
|
||||
message: 'Must request at least one permission.',
|
||||
}))
|
||||
|
||||
} else {
|
||||
|
||||
// attempt to finalize the request and resolve it,
|
||||
// settings caveats as necessary
|
||||
approved.permissions = await this.finalizePermissionsRequest(
|
||||
approved.permissions, accounts,
|
||||
)
|
||||
approval.resolve(approved.permissions)
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
// if finalization fails, reject the request
|
||||
approval.reject(ethErrors.rpc.invalidRequest({
|
||||
message: err.message, data: err,
|
||||
}))
|
||||
}
|
||||
|
||||
this._removePendingApproval(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* User rejection callback. Rejects the Promise for the permissions request
|
||||
* waited upon by rpc-cap, see requestUserApproval in _initializePermissions.
|
||||
*
|
||||
* @param {string} id - The id of the request rejected by the user
|
||||
*/
|
||||
async rejectPermissionsRequest (id) {
|
||||
const approval = this.pendingApprovals.get(id)
|
||||
|
||||
if (!approval) {
|
||||
log.error(`Permissions request with id '${id}' not found`)
|
||||
return
|
||||
}
|
||||
|
||||
approval.reject(ethErrors.provider.userRejectedRequest())
|
||||
this._removePendingApproval(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Grants the given origin the eth_accounts permission for the given account(s).
|
||||
* This method should ONLY be called as a result of direct user action in the UI,
|
||||
* with the intention of supporting legacy dapps that don't support EIP 1102.
|
||||
*
|
||||
* @param {string} origin - The origin to expose the account(s) to.
|
||||
* @param {Array<string>} accounts - The account(s) to expose.
|
||||
*/
|
||||
async legacyExposeAccounts (origin, accounts) {
|
||||
|
||||
// accounts are validated by finalizePermissionsRequest
|
||||
if (typeof origin !== 'string' || !origin.length) {
|
||||
throw new Error('Must provide non-empty string origin.')
|
||||
}
|
||||
|
||||
const existingAccounts = await this.getAccounts(origin)
|
||||
|
||||
if (existingAccounts.length > 0) {
|
||||
throw new Error(
|
||||
'May not call legacyExposeAccounts on origin with exposed accounts.',
|
||||
)
|
||||
}
|
||||
|
||||
const permissions = await this.finalizePermissionsRequest(
|
||||
{ eth_accounts: {} }, accounts,
|
||||
)
|
||||
|
||||
try {
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
this.permissions.grantNewPermissions(
|
||||
origin, permissions, {}, _end,
|
||||
)
|
||||
|
||||
function _end (err) {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.notifyDomain(origin, {
|
||||
method: NOTIFICATION_NAMES.accountsChanged,
|
||||
result: accounts,
|
||||
})
|
||||
this.permissionsLog.logAccountExposure(origin, accounts)
|
||||
|
||||
} catch (error) {
|
||||
|
||||
throw ethErrors.rpc.internal({
|
||||
message: `Failed to add 'eth_accounts' to '${origin}'.`,
|
||||
data: {
|
||||
originalError: error,
|
||||
accounts,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the accounts exposed to the given origin. Changes the eth_accounts
|
||||
* permissions and emits accountsChanged.
|
||||
* At least one account must be exposed. If no accounts are to be exposed, the
|
||||
* eth_accounts permissions should be removed completely.
|
||||
*
|
||||
* Throws error if the update fails.
|
||||
*
|
||||
* @param {string} origin - The origin to change the exposed accounts for.
|
||||
* @param {string[]} accounts - The new account(s) to expose.
|
||||
*/
|
||||
async updatePermittedAccounts (origin, accounts) {
|
||||
|
||||
await this.validatePermittedAccounts(accounts)
|
||||
|
||||
this.permissions.updateCaveatFor(
|
||||
origin, 'eth_accounts', CAVEAT_NAMES.exposedAccounts, accounts,
|
||||
)
|
||||
|
||||
this.notifyDomain(origin, {
|
||||
method: NOTIFICATION_NAMES.accountsChanged,
|
||||
result: accounts,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizes a permissions request. Throws if request validation fails.
|
||||
* Clones the passed-in parameters to prevent inadvertent modification.
|
||||
* Sets (adds or replaces) caveats for the following permissions:
|
||||
* - eth_accounts: the permitted accounts caveat
|
||||
*
|
||||
* @param {Object} requestedPermissions - The requested permissions.
|
||||
* @param {string[]} requestedAccounts - The accounts to expose, if any.
|
||||
* @returns {Object} The finalized permissions request object.
|
||||
*/
|
||||
async finalizePermissionsRequest (requestedPermissions, requestedAccounts) {
|
||||
|
||||
const finalizedPermissions = cloneDeep(requestedPermissions)
|
||||
const finalizedAccounts = cloneDeep(requestedAccounts)
|
||||
|
||||
const { eth_accounts: ethAccounts } = finalizedPermissions
|
||||
|
||||
if (ethAccounts) {
|
||||
|
||||
await this.validatePermittedAccounts(finalizedAccounts)
|
||||
|
||||
if (!ethAccounts.caveats) {
|
||||
ethAccounts.caveats = []
|
||||
}
|
||||
|
||||
// caveat names are unique, and we will only construct this caveat here
|
||||
ethAccounts.caveats = ethAccounts.caveats.filter((c) => (
|
||||
c.name !== CAVEAT_NAMES.exposedAccounts
|
||||
))
|
||||
|
||||
ethAccounts.caveats.push(
|
||||
{
|
||||
type: 'filterResponse',
|
||||
value: finalizedAccounts,
|
||||
name: CAVEAT_NAMES.exposedAccounts,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return finalizedPermissions
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate an array of accounts representing accounts to be exposed
|
||||
* to a domain. Throws error if validation fails.
|
||||
*
|
||||
* @param {string[]} accounts - An array of addresses.
|
||||
*/
|
||||
async validatePermittedAccounts (accounts) {
|
||||
|
||||
if (!Array.isArray(accounts) || accounts.length === 0) {
|
||||
throw new Error('Must provide non-empty array of account(s).')
|
||||
}
|
||||
|
||||
// assert accounts exist
|
||||
const allAccounts = await this.getKeyringAccounts()
|
||||
accounts.forEach((acc) => {
|
||||
if (!allAccounts.includes(acc)) {
|
||||
throw new Error(`Unknown account: ${acc}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
notifyDomain (origin, payload) {
|
||||
|
||||
// if the accounts changed from the perspective of the dapp,
|
||||
// update "last seen" time for the origin and account(s)
|
||||
// exception: no accounts -> no times to update
|
||||
if (
|
||||
payload.method === NOTIFICATION_NAMES.accountsChanged &&
|
||||
Array.isArray(payload.result)
|
||||
) {
|
||||
this.permissionsLog.updateAccountsHistory(
|
||||
origin, payload.result,
|
||||
)
|
||||
}
|
||||
|
||||
this._notifyDomain(origin, payload)
|
||||
|
||||
// NOTE:
|
||||
// we don't check for accounts changing in the notifyAllDomains case,
|
||||
// because the log only records when accounts were last seen,
|
||||
// and the accounts only change for all domains at once when permissions
|
||||
// are removed
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given permissions for the given domain.
|
||||
* Should only be called after confirming that the permissions exist, to
|
||||
* avoid sending unnecessary notifications.
|
||||
*
|
||||
* @param {Object} domains { origin: [permissions] }
|
||||
*/
|
||||
removePermissionsFor (domains) {
|
||||
|
||||
Object.entries(domains).forEach(([origin, perms]) => {
|
||||
|
||||
this.permissions.removePermissionsFor(
|
||||
origin,
|
||||
perms.map((methodName) => {
|
||||
|
||||
if (methodName === 'eth_accounts') {
|
||||
this.notifyDomain(
|
||||
origin,
|
||||
{ method: NOTIFICATION_NAMES.accountsChanged, result: [] },
|
||||
)
|
||||
}
|
||||
|
||||
return { parentCapability: methodName }
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* When a new account is selected in the UI for 'origin', emit accountsChanged
|
||||
* to 'origin' if the selected account is permitted.
|
||||
*
|
||||
* Note: This will emit "false positive" accountsChanged events, but they are
|
||||
* handled by the inpage provider.
|
||||
*
|
||||
* @param {string} origin - The origin.
|
||||
* @param {string} account - The newly selected account's address.
|
||||
*/
|
||||
async handleNewAccountSelected (origin, account) {
|
||||
|
||||
const permittedAccounts = await this.getAccounts(origin)
|
||||
|
||||
if (
|
||||
typeof origin !== 'string' || !origin.length ||
|
||||
typeof account !== 'string' || !account.length
|
||||
) {
|
||||
throw new Error('Should provide non-empty origin and account strings.')
|
||||
}
|
||||
|
||||
// do nothing if the account is not permitted for the origin, or
|
||||
// if it's already first in the array of permitted accounts
|
||||
if (
|
||||
!permittedAccounts.includes(account) ||
|
||||
permittedAccounts[0] === account
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const newPermittedAccounts = [account].concat(
|
||||
permittedAccounts.filter((_account) => _account !== account),
|
||||
)
|
||||
|
||||
// update permitted accounts to ensure that accounts are returned
|
||||
// in the same order every time
|
||||
await this.updatePermittedAccounts(origin, newPermittedAccounts)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all known domains and their related permissions.
|
||||
*/
|
||||
clearPermissions () {
|
||||
this.permissions.clearDomains()
|
||||
this.notifyAllDomains({
|
||||
method: NOTIFICATION_NAMES.accountsChanged,
|
||||
result: [],
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a pending approval.
|
||||
* @param {string} id - The id of the pending approval.
|
||||
* @param {string} origin - The origin of the pending approval.
|
||||
* @param {Function} resolve - The function resolving the pending approval Promise.
|
||||
* @param {Function} reject - The function rejecting the pending approval Promise.
|
||||
*/
|
||||
_addPendingApproval (id, origin, resolve, reject) {
|
||||
|
||||
if (
|
||||
this.pendingApprovalOrigins.has(origin) ||
|
||||
this.pendingApprovals.has(id)
|
||||
) {
|
||||
throw new Error(
|
||||
`Pending approval with id ${id} or origin ${origin} already exists.`,
|
||||
)
|
||||
}
|
||||
|
||||
this.pendingApprovals.set(id, { origin, resolve, reject })
|
||||
this.pendingApprovalOrigins.add(origin)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the pending approval with the given id.
|
||||
* @param {string} id - The id of the pending approval to remove.
|
||||
*/
|
||||
_removePendingApproval (id) {
|
||||
const { origin } = this.pendingApprovals.get(id)
|
||||
this.pendingApprovalOrigins.delete(origin)
|
||||
this.pendingApprovals.delete(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience method for retrieving a login object
|
||||
* or creating a new one if needed.
|
||||
*
|
||||
* @param {string} origin = The origin string representing the domain.
|
||||
*/
|
||||
_initializePermissions (restoredState) {
|
||||
|
||||
// these permission requests are almost certainly stale
|
||||
const initState = { ...restoredState, permissionsRequests: [] }
|
||||
|
||||
this.permissions = new RpcCap({
|
||||
|
||||
// Supports passthrough methods:
|
||||
safeMethods: SAFE_METHODS,
|
||||
|
||||
// optional prefix for internal methods
|
||||
methodPrefix: WALLET_PREFIX,
|
||||
|
||||
restrictedMethods: this._restrictedMethods,
|
||||
|
||||
/**
|
||||
* A promise-returning callback used to determine whether to approve
|
||||
* permissions requests or not.
|
||||
*
|
||||
* Currently only returns a boolean, but eventually should return any
|
||||
* specific parameters or amendments to the permissions.
|
||||
*
|
||||
* @param {string} req - The internal rpc-cap user request object.
|
||||
*/
|
||||
requestUserApproval: async (req) => {
|
||||
const { origin, metadata: { id } } = req
|
||||
|
||||
if (this.pendingApprovalOrigins.has(origin)) {
|
||||
throw ethErrors.rpc.resourceUnavailable(
|
||||
'Permissions request already pending; please wait.',
|
||||
)
|
||||
}
|
||||
|
||||
this._platform.openExtensionInBrowser(`connect/${id}`)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this._addPendingApproval(id, origin, resolve, reject)
|
||||
})
|
||||
},
|
||||
}, initState)
|
||||
}
|
||||
}
|
||||
|
||||
export function addInternalMethodPrefix (method) {
|
||||
return WALLET_PREFIX + method
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
import createAsyncMiddleware from 'json-rpc-engine/src/createAsyncMiddleware'
|
||||
import { ethErrors } from 'eth-json-rpc-errors'
|
||||
|
||||
/**
|
||||
* Create middleware for handling certain methods and preprocessing permissions requests.
|
||||
*/
|
||||
export default function createMethodMiddleware ({
|
||||
getAccounts,
|
||||
getUnlockPromise,
|
||||
hasPermission,
|
||||
requestAccountsPermission,
|
||||
store,
|
||||
storeKey,
|
||||
}) {
|
||||
|
||||
const isProcessingRequestAccounts = false
|
||||
|
||||
return createAsyncMiddleware(async (req, res, next) => {
|
||||
|
||||
switch (req.method) {
|
||||
|
||||
// Intercepting eth_accounts requests for backwards compatibility:
|
||||
// The getAccounts call below wraps the rpc-cap middleware, and returns
|
||||
// an empty array in case of errors (such as 4100:unauthorized)
|
||||
case 'eth_accounts':
|
||||
|
||||
res.result = await getAccounts()
|
||||
return
|
||||
|
||||
case 'eth_requestAccounts':
|
||||
|
||||
if (isProcessingRequestAccounts) {
|
||||
res.error = ethErrors.rpc.resourceUnavailable(
|
||||
'Already processing eth_requestAccounts. Please wait.',
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// if (hasPermission('eth_accounts')) {
|
||||
// isProcessingRequestAccounts = true
|
||||
// await getUnlockPromise()
|
||||
// isProcessingRequestAccounts = false
|
||||
// }
|
||||
|
||||
// first, just try to get accounts
|
||||
// let accounts = await getAccounts()
|
||||
// if (accounts.length > 0) {
|
||||
// res.result = accounts
|
||||
// return
|
||||
// }
|
||||
|
||||
// if no accounts, request the accounts permission
|
||||
// try {
|
||||
// await requestAccountsPermission()
|
||||
// } catch (err) {
|
||||
// res.error = err
|
||||
// return
|
||||
// }
|
||||
|
||||
// get the accounts again
|
||||
const accounts = await getAccounts()
|
||||
/* istanbul ignore else: too hard to induce, see below comment */
|
||||
if (accounts.length > 0) {
|
||||
res.result = accounts
|
||||
} else {
|
||||
// this should never happen, because it should be caught in the
|
||||
// above catch clause
|
||||
res.error = ethErrors.rpc.internal(
|
||||
'Accounts unexpectedly unavailable. Please report this bug.',
|
||||
)
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
// custom method for getting metadata from the requesting domain,
|
||||
// sent automatically by the inpage provider
|
||||
case 'wallet_sendDomainMetadata':
|
||||
|
||||
const storeState = store.getState()[storeKey]
|
||||
const extensionId = storeState[req.origin]
|
||||
? storeState[req.origin].extensionId
|
||||
: undefined
|
||||
|
||||
if (
|
||||
req.domainMetadata &&
|
||||
typeof req.domainMetadata.name === 'string'
|
||||
) {
|
||||
|
||||
store.updateState({
|
||||
[storeKey]: {
|
||||
...storeState,
|
||||
[req.origin]: {
|
||||
extensionId,
|
||||
...req.domainMetadata,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
res.result = true
|
||||
return
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
next()
|
||||
})
|
||||
}
|
|
@ -0,0 +1,410 @@
|
|||
import { cloneDeep } from 'lodash'
|
||||
import {
|
||||
CAVEAT_NAMES,
|
||||
HISTORY_STORE_KEY,
|
||||
LOG_IGNORE_METHODS,
|
||||
LOG_LIMIT,
|
||||
LOG_METHOD_TYPES,
|
||||
LOG_STORE_KEY,
|
||||
WALLET_PREFIX,
|
||||
} from './enums'
|
||||
|
||||
/**
|
||||
* Controller with middleware for logging requests and responses to restricted
|
||||
* and permissions-related methods.
|
||||
*/
|
||||
export default class PermissionsLogController {
|
||||
|
||||
constructor ({ restrictedMethods, store }) {
|
||||
this.restrictedMethods = restrictedMethods
|
||||
this.store = store
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the activity log.
|
||||
*
|
||||
* @returns {Array<Object>} The activity log.
|
||||
*/
|
||||
getActivityLog () {
|
||||
return this.store.getState()[LOG_STORE_KEY] || []
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the activity log.
|
||||
*
|
||||
* @param {Array<Object>} logs - The new activity log array.
|
||||
*/
|
||||
updateActivityLog (logs) {
|
||||
this.store.updateState({ [LOG_STORE_KEY]: logs })
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the permissions history log.
|
||||
*
|
||||
* @returns {Object} The permissions history log.
|
||||
*/
|
||||
getHistory () {
|
||||
return this.store.getState()[HISTORY_STORE_KEY] || {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the permissions history log.
|
||||
*
|
||||
* @param {Object} history - The new permissions history log object.
|
||||
*/
|
||||
updateHistory (history) {
|
||||
this.store.updateState({ [HISTORY_STORE_KEY]: history })
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the exposed account history for the given origin.
|
||||
* Sets the 'last seen' time to Date.now() for the given accounts.
|
||||
*
|
||||
* @param {string} origin - The origin that the accounts are exposed to.
|
||||
* @param {Array<string>} accounts - The accounts.
|
||||
*/
|
||||
updateAccountsHistory (origin, accounts) {
|
||||
|
||||
if (accounts.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const accountToTimeMap = getAccountToTimeMap(accounts, Date.now())
|
||||
|
||||
this.commitNewHistory(origin, {
|
||||
eth_accounts: {
|
||||
accounts: accountToTimeMap,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a permissions log middleware. Records permissions activity and history:
|
||||
*
|
||||
* Activity: requests and responses for restricted and most wallet_ methods.
|
||||
*
|
||||
* History: for each origin, the last time a permission was granted, including
|
||||
* which accounts were exposed, if any.
|
||||
*
|
||||
* @returns {JsonRpcEngineMiddleware} The permissions log middleware.
|
||||
*/
|
||||
createMiddleware () {
|
||||
return (req, res, next, _end) => {
|
||||
|
||||
let activityEntry, requestedMethods
|
||||
const { origin, method } = req
|
||||
const isInternal = method.startsWith(WALLET_PREFIX)
|
||||
|
||||
// we only log certain methods
|
||||
if (
|
||||
!LOG_IGNORE_METHODS.includes(method) &&
|
||||
(isInternal || this.restrictedMethods.includes(method))
|
||||
) {
|
||||
|
||||
activityEntry = this.logRequest(req, isInternal)
|
||||
|
||||
if (method === `${WALLET_PREFIX}requestPermissions`) {
|
||||
// get the corresponding methods from the requested permissions so
|
||||
// that we can record permissions history
|
||||
requestedMethods = this.getRequestedMethods(req)
|
||||
}
|
||||
} else if (method === 'eth_requestAccounts') {
|
||||
|
||||
// eth_requestAccounts is a special case; we need to extract the accounts
|
||||
// from it
|
||||
activityEntry = this.logRequest(req, isInternal)
|
||||
requestedMethods = [ 'eth_accounts' ]
|
||||
} else {
|
||||
// no-op
|
||||
return next()
|
||||
}
|
||||
|
||||
// call next with a return handler for capturing the response
|
||||
next((cb) => {
|
||||
|
||||
const time = Date.now()
|
||||
this.logResponse(activityEntry, res, time)
|
||||
|
||||
if (requestedMethods && !res.error && res.result) {
|
||||
// any permissions or accounts changes will be recorded on the response,
|
||||
// so we only log permissions history here
|
||||
this.logPermissionsHistory(
|
||||
requestedMethods, origin, res.result, time,
|
||||
method === 'eth_requestAccounts',
|
||||
)
|
||||
}
|
||||
cb()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and commits an activity log entry, without response data.
|
||||
*
|
||||
* @param {Object} request - The request object.
|
||||
* @param {boolean} isInternal - Whether the request is internal.
|
||||
*/
|
||||
logRequest (request, isInternal) {
|
||||
const activityEntry = {
|
||||
id: request.id,
|
||||
method: request.method,
|
||||
methodType: (
|
||||
isInternal ? LOG_METHOD_TYPES.internal : LOG_METHOD_TYPES.restricted
|
||||
),
|
||||
origin: request.origin,
|
||||
request: cloneDeep(request),
|
||||
requestTime: Date.now(),
|
||||
response: null,
|
||||
responseTime: null,
|
||||
success: null,
|
||||
}
|
||||
this.commitNewActivity(activityEntry)
|
||||
return activityEntry
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds response data to an existing activity log entry.
|
||||
* Entry assumed already committed (i.e., in the log).
|
||||
*
|
||||
* @param {Object} entry - The entry to add a response to.
|
||||
* @param {Object} response - The response object.
|
||||
* @param {number} time - Output from Date.now()
|
||||
*/
|
||||
logResponse (entry, response, time) {
|
||||
|
||||
if (!entry || !response) {
|
||||
return
|
||||
}
|
||||
|
||||
entry.response = cloneDeep(response)
|
||||
entry.responseTime = time
|
||||
entry.success = !response.error
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit a new entry to the activity log.
|
||||
* Removes the oldest entry from the log if it exceeds the log limit.
|
||||
*
|
||||
* @param {Object} entry - The activity log entry.
|
||||
*/
|
||||
commitNewActivity (entry) {
|
||||
|
||||
const logs = this.getActivityLog()
|
||||
|
||||
// add new entry to end of log
|
||||
logs.push(entry)
|
||||
|
||||
// remove oldest log if exceeding size limit
|
||||
if (logs.length > LOG_LIMIT) {
|
||||
logs.shift()
|
||||
}
|
||||
|
||||
this.updateActivityLog(logs)
|
||||
}
|
||||
|
||||
/**
|
||||
* Record account exposure and eth_accounts permissions history for the given
|
||||
* origin.
|
||||
*
|
||||
* @param {string} origin - The origin accounts were exposed to.
|
||||
* @param {Array<string>} accounts - The accounts that were exposed.
|
||||
*/
|
||||
logAccountExposure (origin, accounts) {
|
||||
|
||||
if (
|
||||
typeof origin !== 'string' || !origin.length ||
|
||||
!Array.isArray(accounts) || accounts.length === 0
|
||||
) {
|
||||
throw new Error(
|
||||
'Must provide non-empty string origin and array of accounts.',
|
||||
)
|
||||
}
|
||||
|
||||
this.logPermissionsHistory(
|
||||
['eth_accounts'],
|
||||
origin,
|
||||
accounts,
|
||||
Date.now(),
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new permissions history log entries, if any, and commit them.
|
||||
*
|
||||
* @param {Array<string>} requestedMethods - The method names corresponding to the requested permissions.
|
||||
* @param {string} origin - The origin of the permissions request.
|
||||
* @param {Array<IOcapLdCapability>} result - The permissions request response.result.
|
||||
* @param {string} time - The time of the request, i.e. Date.now().
|
||||
* @param {boolean} isEthRequestAccounts - Whether the permissions request was 'eth_requestAccounts'.
|
||||
*/
|
||||
logPermissionsHistory (
|
||||
requestedMethods, origin, result,
|
||||
time, isEthRequestAccounts,
|
||||
) {
|
||||
|
||||
let accounts, newEntries
|
||||
|
||||
if (isEthRequestAccounts) {
|
||||
|
||||
accounts = result
|
||||
const accountToTimeMap = getAccountToTimeMap(accounts, time)
|
||||
|
||||
newEntries = {
|
||||
'eth_accounts': {
|
||||
accounts: accountToTimeMap,
|
||||
lastApproved: time,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
|
||||
// Records new "lastApproved" times for the granted permissions, if any.
|
||||
// Special handling for eth_accounts, in order to record the time the
|
||||
// accounts were last seen or approved by the origin.
|
||||
newEntries = result
|
||||
.map((perm) => {
|
||||
|
||||
if (perm.parentCapability === 'eth_accounts') {
|
||||
accounts = this.getAccountsFromPermission(perm)
|
||||
}
|
||||
|
||||
return perm.parentCapability
|
||||
})
|
||||
.reduce((acc, method) => {
|
||||
|
||||
// all approved permissions will be included in the response,
|
||||
// not just the newly requested ones
|
||||
if (requestedMethods.includes(method)) {
|
||||
|
||||
if (method === 'eth_accounts') {
|
||||
|
||||
const accountToTimeMap = getAccountToTimeMap(accounts, time)
|
||||
|
||||
acc[method] = {
|
||||
lastApproved: time,
|
||||
accounts: accountToTimeMap,
|
||||
}
|
||||
} else {
|
||||
acc[method] = { lastApproved: time }
|
||||
}
|
||||
}
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
|
||||
if (Object.keys(newEntries).length > 0) {
|
||||
this.commitNewHistory(origin, newEntries)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit new entries to the permissions history log.
|
||||
* Merges the history for the given origin, overwriting existing entries
|
||||
* with the same key (permission name).
|
||||
*
|
||||
* @param {string} origin - The requesting origin.
|
||||
* @param {Object} newEntries - The new entries to commit.
|
||||
*/
|
||||
commitNewHistory (origin, newEntries) {
|
||||
|
||||
// a simple merge updates most permissions
|
||||
const history = this.getHistory()
|
||||
const newOriginHistory = {
|
||||
...history[origin],
|
||||
...newEntries,
|
||||
}
|
||||
|
||||
// eth_accounts requires special handling, because of information
|
||||
// we store about the accounts
|
||||
const existingEthAccountsEntry = (
|
||||
history[origin] && history[origin]['eth_accounts']
|
||||
)
|
||||
const newEthAccountsEntry = newEntries['eth_accounts']
|
||||
|
||||
if (existingEthAccountsEntry && newEthAccountsEntry) {
|
||||
|
||||
// we may intend to update just the accounts, not the permission
|
||||
// itself
|
||||
const lastApproved = (
|
||||
newEthAccountsEntry.lastApproved ||
|
||||
existingEthAccountsEntry.lastApproved
|
||||
)
|
||||
|
||||
// merge old and new eth_accounts history entries
|
||||
newOriginHistory['eth_accounts'] = {
|
||||
lastApproved,
|
||||
accounts: {
|
||||
...existingEthAccountsEntry.accounts,
|
||||
...newEthAccountsEntry.accounts,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
history[origin] = newOriginHistory
|
||||
|
||||
this.updateHistory(history)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all requested methods from a permissions request.
|
||||
*
|
||||
* @param {Object} request - The request object.
|
||||
* @returns {Array<string>} The names of the requested permissions.
|
||||
*/
|
||||
getRequestedMethods (request) {
|
||||
if (
|
||||
!request.params ||
|
||||
!request.params[0] ||
|
||||
typeof request.params[0] !== 'object' ||
|
||||
Array.isArray(request.params[0])
|
||||
) {
|
||||
return null
|
||||
}
|
||||
return Object.keys(request.params[0])
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the permitted accounts from an eth_accounts permissions object.
|
||||
* Returns an empty array if the permission is not eth_accounts.
|
||||
*
|
||||
* @param {Object} perm - The permissions object.
|
||||
* @returns {Array<string>} The permitted accounts.
|
||||
*/
|
||||
getAccountsFromPermission (perm) {
|
||||
|
||||
if (perm.parentCapability !== 'eth_accounts' || !perm.caveats) {
|
||||
return []
|
||||
}
|
||||
|
||||
const accounts = new Set()
|
||||
for (const caveat of perm.caveats) {
|
||||
|
||||
if (
|
||||
caveat.name === CAVEAT_NAMES.exposedAccounts &&
|
||||
Array.isArray(caveat.value)
|
||||
) {
|
||||
|
||||
for (const value of caveat.value) {
|
||||
accounts.add(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
return [ ...accounts ]
|
||||
}
|
||||
}
|
||||
|
||||
// helper functions
|
||||
|
||||
/**
|
||||
* Get a map from account addresses to the given time.
|
||||
*
|
||||
* @param {Array<string>} accounts - An array of addresses.
|
||||
* @param {number} time - A time, e.g. Date.now().
|
||||
* @returns {Object} A string:number map of addresses to time.
|
||||
*/
|
||||
function getAccountToTimeMap (accounts, time) {
|
||||
return accounts.reduce(
|
||||
(acc, account) => ({ ...acc, [account]: time }), {},
|
||||
)
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
export default function getRestrictedMethods (permissionsController) {
|
||||
return {
|
||||
|
||||
'eth_accounts': {
|
||||
description: `View the addresses of the user's chosen accounts.`,
|
||||
method: (_, res, __, end) => {
|
||||
permissionsController.getKeyringAccounts()
|
||||
.then((accounts) => {
|
||||
res.result = accounts
|
||||
end()
|
||||
})
|
||||
.catch(
|
||||
(err) => {
|
||||
res.error = err
|
||||
end(err)
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,15 +1,21 @@
|
|||
const EventEmitter = require('events')
|
||||
const ObservableStore = require('obs-store')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const Transaction = require('ethereumjs-tx')
|
||||
const EthQuery = require('ethjs-query')
|
||||
const TransactionStateManager = require('./tx-state-manager')
|
||||
import EventEmitter from 'safe-event-emitter'
|
||||
import ObservableStore from 'obs-store'
|
||||
import ethUtil from 'ethereumjs-util'
|
||||
import Transaction from 'ethereumjs-tx'
|
||||
import EthQuery from 'ethjs-query'
|
||||
|
||||
import abi from 'human-standard-token-abi'
|
||||
import abiDecoder from 'abi-decoder'
|
||||
|
||||
abiDecoder.addABI(abi)
|
||||
|
||||
import TransactionStateManager from './tx-state-manager'
|
||||
const TxGasUtil = require('./tx-gas-utils')
|
||||
const PendingTransactionTracker = require('./pending-tx-tracker')
|
||||
const NonceTracker = require('./nonce-tracker')
|
||||
const txUtils = require('./lib/util')
|
||||
import * as txUtils from './lib/util'
|
||||
const cleanErrorStack = require('../../lib/cleanErrorStack')
|
||||
const log = require('loglevel')
|
||||
import log from 'loglevel'
|
||||
const recipientBlacklistChecker = require('./lib/recipient-blacklist-checker')
|
||||
const {
|
||||
TRANSACTION_TYPE_CANCEL,
|
||||
|
@ -222,19 +228,28 @@ class TransactionController extends EventEmitter {
|
|||
@return {txMeta}
|
||||
*/
|
||||
|
||||
async retryTransaction (originalTxId) {
|
||||
const originalTxMeta = this.txStateManager.getTx(originalTxId)
|
||||
const lastGasPrice = originalTxMeta.txParams.gasPrice
|
||||
const txMeta = this.txStateManager.generateTxMeta({
|
||||
txParams: originalTxMeta.txParams,
|
||||
lastGasPrice,
|
||||
loadingDefaults: false,
|
||||
type: TRANSACTION_TYPE_RETRY,
|
||||
})
|
||||
this.addTx(txMeta)
|
||||
this.emit('newUnapprovedTx', txMeta)
|
||||
return txMeta
|
||||
}
|
||||
async retryTransaction (originalTxId, gasPrice) {
|
||||
const originalTxMeta = this.txStateManager.getTx(originalTxId)
|
||||
const { txParams } = originalTxMeta
|
||||
const lastGasPrice = gasPrice || originalTxMeta.txParams.gasPrice
|
||||
const suggestedGasPriceBN = new ethUtil.BN(ethUtil.stripHexPrefix(this.getGasPrice()), 16)
|
||||
const lastGasPriceBN = new ethUtil.BN(ethUtil.stripHexPrefix(lastGasPrice), 16)
|
||||
// essentially lastGasPrice * 1.1 but
|
||||
// dont trust decimals so a round about way of doing that
|
||||
const lastGasPriceBNBumped = lastGasPriceBN.mul(new ethUtil.BN(110, 10)).div(new ethUtil.BN(100, 10))
|
||||
// transactions that are being retried require a >=%10 bump or the clients will throw an error
|
||||
txParams.gasPrice = suggestedGasPriceBN.gt(lastGasPriceBNBumped) ? `0x${suggestedGasPriceBN.toString(16)}` : `0x${lastGasPriceBNBumped.toString(16)}`
|
||||
|
||||
const txMeta = this.txStateManager.generateTxMeta({
|
||||
txParams: originalTxMeta.txParams,
|
||||
lastGasPrice,
|
||||
loadingDefaults: false,
|
||||
type: TRANSACTION_TYPE_RETRY,
|
||||
})
|
||||
this.addTx(txMeta)
|
||||
this.emit('newUnapprovedTx', txMeta)
|
||||
return txMeta
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new approved transaction to attempt to cancel a previously submitted transaction. The
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
const jsonDiffer = require('fast-json-patch')
|
||||
const clone = require('clone')
|
||||
import jsonDiffer from 'fast-json-patch'
|
||||
import { cloneDeep } from 'lodash'
|
||||
|
||||
/** @module*/
|
||||
module.exports = {
|
||||
export default {
|
||||
generateHistoryEntry,
|
||||
replayHistory,
|
||||
snapshotFromTxMeta,
|
||||
|
@ -10,17 +11,19 @@ module.exports = {
|
|||
|
||||
/**
|
||||
converts non-initial history entries into diffs
|
||||
@param longHistory {array}
|
||||
@param {array} longHistory
|
||||
@returns {array}
|
||||
*/
|
||||
function migrateFromSnapshotsToDiffs (longHistory) {
|
||||
return (
|
||||
longHistory
|
||||
// convert non-initial history entries into diffs
|
||||
.map((entry, index) => {
|
||||
if (index === 0) return entry
|
||||
return generateHistoryEntry(longHistory[index - 1], entry)
|
||||
})
|
||||
.map((entry, index) => {
|
||||
if (index === 0) {
|
||||
return entry
|
||||
}
|
||||
return generateHistoryEntry(longHistory[index - 1], entry)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -31,16 +34,18 @@ function migrateFromSnapshotsToDiffs (longHistory) {
|
|||
path (the key and if a nested object then each key will be seperated with a `/`)
|
||||
value
|
||||
with the first entry having the note and a timestamp when the change took place
|
||||
@param previousState {object} - the previous state of the object
|
||||
@param newState {object} - the update object
|
||||
@param note {string} - a optional note for the state change
|
||||
@param {Object} previousState - the previous state of the object
|
||||
@param {Object} newState - the update object
|
||||
@param {string} [note] - a optional note for the state change
|
||||
@returns {array}
|
||||
*/
|
||||
function generateHistoryEntry (previousState, newState, note) {
|
||||
const entry = jsonDiffer.compare(previousState, newState)
|
||||
// Add a note to the first op, since it breaks if we append it to the entry
|
||||
if (entry[0]) {
|
||||
if (note) entry[0].note = note
|
||||
if (note) {
|
||||
entry[0].note = note
|
||||
}
|
||||
|
||||
entry[0].timestamp = Date.now()
|
||||
}
|
||||
|
@ -49,20 +54,20 @@ function generateHistoryEntry (previousState, newState, note) {
|
|||
|
||||
/**
|
||||
Recovers previous txMeta state obj
|
||||
@returns {object}
|
||||
@returns {Object}
|
||||
*/
|
||||
function replayHistory (_shortHistory) {
|
||||
const shortHistory = clone(_shortHistory)
|
||||
const shortHistory = cloneDeep(_shortHistory)
|
||||
return shortHistory.reduce((val, entry) => jsonDiffer.applyPatch(val, entry).newDocument)
|
||||
}
|
||||
|
||||
/**
|
||||
@param txMeta {Object}
|
||||
@returns {object} a clone object of the txMeta with out history
|
||||
@param {Object} txMeta
|
||||
@returns {Object} - a clone object of the txMeta with out history
|
||||
*/
|
||||
function snapshotFromTxMeta (txMeta) {
|
||||
// create txMeta snapshot for history
|
||||
const snapshot = clone(txMeta)
|
||||
const snapshot = cloneDeep(txMeta)
|
||||
// dont include previous history in this snapshot
|
||||
delete snapshot.history
|
||||
return snapshot
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
const extend = require('xtend')
|
||||
const EventEmitter = require('events')
|
||||
const ObservableStore = require('obs-store')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const log = require('loglevel')
|
||||
const txStateHistoryHelper = require('./lib/tx-state-history-helper')
|
||||
const createId = require('../../lib/random-id')
|
||||
const { getFinalStates } = require('./lib/util')
|
||||
import ethUtil from 'ethereumjs-util'
|
||||
import extend from 'extend'
|
||||
import EventEmitter from 'safe-event-emitter'
|
||||
import ObservableStore from 'obs-store'
|
||||
import log from 'loglevel'
|
||||
import txStateHistoryHelper from './lib/tx-state-history-helper'
|
||||
import createId from '../../lib/random-id'
|
||||
import { getFinalStates } from './lib/util'
|
||||
/**
|
||||
TransactionStateManager is responsible for the state of a transaction and
|
||||
storing the transaction
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const log = require('loglevel')
|
||||
import log from 'loglevel'
|
||||
|
||||
module.exports = createLoggerMiddleware
|
||||
export default createLoggerMiddleware
|
||||
|
||||
/**
|
||||
* Returns a middleware that logs RPC activity
|
||||
|
@ -13,7 +13,9 @@ function createLoggerMiddleware (opts) {
|
|||
if (res.error) {
|
||||
log.error('Error in RPC response:\n', res)
|
||||
}
|
||||
if (req.isMetamaskInternal) return
|
||||
if (req.isMetamaskInternal) {
|
||||
return
|
||||
}
|
||||
log.info(`RPC (${opts.origin}):`, req, '->', res)
|
||||
cb()
|
||||
})
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
const ethJsRpcSlug = 'Error: [ethjs-rpc] rpc error with payload '
|
||||
const errorLabelPrefix = 'Error: '
|
||||
|
||||
module.exports = extractEthjsErrorMessage
|
||||
export default extractEthjsErrorMessage
|
||||
|
||||
|
||||
/**
|
||||
* Extracts the important part of an ethjs-rpc error message. If the passed error is not an isEthjsRpcError, the error
|
||||
* is returned unchanged.
|
||||
*
|
||||
* @param {string} errorMessage The error message to parse
|
||||
* @returns {string} Returns an error message, either the same as was passed, or the ending message portion of an isEthjsRpcError
|
||||
* @param {string} errorMessage - The error message to parse
|
||||
* @returns {string} - Returns an error message, either the same as was passed, or the ending message portion of an isEthjsRpcError
|
||||
*
|
||||
* @example
|
||||
* // returns 'Transaction Failed: replacement transaction underpriced'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const BN = require('ethereumjs-util').BN
|
||||
const normalize = require('eth-sig-util').normalize
|
||||
import { BN } from 'ethereumjs-util'
|
||||
import { normalize } from 'eth-sig-util'
|
||||
|
||||
class PendingBalanceCalculator {
|
||||
|
||||
|
@ -8,8 +8,8 @@ class PendingBalanceCalculator {
|
|||
* pending transactions.
|
||||
*
|
||||
* @typedef {Object} PendingBalanceCalculator
|
||||
* @param {Function} getBalance Returns a promise of a BN of the current balance in Wei
|
||||
* @param {Function} getPendingTransactions Returns an array of TxMeta Objects, which have txParams properties,
|
||||
* @param {Function} getBalance - Returns a promise of a BN of the current balance in Wei
|
||||
* @param {Function} getPendingTransactions - Returns an array of TxMeta Objects, which have txParams properties,
|
||||
* which include value, gasPrice, and gas, all in a base=16 hex format.
|
||||
*
|
||||
*/
|
||||
|
@ -22,7 +22,7 @@ class PendingBalanceCalculator {
|
|||
* Returns the users "pending balance": their current balance minus the total possible cost of all their
|
||||
* pending transactions.
|
||||
*
|
||||
* @returns {Promise<string>} Promises a base 16 hex string that contains the user's "pending balance"
|
||||
* @returns {Promise<string>} - Promises a base 16 hex string that contains the user's "pending balance"
|
||||
*
|
||||
*/
|
||||
async getBalance () {
|
||||
|
@ -32,7 +32,9 @@ class PendingBalanceCalculator {
|
|||
])
|
||||
|
||||
const [ balance, pending ] = results
|
||||
if (!balance) return undefined
|
||||
if (!balance) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const pendingValue = pending.reduce((total, tx) => {
|
||||
return total.add(this.calculateMaxCost(tx))
|
||||
|
@ -44,11 +46,11 @@ class PendingBalanceCalculator {
|
|||
/**
|
||||
* Calculates the maximum possible cost of a single transaction, based on the value, gas price and gas limit.
|
||||
*
|
||||
* @param {object} tx Contains all that data about a transaction.
|
||||
* @param {Object} tx - Contains all that data about a transaction.
|
||||
* @property {object} tx.txParams Contains data needed to calculate the maximum cost of the transaction: gas,
|
||||
* gasLimit and value.
|
||||
*
|
||||
* @returns {string} Returns a base 16 hex string that contains the maximum possible cost of the transaction.
|
||||
* @returns {string} - Returns a base 16 hex string that contains the maximum possible cost of the transaction.
|
||||
*/
|
||||
calculateMaxCost (tx) {
|
||||
const txValue = tx.txParams.value
|
||||
|
@ -66,8 +68,8 @@ class PendingBalanceCalculator {
|
|||
/**
|
||||
* Converts a hex string to a BN object
|
||||
*
|
||||
* @param {string} hex A number represented as a hex string
|
||||
* @returns {Object} A BN object
|
||||
* @param {string} hex - A number represented as a hex string
|
||||
* @returns {Object} - A BN object
|
||||
*
|
||||
*/
|
||||
hexToBn (hex) {
|
||||
|
@ -76,4 +78,4 @@ class PendingBalanceCalculator {
|
|||
|
||||
}
|
||||
|
||||
module.exports = PendingBalanceCalculator
|
||||
export default PendingBalanceCalculator
|
||||
|
|
|
@ -6,4 +6,4 @@ function createRandomId () {
|
|||
return idCounter++
|
||||
}
|
||||
|
||||
module.exports = createRandomId
|
||||
export default createRandomId
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const extractEthjsErrorMessage = require('./extractEthjsErrorMessage')
|
||||
import extractEthjsErrorMessage from './extractEthjsErrorMessage'
|
||||
|
||||
module.exports = reportFailedTxToSentry
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const Raven = require('raven-js')
|
||||
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
|
||||
const extractEthjsErrorMessage = require('./extractEthjsErrorMessage')
|
||||
import extractEthjsErrorMessage from './extractEthjsErrorMessage'
|
||||
const PROD = 'https://3bd485f8ed6047d882f3f010cbae46ca@sentry.io/1250701'
|
||||
const DEV = 'https://267dbd2f3447444faa637bc34bcc7317@sentry.io/1253429'
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ const createEngineStream = require('json-rpc-middleware-stream/engineStream')
|
|||
const createFilterMiddleware = require('eth-json-rpc-filters')
|
||||
const createSubscriptionManager = require('eth-json-rpc-filters/subscriptionManager')
|
||||
const createOriginMiddleware = require('./lib/createOriginMiddleware')
|
||||
const createLoggerMiddleware = require('./lib/createLoggerMiddleware')
|
||||
import createLoggerMiddleware from './lib/createLoggerMiddleware'
|
||||
import createTabIdMiddleware from './lib/createTabIdMiddleware'
|
||||
import providerAsMiddleware from 'eth-json-rpc-middleware/providerAsMiddleware'
|
||||
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
|
||||
|
@ -42,6 +42,8 @@ const TransactionController = require('./controllers/transactions')
|
|||
const BalancesController = require('./controllers/computed-balances')
|
||||
const TokenRatesController = require('./controllers/token-rates')
|
||||
const DetectTokensController = require('./controllers/detect-tokens')
|
||||
// import { PermissionsController } from './controllers/permissions'
|
||||
// import getRestrictedMethods from './controllers/permissions/restrictedMethods'
|
||||
const nodeify = require('./lib/nodeify')
|
||||
const accountImporter = require('./account-import-strategies')
|
||||
import { Mutex } from 'await-semaphore'
|
||||
|
@ -206,6 +208,14 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||
this.keyringController.memStore.subscribe((s) => this._onKeyringControllerUpdate(s))
|
||||
this.keyringController.on('unlock', () => this.emit('unlock'))
|
||||
|
||||
// this.permissionsController = new PermissionsController({
|
||||
// getKeyringAccounts: this.keyringController.getAccounts.bind(this.keyringController),
|
||||
// getRestrictedMethods,
|
||||
// notifyDomain: this.notifyConnections.bind(this),
|
||||
// notifyAllDomains: this.notifyAllConnections.bind(this),
|
||||
// platform: opts.platform,
|
||||
// }, initState.PermissionsController, initState.PermissionsMetadata)
|
||||
|
||||
// detect tokens controller
|
||||
this.detectTokensController = new DetectTokensController({
|
||||
preferences: this.preferencesController,
|
||||
|
@ -285,6 +295,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||
NetworkController: this.networkController.store,
|
||||
InfuraController: this.infuraController.store,
|
||||
CachedBalancesController: this.cachedBalancesController.store,
|
||||
// PermissionsController: this.permissionsController.permissions,
|
||||
// PermissionsMetadata: this.permissionsController.store,
|
||||
})
|
||||
|
||||
this.memStore = new ComposableObservableStore(null, {
|
||||
|
@ -307,6 +319,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||
NoticeController: this.noticeController.memStore,
|
||||
ShapeshiftController: this.shapeshiftController.store,
|
||||
InfuraController: this.infuraController.store,
|
||||
// PermissionsController: this.permissionsController.permissions,
|
||||
// PermissionsMetadata: this.permissionsController.store,
|
||||
})
|
||||
this.memStore.subscribe(this.sendUpdate.bind(this))
|
||||
}
|
||||
|
@ -341,6 +355,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||
processDecryptMessage: this.newRequestDecryptMessage.bind(this),
|
||||
processEncryptionPublicKey: this.newRequestEncryptionPublicKey.bind(this),
|
||||
getPendingNonce: this.getPendingNonce.bind(this),
|
||||
getPendingTransactionByHash: (hash) => this.txController.getFilteredTxList({ hash, status: 'submitted' })[0],
|
||||
}
|
||||
const providerProxy = this.networkController.initializeProvider(providerOpts)
|
||||
return providerProxy
|
||||
|
@ -414,6 +429,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||
const txController = this.txController
|
||||
const noticeController = this.noticeController
|
||||
const addressBookController = this.addressBookController
|
||||
// const permissionsController = this.permissionsController
|
||||
|
||||
return {
|
||||
// etc
|
||||
|
@ -514,6 +530,16 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||
// notices
|
||||
checkNotices: noticeController.updateNoticesList.bind(noticeController),
|
||||
markNoticeRead: noticeController.markNoticeRead.bind(noticeController),
|
||||
|
||||
// // permissions
|
||||
// approvePermissionsRequest: nodeify(permissionsController.approvePermissionsRequest, permissionsController),
|
||||
// clearPermissions: permissionsController.clearPermissions.bind(permissionsController),
|
||||
// getApprovedAccounts: nodeify(permissionsController.getAccounts.bind(permissionsController)),
|
||||
// rejectPermissionsRequest: nodeify(permissionsController.rejectPermissionsRequest, permissionsController),
|
||||
// removePermissionsFor: permissionsController.removePermissionsFor.bind(permissionsController),
|
||||
// updatePermittedAccounts: nodeify(permissionsController.updatePermittedAccounts, permissionsController),
|
||||
// legacyExposeAccounts: nodeify(permissionsController.legacyExposeAccounts, permissionsController),
|
||||
// handleNewAccountSelected: nodeify(this.handleNewAccountSelected, this),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1710,6 +1736,8 @@ cancelEncryptionPublicKey (msgId, cb) {
|
|||
// filter and subscription polyfills
|
||||
engine.push(filterMiddleware)
|
||||
engine.push(subscriptionManager.middleware)
|
||||
// permissions
|
||||
// engine.push(this.permissionsController.createMiddleware({ origin, extensionId }))
|
||||
// watch asset
|
||||
engine.push(this.preferencesController.requestWatchAsset.bind(this.preferencesController))
|
||||
// sign typed data middleware
|
||||
|
|
|
@ -7,7 +7,7 @@ This migration updates "transaction state history" to diffs style
|
|||
*/
|
||||
|
||||
const clone = require('clone')
|
||||
const txStateHistoryHelper = require('../controllers/transactions/lib/tx-state-history-helper')
|
||||
import txStateHistoryHelper from '../controllers/transactions/lib/tx-state-history-helper'
|
||||
|
||||
|
||||
module.exports = {
|
||||
|
|
|
@ -157,7 +157,7 @@ class ExtensionPlatform {
|
|||
}
|
||||
|
||||
_subscribeToNotificationClicked () {
|
||||
if (extension.notifications.onClicked.hasListener(this._viewOnExplorer)) {
|
||||
if (!extension.notifications.onClicked.hasListener(this._viewOnExplorer)) {
|
||||
extension.notifications.onClicked.removeListener(this._viewOnExplorer)
|
||||
}
|
||||
extension.notifications.onClicked.addListener(this._viewOnExplorer)
|
||||
|
|
|
@ -4,7 +4,7 @@ const startPopup = require('./popup-core')
|
|||
const PortStream = require('extension-port-stream')
|
||||
const { getEnvironmentType } = require('./lib/util')
|
||||
const { ENVIRONMENT_TYPE_NOTIFICATION } = require('./lib/enums')
|
||||
const extension = require('extensionizer')
|
||||
import extension from 'extensionizer'
|
||||
const ExtensionPlatform = require('./platforms/extension')
|
||||
const NotificationManager = require('./lib/notification-manager')
|
||||
const notificationManager = new NotificationManager()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const txStateHistoryHelper = require('../../app/scripts/controllers/transactions/lib/tx-state-history-helper')
|
||||
import txStateHistoryHelper from '../../app/scripts/controllers/transactions/lib/tx-state-history-helper'
|
||||
|
||||
module.exports = createTxMeta
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const JsonRpcEngine = require('json-rpc-engine')
|
||||
import JsonRpcEngine from 'json-rpc-engine'
|
||||
const scaffoldMiddleware = require('eth-json-rpc-middleware/scaffold')
|
||||
const providerAsMiddleware = require('eth-json-rpc-middleware/providerAsMiddleware')
|
||||
const GanacheCore = require('ganache-core')
|
||||
|
|
|
@ -68,6 +68,7 @@ describe('MetaMaskController', function () {
|
|||
},
|
||||
},
|
||||
initState: cloneDeep(firstTimeState),
|
||||
platform: { showTransactionNotification: () => {} },
|
||||
})
|
||||
// disable diagnostics
|
||||
metamaskController.diagnostics = null
|
||||
|
|
|
@ -28,6 +28,9 @@ describe('Transaction Controller', function () {
|
|||
blockTrackerStub.getLatestBlock = noop
|
||||
txController = new TransactionController({
|
||||
provider,
|
||||
getGasPrice: function () {
|
||||
return '0xee6b2800'
|
||||
},
|
||||
networkStore: new ObservableStore(currentNetworkId),
|
||||
txHistoryLimit: 10,
|
||||
blockTracker: blockTrackerStub,
|
||||
|
@ -414,26 +417,28 @@ describe('Transaction Controller', function () {
|
|||
})
|
||||
|
||||
describe('#retryTransaction', function () {
|
||||
it('should create a new txMeta with the same txParams as the original one', function (done) {
|
||||
it('should create a new txMeta with the same txParams as the original one but with a higher gasPrice', function (done) {
|
||||
const txParams = {
|
||||
gasPrice: '0xee6b2800',
|
||||
nonce: '0x00',
|
||||
from: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4',
|
||||
to: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4',
|
||||
data: '0x0',
|
||||
}
|
||||
txController.txStateManager._saveTxList([
|
||||
{ id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams, history: [] },
|
||||
{ id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams, history: [{}] },
|
||||
])
|
||||
txController.retryTransaction(1)
|
||||
.then((txMeta) => {
|
||||
assert.equal(txMeta.txParams.nonce, txParams.nonce, 'nonce should be the same')
|
||||
assert.equal(txMeta.txParams.from, txParams.from, 'from should be the same')
|
||||
assert.equal(txMeta.txParams.to, txParams.to, 'to should be the same')
|
||||
assert.equal(txMeta.txParams.data, txParams.data, 'data should be the same')
|
||||
assert.ok(('lastGasPrice' in txMeta), 'should have the key `lastGasPrice`')
|
||||
assert.equal(txController.txStateManager.getTxList().length, 2)
|
||||
done()
|
||||
}).catch(done)
|
||||
.then((txMeta) => {
|
||||
assert.equal(txMeta.txParams.gasPrice, '0x10642ac00', 'gasPrice should have a %10 gasPrice bump')
|
||||
assert.equal(txMeta.txParams.nonce, txParams.nonce, 'nonce should be the same')
|
||||
assert.equal(txMeta.txParams.from, txParams.from, 'from should be the same')
|
||||
assert.equal(txMeta.txParams.to, txParams.to, 'to should be the same')
|
||||
assert.equal(txMeta.txParams.data, txParams.data, 'data should be the same')
|
||||
assert.ok(('lastGasPrice' in txMeta), 'should have the key `lastGasPrice`')
|
||||
assert.equal(txController.txStateManager.getTxList().length, 2)
|
||||
done()
|
||||
}).catch(done)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const assert = require('assert')
|
||||
const txStateHistoryHelper = require('../../../../../app/scripts/controllers/transactions/lib/tx-state-history-helper')
|
||||
import txStateHistoryHelper from '../../../../../app/scripts/controllers/transactions/lib/tx-state-history-helper'
|
||||
const testVault = require('../../../../data/v17-long-history.json')
|
||||
|
||||
describe('Transaction state history helper', function () {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const assert = require('assert')
|
||||
const TxStateManager = require('../../../../../app/scripts/controllers/transactions/tx-state-manager')
|
||||
const txStateHistoryHelper = require('../../../../../app/scripts/controllers/transactions/lib/tx-state-history-helper')
|
||||
import txStateHistoryHelper from '../../../../../app/scripts/controllers/transactions/lib/tx-state-history-helper'
|
||||
const noop = () => true
|
||||
|
||||
describe('TransactionStateManager', function () {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
const assert = require('assert')
|
||||
const txUtils = require('../../../../../app/scripts/controllers/transactions/lib/util')
|
||||
import assert from 'assert'
|
||||
import * as txUtils from '../../../../../app/scripts/controllers/transactions/lib/util'
|
||||
|
||||
|
||||
describe('txUtils', function () {
|
||||
describe('#validateTxParams', function () {
|
||||
it('does not throw for positive values', function () {
|
||||
var sample = {
|
||||
const sample = {
|
||||
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
|
||||
value: '0x01',
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ describe('txUtils', function () {
|
|||
})
|
||||
|
||||
it('returns error for negative values', function () {
|
||||
var sample = {
|
||||
const sample = {
|
||||
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
|
||||
value: '-0x01',
|
||||
}
|
||||
|
@ -25,8 +25,8 @@ describe('txUtils', function () {
|
|||
})
|
||||
})
|
||||
|
||||
describe('#normalizeTxParams', () => {
|
||||
it('should normalize txParams', () => {
|
||||
describe('#normalizeTxParams', function () {
|
||||
it('should normalize txParams', function () {
|
||||
const txParams = {
|
||||
chainId: '0x1',
|
||||
from: 'a7df1beDBF813f57096dF77FCd515f0B3900e402',
|
||||
|
@ -50,7 +50,7 @@ describe('txUtils', function () {
|
|||
})
|
||||
})
|
||||
|
||||
describe('#validateRecipient', () => {
|
||||
describe('#validateRecipient', function () {
|
||||
it('removes recipient for txParams with 0x when contract data is provided', function () {
|
||||
const zeroRecipientandDataTxParams = {
|
||||
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
|
||||
|
@ -66,33 +66,43 @@ describe('txUtils', function () {
|
|||
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
|
||||
to: '0x',
|
||||
}
|
||||
assert.throws(() => { txUtils.validateRecipient(zeroRecipientTxParams) }, Error, 'Invalid recipient address')
|
||||
assert.throws(() => {
|
||||
txUtils.validateRecipient(zeroRecipientTxParams)
|
||||
}, Error, 'Invalid recipient address')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
describe('#validateFrom', () => {
|
||||
describe('#validateFrom', function () {
|
||||
it('should error when from is not a hex string', function () {
|
||||
|
||||
// where from is undefined
|
||||
const txParams = {}
|
||||
assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`)
|
||||
assert.throws(() => {
|
||||
txUtils.validateFrom(txParams)
|
||||
}, Error, `Invalid from address ${txParams.from} not a string`)
|
||||
|
||||
// where from is array
|
||||
txParams.from = []
|
||||
assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`)
|
||||
assert.throws(() => {
|
||||
txUtils.validateFrom(txParams)
|
||||
}, Error, `Invalid from address ${txParams.from} not a string`)
|
||||
|
||||
// where from is a object
|
||||
txParams.from = {}
|
||||
assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`)
|
||||
assert.throws(() => {
|
||||
txUtils.validateFrom(txParams)
|
||||
}, Error, `Invalid from address ${txParams.from} not a string`)
|
||||
|
||||
// where from is a invalid address
|
||||
txParams.from = 'im going to fail'
|
||||
assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address`)
|
||||
assert.throws(() => {
|
||||
txUtils.validateFrom(txParams)
|
||||
}, Error, `Invalid from address`)
|
||||
|
||||
// should run
|
||||
txParams.from = '0x1678a085c290ebd122dc42cba69373b5953b831d'
|
||||
txUtils.validateFrom(txParams)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const assert = require('assert')
|
||||
const PendingBalanceCalculator = require('../../../app/scripts/lib/pending-balance-calculator')
|
||||
import PendingBalanceCalculator from '../../../app/scripts/lib/pending-balance-calculator'
|
||||
const MockTxGen = require('../../lib/mock-tx-gen')
|
||||
const BN = require('ethereumjs-util').BN
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
export const UNAPPROVED_STATUS = 'unapproved'
|
||||
export const REJECTED_STATUS = 'rejected'
|
||||
export const APPROVED_STATUS = 'approved'
|
||||
export const SIGNED_STATUS = 'signed'
|
||||
export const SUBMITTED_STATUS = 'submitted'
|
||||
export const CONFIRMED_STATUS = 'confirmed'
|
||||
export const FAILED_STATUS = 'failed'
|
||||
export const DROPPED_STATUS = 'dropped'
|
||||
export const CANCELLED_STATUS = 'cancelled'
|
||||
|
||||
export const TOKEN_METHOD_TRANSFER = 'transfer'
|
||||
export const TOKEN_METHOD_APPROVE = 'approve'
|
||||
export const TOKEN_METHOD_TRANSFER_FROM = 'transferfrom'
|
||||
|
||||
export const SEND_ETHER_ACTION_KEY = 'sentEther'
|
||||
export const DEPLOY_CONTRACT_ACTION_KEY = 'contractDeployment'
|
||||
export const APPROVE_ACTION_KEY = 'approve'
|
||||
export const SEND_TOKEN_ACTION_KEY = 'sentTokens'
|
||||
export const TRANSFER_FROM_ACTION_KEY = 'transferFrom'
|
||||
export const SIGNATURE_REQUEST_KEY = 'signatureRequest'
|
||||
export const DECRYPT_REQUEST_KEY = 'decryptRequest'
|
||||
export const ENCRYPTION_PUBLIC_KEY_REQUEST_KEY = 'encryptionPublicKeyRequest'
|
||||
export const CONTRACT_INTERACTION_KEY = 'contractInteraction'
|
||||
export const CANCEL_ATTEMPT_ACTION_KEY = 'cancelAttempt'
|
||||
export const DEPOSIT_TRANSACTION_KEY = 'deposit'
|
||||
|
||||
export const TRANSACTION_TYPE_SHAPESHIFT = 'shapeshift'
|
|
@ -4,13 +4,11 @@ const connect = require('react-redux').connect
|
|||
const h = require('react-hyperscript')
|
||||
const { HashRouter } = require('react-router-dom')
|
||||
const OldApp = require('../../old-ui/app/app')
|
||||
const { autoAddToBetaUI } = require('./selectors')
|
||||
const { setFeatureFlag } = require('./actions')
|
||||
const I18nProvider = require('./i18n-provider')
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
autoAdd: autoAddToBetaUI(state),
|
||||
isUnlocked: state.metamask.isUnlocked,
|
||||
isMascara: state.metamask.isMascara,
|
||||
firstTime: Object.keys(state.metamask.identities).length === 0,
|
||||
|
|
|
@ -27,7 +27,6 @@ const selectors = {
|
|||
getSendAmount,
|
||||
getSelectedTokenToFiatRate,
|
||||
getSelectedTokenContract,
|
||||
autoAddToBetaUI,
|
||||
getSendMaxModeState,
|
||||
getCurrentViewContext,
|
||||
getTotalUnapprovedCount,
|
||||
|
@ -184,23 +183,6 @@ function getSelectedTokenContract (state) {
|
|||
: null
|
||||
}
|
||||
|
||||
function autoAddToBetaUI (state) {
|
||||
const autoAddTransactionThreshold = 12
|
||||
const autoAddAccountsThreshold = 2
|
||||
const autoAddTokensThreshold = 1
|
||||
|
||||
const numberOfTransactions = state.metamask.selectedAddressTxList.length
|
||||
const numberOfAccounts = Object.keys(getMetaMaskAccounts(state)).length
|
||||
const numberOfTokensAdded = state.metamask.tokens.length
|
||||
|
||||
const userPassesThreshold = (numberOfTransactions > autoAddTransactionThreshold) &&
|
||||
(numberOfAccounts > autoAddAccountsThreshold) &&
|
||||
(numberOfTokensAdded > autoAddTokensThreshold)
|
||||
const userIsNotInBeta = !state.metamask.featureFlags.betaUI
|
||||
|
||||
return userIsNotInBeta && userPassesThreshold
|
||||
}
|
||||
|
||||
function getCurrentViewContext (state) {
|
||||
const { currentView = {} } = state.appState
|
||||
return currentView.context
|
||||
|
|
Loading…
Reference in New Issue