mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-23 02:10:12 +01:00
Merge remote-tracking branch 'origin/develop' into Version-v8.0.0
* origin/develop: (35 commits) Delete unused InfuraController & tests (#8773) Permissions: Do not display HTTP/HTTPS URL schemes for unique hosts (#8768) Refactor confirm approve page (#8757) blocklisted -> blocked Update app/scripts/contentscript.js blacklist -> blocklist; whitelist -> safelist replace blacklist with blocklist Delete unused transaction history test state (#8769) fix-formatting-of-gif (#8767) Order accounts on connect page (#8762) add gif for loading dev build (#8766) Bump websocket-extensions from 0.1.3 to 0.1.4 (#8759) Fix prop type mismatch (#8754) use grid template to position list item (#8753) Fix account menu entry for imported accounts (#8747) Fix permissions connect close and redirect behavior (#8751) Refactor `TokenBalance` component (#8752) Fix 'Remove account' in Account Options menu (#8748) move activation logic into token rates controller (#8744) asset outdated warning inline on full screen (#8734) ...
This commit is contained in:
commit
3604c3519c
@ -1,4 +1,8 @@
|
||||
{
|
||||
"acceptTermsOfUse": {
|
||||
"message": "I have read and agree to the $1",
|
||||
"description": "$1 is the `terms` message"
|
||||
},
|
||||
"eth_accounts": {
|
||||
"message": "View the addresses of your permitted accounts (required)",
|
||||
"description": "The description for the `eth_accounts` permission"
|
||||
|
@ -134,7 +134,6 @@ initialize().catch(log.error)
|
||||
* @property {string} currentCurrency - A string identifying the user's preferred display currency, for use in showing conversion rates.
|
||||
* @property {number} conversionRate - A number representing the current exchange rate from the user's preferred currency to Ether.
|
||||
* @property {number} conversionDate - A unix epoch date (ms) for the time the current conversion rate was last retrieved.
|
||||
* @property {Object} infuraNetworkStatus - An object of infura network status checks.
|
||||
* @property {boolean} forgottenPassword - Returns true if the user has initiated the password recovery screen, is recovering from seed phrase.
|
||||
*/
|
||||
|
||||
@ -319,7 +318,7 @@ function setupController (initState, initLangCode) {
|
||||
[ENVIRONMENT_TYPE_FULLSCREEN]: true,
|
||||
}
|
||||
|
||||
const metamaskBlacklistedPorts = [
|
||||
const metamaskBlockedPorts = [
|
||||
'trezor-connect',
|
||||
]
|
||||
|
||||
@ -343,7 +342,7 @@ function setupController (initState, initLangCode) {
|
||||
const processName = remotePort.name
|
||||
const isMetaMaskInternalProcess = metamaskInternalProcessHash[processName]
|
||||
|
||||
if (metamaskBlacklistedPorts.includes(remotePort.name)) {
|
||||
if (metamaskBlockedPorts.includes(remotePort.name)) {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -384,7 +383,7 @@ function setupController (initState, initLangCode) {
|
||||
if (remotePort.sender && remotePort.sender.tab && remotePort.sender.url) {
|
||||
const tabId = remotePort.sender.tab.id
|
||||
const url = new URL(remotePort.sender.url)
|
||||
const origin = url.hostname
|
||||
const { origin } = url
|
||||
|
||||
remotePort.onMessage.addListener((msg) => {
|
||||
if (msg.data && msg.data.method === 'eth_requestAccounts') {
|
||||
|
@ -127,7 +127,7 @@ function logStreamDisconnectWarning (remoteLabel, err) {
|
||||
*/
|
||||
function shouldInjectProvider () {
|
||||
return doctypeCheck() && suffixCheck() &&
|
||||
documentElementCheck() && !blacklistedDomainCheck()
|
||||
documentElementCheck() && !blockedDomainCheck()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -181,12 +181,12 @@ function documentElementCheck () {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current domain is blacklisted
|
||||
* Checks if the current domain is blocked
|
||||
*
|
||||
* @returns {boolean} {@code true} - if the current domain is blacklisted
|
||||
* @returns {boolean} {@code true} - if the current domain is blocked
|
||||
*/
|
||||
function blacklistedDomainCheck () {
|
||||
const blacklistedDomains = [
|
||||
function blockedDomainCheck () {
|
||||
const blockedDomains = [
|
||||
'uscourts.gov',
|
||||
'dropbox.com',
|
||||
'webbyawards.com',
|
||||
@ -200,9 +200,9 @@ function blacklistedDomainCheck () {
|
||||
]
|
||||
const currentUrl = window.location.href
|
||||
let currentRegex
|
||||
for (let i = 0; i < blacklistedDomains.length; i++) {
|
||||
const blacklistedDomain = blacklistedDomains[i].replace('.', '\\.')
|
||||
currentRegex = new RegExp(`(?:https?:\\/\\/)(?:(?!${blacklistedDomain}).)*$`)
|
||||
for (let i = 0; i < blockedDomains.length; i++) {
|
||||
const blockedDomain = blockedDomains[i].replace('.', '\\.')
|
||||
currentRegex = new RegExp(`(?:https?:\\/\\/)(?:(?!${blockedDomain}).)*$`)
|
||||
if (!currentRegex.test(currentUrl)) {
|
||||
return true
|
||||
}
|
||||
|
@ -1,39 +0,0 @@
|
||||
import ObservableStore from 'obs-store'
|
||||
import log from 'loglevel'
|
||||
|
||||
// every ten minutes
|
||||
const POLLING_INTERVAL = 10 * 60 * 1000
|
||||
|
||||
export default class InfuraController {
|
||||
|
||||
constructor (opts = {}) {
|
||||
const initState = Object.assign({
|
||||
infuraNetworkStatus: {},
|
||||
}, opts.initState)
|
||||
this.store = new ObservableStore(initState)
|
||||
}
|
||||
|
||||
//
|
||||
// PUBLIC METHODS
|
||||
//
|
||||
|
||||
// Responsible for retrieving the status of Infura's nodes. Can return either
|
||||
// ok, degraded, or down.
|
||||
async checkInfuraNetworkStatus () {
|
||||
const response = await window.fetch('https://api.infura.io/v1/status/metamask')
|
||||
const parsedResponse = await response.json()
|
||||
this.store.updateState({
|
||||
infuraNetworkStatus: parsedResponse,
|
||||
})
|
||||
return parsedResponse
|
||||
}
|
||||
|
||||
scheduleInfuraNetworkCheck () {
|
||||
if (this.conversionInterval) {
|
||||
clearInterval(this.conversionInterval)
|
||||
}
|
||||
this.conversionInterval = setInterval(() => {
|
||||
this.checkInfuraNetworkStatus().catch(log.warn)
|
||||
}, POLLING_INTERVAL)
|
||||
}
|
||||
}
|
@ -47,7 +47,7 @@ export class PermissionsController {
|
||||
this.getKeyringAccounts = getKeyringAccounts
|
||||
this._getUnlockPromise = getUnlockPromise
|
||||
this._notifyDomain = notifyDomain
|
||||
this.notifyAllDomains = notifyAllDomains
|
||||
this._notifyAllDomains = notifyAllDomains
|
||||
this._showPermissionRequest = showPermissionRequest
|
||||
|
||||
this._restrictedMethods = getRestrictedMethods({
|
||||
@ -95,6 +95,7 @@ export class PermissionsController {
|
||||
getAccounts: this.getAccounts.bind(this, origin),
|
||||
getUnlockPromise: () => this._getUnlockPromise(true),
|
||||
hasPermission: this.hasPermission.bind(this, origin),
|
||||
notifyAccountsChanged: this.notifyAccountsChanged.bind(this, origin),
|
||||
requestAccountsPermission: this._requestPermissions.bind(
|
||||
this, { origin }, { eth_accounts: {} },
|
||||
),
|
||||
@ -196,6 +197,7 @@ export class PermissionsController {
|
||||
* 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.
|
||||
* Idempotent for a given request id.
|
||||
*
|
||||
* @param {Object} approved - The request object approved by the user
|
||||
* @param {Array} accounts - The accounts to expose, if any
|
||||
@ -206,7 +208,7 @@ export class PermissionsController {
|
||||
const approval = this.pendingApprovals.get(id)
|
||||
|
||||
if (!approval) {
|
||||
log.error(`Permissions request with id '${id}' not found`)
|
||||
log.debug(`Permissions request with id '${id}' not found`)
|
||||
return
|
||||
}
|
||||
|
||||
@ -241,6 +243,7 @@ export class PermissionsController {
|
||||
/**
|
||||
* User rejection callback. Rejects the Promise for the permissions request
|
||||
* waited upon by rpc-cap, see requestUserApproval in _initializePermissions.
|
||||
* Idempotent for a given id.
|
||||
*
|
||||
* @param {string} id - The id of the request rejected by the user
|
||||
*/
|
||||
@ -248,7 +251,7 @@ export class PermissionsController {
|
||||
const approval = this.pendingApprovals.get(id)
|
||||
|
||||
if (!approval) {
|
||||
log.error(`Permissions request with id '${id}' not found`)
|
||||
log.debug(`Permissions request with id '${id}' not found`)
|
||||
return
|
||||
}
|
||||
|
||||
@ -289,10 +292,7 @@ export class PermissionsController {
|
||||
|
||||
const permittedAccounts = await this.getAccounts(origin)
|
||||
|
||||
this.notifyDomain(origin, {
|
||||
method: NOTIFICATION_NAMES.accountsChanged,
|
||||
result: permittedAccounts,
|
||||
})
|
||||
this.notifyAccountsChanged(origin, permittedAccounts)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -338,10 +338,7 @@ export class PermissionsController {
|
||||
newPermittedAccounts = await this.getAccounts(origin)
|
||||
}
|
||||
|
||||
this.notifyDomain(origin, {
|
||||
method: NOTIFICATION_NAMES.accountsChanged,
|
||||
result: newPermittedAccounts,
|
||||
})
|
||||
this.notifyAccountsChanged(origin, newPermittedAccounts)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -410,21 +407,34 @@ export class PermissionsController {
|
||||
})
|
||||
}
|
||||
|
||||
notifyDomain (origin, payload) {
|
||||
/**
|
||||
* Notify a domain that its permitted accounts have changed.
|
||||
* Also updates the accounts history log.
|
||||
*
|
||||
* @param {string} origin - The origin of the domain to notify.
|
||||
* @param {Array<string>} newAccounts - The currently permitted accounts.
|
||||
*/
|
||||
notifyAccountsChanged (origin, newAccounts) {
|
||||
|
||||
if (typeof origin !== 'string' || !origin) {
|
||||
throw new Error(`Invalid origin: '${origin}'`)
|
||||
}
|
||||
|
||||
if (!Array.isArray(newAccounts)) {
|
||||
throw new Error('Invalid accounts', newAccounts)
|
||||
}
|
||||
|
||||
this._notifyDomain(origin, {
|
||||
method: NOTIFICATION_NAMES.accountsChanged,
|
||||
result: newAccounts,
|
||||
})
|
||||
|
||||
// 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)
|
||||
this.permissionsLog.updateAccountsHistory(
|
||||
origin, newAccounts
|
||||
)
|
||||
|
||||
// NOTE:
|
||||
// we don't check for accounts changing in the notifyAllDomains case,
|
||||
@ -438,7 +448,8 @@ export class PermissionsController {
|
||||
* Should only be called after confirming that the permissions exist, to
|
||||
* avoid sending unnecessary notifications.
|
||||
*
|
||||
* @param {Object} domains { origin: [permissions] }
|
||||
* @param {Object} domains { origin: [permissions] } - The map of domain
|
||||
* origins to permissions to remove.
|
||||
*/
|
||||
removePermissionsFor (domains) {
|
||||
|
||||
@ -449,10 +460,7 @@ export class PermissionsController {
|
||||
perms.map((methodName) => {
|
||||
|
||||
if (methodName === 'eth_accounts') {
|
||||
this.notifyDomain(
|
||||
origin,
|
||||
{ method: NOTIFICATION_NAMES.accountsChanged, result: [] }
|
||||
)
|
||||
this.notifyAccountsChanged(origin, [])
|
||||
}
|
||||
|
||||
return { parentCapability: methodName }
|
||||
@ -466,7 +474,7 @@ export class PermissionsController {
|
||||
*/
|
||||
clearPermissions () {
|
||||
this.permissions.clearDomains()
|
||||
this.notifyAllDomains({
|
||||
this._notifyAllDomains({
|
||||
method: NOTIFICATION_NAMES.accountsChanged,
|
||||
result: [],
|
||||
})
|
||||
@ -505,6 +513,11 @@ export class PermissionsController {
|
||||
...metadata,
|
||||
lastUpdated: Date.now(),
|
||||
}
|
||||
|
||||
if (!newMetadataState[origin].extensionId && !newMetadataState[origin].host) {
|
||||
newMetadataState[origin].host = new URL(origin).host
|
||||
}
|
||||
|
||||
this._pendingSiteMetadata.add(origin)
|
||||
this._setDomainMetadata(newMetadataState)
|
||||
}
|
||||
@ -583,6 +596,7 @@ export class PermissionsController {
|
||||
* @param {string} account - The newly selected account's address.
|
||||
*/
|
||||
async _handleAccountSelected (account) {
|
||||
|
||||
if (typeof account !== 'string') {
|
||||
throw new Error('Selected account should be a non-empty string.')
|
||||
}
|
||||
@ -618,10 +632,7 @@ export class PermissionsController {
|
||||
async _handleConnectedAccountSelected (origin) {
|
||||
const permittedAccounts = await this.getAccounts(origin)
|
||||
|
||||
this.notifyDomain(origin, {
|
||||
method: NOTIFICATION_NAMES.accountsChanged,
|
||||
result: permittedAccounts,
|
||||
})
|
||||
this.notifyAccountsChanged(origin, permittedAccounts)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -9,6 +9,7 @@ export default function createMethodMiddleware ({
|
||||
getAccounts,
|
||||
getUnlockPromise,
|
||||
hasPermission,
|
||||
notifyAccountsChanged,
|
||||
requestAccountsPermission,
|
||||
}) {
|
||||
|
||||
@ -16,6 +17,8 @@ export default function createMethodMiddleware ({
|
||||
|
||||
return createAsyncMiddleware(async (req, res, next) => {
|
||||
|
||||
let responseHandler
|
||||
|
||||
switch (req.method) {
|
||||
|
||||
// Intercepting eth_accounts requests for backwards compatibility:
|
||||
@ -81,10 +84,33 @@ export default function createMethodMiddleware ({
|
||||
res.result = true
|
||||
return
|
||||
|
||||
// register return handler to send accountsChanged notification
|
||||
case 'wallet_requestPermissions':
|
||||
|
||||
if ('eth_accounts' in req.params?.[0]) {
|
||||
|
||||
responseHandler = async () => {
|
||||
|
||||
if (Array.isArray(res.result)) {
|
||||
for (const permission of res.result) {
|
||||
if (permission.parentCapability === 'eth_accounts') {
|
||||
notifyAccountsChanged(await getAccounts())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
next()
|
||||
// when this promise resolves, the response is on its way back
|
||||
await next()
|
||||
|
||||
if (responseHandler) {
|
||||
responseHandler()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -17,11 +17,10 @@ export default class TokenRatesController {
|
||||
*
|
||||
* @param {Object} [config] - Options to configure controller
|
||||
*/
|
||||
constructor ({ interval = DEFAULT_INTERVAL, currency, preferences } = {}) {
|
||||
constructor ({ currency, preferences } = {}) {
|
||||
this.store = new ObservableStore()
|
||||
this.currency = currency
|
||||
this.preferences = preferences
|
||||
this.interval = interval
|
||||
}
|
||||
|
||||
/**
|
||||
@ -50,19 +49,6 @@ export default class TokenRatesController {
|
||||
this.store.putState({ contractExchangeRates })
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Number}
|
||||
*/
|
||||
set interval (interval) {
|
||||
this._handle && clearInterval(this._handle)
|
||||
if (!interval) {
|
||||
return
|
||||
}
|
||||
this._handle = setInterval(() => {
|
||||
this.updateExchangeRates()
|
||||
}, interval)
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Object}
|
||||
*/
|
||||
@ -85,4 +71,19 @@ export default class TokenRatesController {
|
||||
this._tokens = tokens
|
||||
this.updateExchangeRates()
|
||||
}
|
||||
|
||||
start (interval = DEFAULT_INTERVAL) {
|
||||
this._handle && clearInterval(this._handle)
|
||||
if (!interval) {
|
||||
return
|
||||
}
|
||||
this._handle = setInterval(() => {
|
||||
this.updateExchangeRates()
|
||||
}, interval)
|
||||
this.updateExchangeRates()
|
||||
}
|
||||
|
||||
stop () {
|
||||
this._handle && clearInterval(this._handle)
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ import NonceTracker from 'nonce-tracker'
|
||||
import * as txUtils from './lib/util'
|
||||
import cleanErrorStack from '../../lib/cleanErrorStack'
|
||||
import log from 'loglevel'
|
||||
import { throwIfAccountIsBlacklisted } from './lib/recipient-blacklist-checker'
|
||||
import { throwIfAccountIsBlocked } from './lib/recipient-blocklist-checker'
|
||||
|
||||
import {
|
||||
TRANSACTION_TYPE_CANCEL,
|
||||
@ -241,7 +241,7 @@ export default class TransactionController extends EventEmitter {
|
||||
this.emit('newUnapprovedTx', txMeta)
|
||||
|
||||
try {
|
||||
throwIfAccountIsBlacklisted(txMeta.metamaskNetworkId, normalizedTxParams.to)
|
||||
throwIfAccountIsBlocked(txMeta.metamaskNetworkId, normalizedTxParams.to)
|
||||
txMeta = await this.addTxGasDefaults(txMeta, getCodeResponse)
|
||||
} catch (error) {
|
||||
log.warn(error)
|
||||
|
@ -1,19 +0,0 @@
|
||||
import blacklist from './recipient-blacklist'
|
||||
|
||||
/**
|
||||
* Checks if a specified account on a specified network is blacklisted
|
||||
* @param {number} networkId
|
||||
* @param {string} account
|
||||
* @throws {Error} if the account is blacklisted on mainnet
|
||||
*/
|
||||
export function throwIfAccountIsBlacklisted (networkId, account) {
|
||||
const mainnetId = 1
|
||||
if (networkId !== mainnetId) {
|
||||
return
|
||||
}
|
||||
|
||||
const accountToCheck = account.toLowerCase()
|
||||
if (blacklist.includes(accountToCheck)) {
|
||||
throw new Error('Recipient is a public account')
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
import blocklist from './recipient-blocklist'
|
||||
|
||||
/**
|
||||
* Checks if a specified account on a specified network is blocked
|
||||
* @param {number} networkId
|
||||
* @param {string} account
|
||||
* @throws {Error} if the account is blocked on mainnet
|
||||
*/
|
||||
export function throwIfAccountIsBlocked (networkId, account) {
|
||||
const mainnetId = 1
|
||||
if (networkId !== mainnetId) {
|
||||
return
|
||||
}
|
||||
|
||||
const accountToCheck = account.toLowerCase()
|
||||
if (blocklist.includes(accountToCheck)) {
|
||||
throw new Error('Recipient is a public account')
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
const blacklist = [
|
||||
const blocklist = [
|
||||
// IDEX phisher
|
||||
'0x9bcb0A9d99d815Bb87ee3191b1399b1Bcc46dc77',
|
||||
// Ganache default seed phrases
|
||||
@ -14,4 +14,4 @@ const blacklist = [
|
||||
'0x5aeda56215b167893e80b4fe645ba6d5bab767de',
|
||||
]
|
||||
|
||||
export default blacklist
|
||||
export default blocklist
|
@ -3,6 +3,7 @@ import ObservableStore from 'obs-store'
|
||||
import ethUtil from 'ethereumjs-util'
|
||||
import { ethErrors } from 'eth-json-rpc-errors'
|
||||
import createId from './random-id'
|
||||
import { MESSAGE_TYPE } from './enums'
|
||||
|
||||
const hexRe = /^[0-9A-Fa-f]+$/g
|
||||
import log from 'loglevel'
|
||||
@ -124,7 +125,7 @@ export default class DecryptMessageManager extends EventEmitter {
|
||||
msgParams: msgParams,
|
||||
time: time,
|
||||
status: 'unapproved',
|
||||
type: 'eth_decrypt',
|
||||
type: MESSAGE_TYPE.ETH_DECRYPT,
|
||||
}
|
||||
this.addMsg(msgData)
|
||||
|
||||
|
@ -3,6 +3,7 @@ import ObservableStore from 'obs-store'
|
||||
import { ethErrors } from 'eth-json-rpc-errors'
|
||||
import createId from './random-id'
|
||||
import log from 'loglevel'
|
||||
import { MESSAGE_TYPE } from './enums'
|
||||
|
||||
/**
|
||||
* Represents, and contains data about, an 'eth_getEncryptionPublicKey' type request. These are created when
|
||||
@ -114,7 +115,7 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
|
||||
msgParams: address,
|
||||
time: time,
|
||||
status: 'unapproved',
|
||||
type: 'eth_getEncryptionPublicKey',
|
||||
type: MESSAGE_TYPE.ETH_GET_ENCRYPTION_PUBLIC_KEY,
|
||||
}
|
||||
|
||||
if (req) {
|
||||
|
@ -9,11 +9,20 @@ const PLATFORM_EDGE = 'Edge'
|
||||
const PLATFORM_FIREFOX = 'Firefox'
|
||||
const PLATFORM_OPERA = 'Opera'
|
||||
|
||||
const MESSAGE_TYPE = {
|
||||
ETH_DECRYPT: 'eth_decrypt',
|
||||
ETH_GET_ENCRYPTION_PUBLIC_KEY: 'eth_getEncryptionPublicKey',
|
||||
ETH_SIGN: 'eth_sign',
|
||||
ETH_SIGN_TYPED_DATA: 'eth_signTypedData',
|
||||
PERSONAL_SIGN: 'personal_sign',
|
||||
}
|
||||
|
||||
export {
|
||||
ENVIRONMENT_TYPE_POPUP,
|
||||
ENVIRONMENT_TYPE_NOTIFICATION,
|
||||
ENVIRONMENT_TYPE_FULLSCREEN,
|
||||
ENVIRONMENT_TYPE_BACKGROUND,
|
||||
MESSAGE_TYPE,
|
||||
PLATFORM_BRAVE,
|
||||
PLATFORM_CHROME,
|
||||
PLATFORM_EDGE,
|
||||
|
@ -3,6 +3,7 @@ import ObservableStore from 'obs-store'
|
||||
import ethUtil from 'ethereumjs-util'
|
||||
import { ethErrors } from 'eth-json-rpc-errors'
|
||||
import createId from './random-id'
|
||||
import { MESSAGE_TYPE } from './enums'
|
||||
|
||||
/**
|
||||
* Represents, and contains data about, an 'eth_sign' type signature request. These are created when a signature for
|
||||
@ -116,7 +117,7 @@ export default class MessageManager extends EventEmitter {
|
||||
msgParams: msgParams,
|
||||
time: time,
|
||||
status: 'unapproved',
|
||||
type: 'eth_sign',
|
||||
type: MESSAGE_TYPE.ETH_SIGN,
|
||||
}
|
||||
this.addMsg(msgData)
|
||||
|
||||
|
@ -3,6 +3,7 @@ import ObservableStore from 'obs-store'
|
||||
import ethUtil from 'ethereumjs-util'
|
||||
import { ethErrors } from 'eth-json-rpc-errors'
|
||||
import createId from './random-id'
|
||||
import { MESSAGE_TYPE } from './enums'
|
||||
|
||||
const hexRe = /^[0-9A-Fa-f]+$/g
|
||||
import log from 'loglevel'
|
||||
@ -125,7 +126,7 @@ export default class PersonalMessageManager extends EventEmitter {
|
||||
msgParams: msgParams,
|
||||
time: time,
|
||||
status: 'unapproved',
|
||||
type: 'personal_sign',
|
||||
type: MESSAGE_TYPE.PERSONAL_SIGN,
|
||||
}
|
||||
this.addMsg(msgData)
|
||||
|
||||
|
@ -6,7 +6,7 @@ import { ethErrors } from 'eth-json-rpc-errors'
|
||||
import sigUtil from 'eth-sig-util'
|
||||
import log from 'loglevel'
|
||||
import jsonschema from 'jsonschema'
|
||||
|
||||
import { MESSAGE_TYPE } from './enums'
|
||||
/**
|
||||
* Represents, and contains data about, an 'eth_signTypedData' type signature request. These are created when a
|
||||
* signature for an eth_signTypedData call is requested.
|
||||
@ -118,7 +118,7 @@ export default class TypedMessageManager extends EventEmitter {
|
||||
msgParams: msgParams,
|
||||
time: time,
|
||||
status: 'unapproved',
|
||||
type: 'eth_signTypedData',
|
||||
type: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA,
|
||||
}
|
||||
this.addMsg(msgData)
|
||||
|
||||
|
@ -29,7 +29,6 @@ import EnsController from './controllers/ens'
|
||||
import NetworkController from './controllers/network'
|
||||
import PreferencesController from './controllers/preferences'
|
||||
import AppStateController from './controllers/app-state'
|
||||
import InfuraController from './controllers/infura'
|
||||
import CachedBalancesController from './controllers/cached-balances'
|
||||
import AlertController from './controllers/alert'
|
||||
import OnboardingController from './controllers/onboarding'
|
||||
@ -128,11 +127,6 @@ export default class MetamaskController extends EventEmitter {
|
||||
|
||||
this.currencyRateController = new CurrencyRateController(undefined, initState.CurrencyController)
|
||||
|
||||
this.infuraController = new InfuraController({
|
||||
initState: initState.InfuraController,
|
||||
})
|
||||
this.infuraController.scheduleInfuraNetworkCheck()
|
||||
|
||||
this.phishingController = new PhishingController()
|
||||
|
||||
// now we can initialize the RPC provider, which other controllers require
|
||||
@ -170,9 +164,11 @@ export default class MetamaskController extends EventEmitter {
|
||||
if (activeControllerConnections > 0) {
|
||||
this.accountTracker.start()
|
||||
this.incomingTransactionsController.start()
|
||||
this.tokenRatesController.start()
|
||||
} else {
|
||||
this.accountTracker.stop()
|
||||
this.incomingTransactionsController.stop()
|
||||
this.tokenRatesController.stop()
|
||||
}
|
||||
})
|
||||
|
||||
@ -281,10 +277,6 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.encryptionPublicKeyManager = new EncryptionPublicKeyManager()
|
||||
this.typedMessageManager = new TypedMessageManager({ networkController: this.networkController })
|
||||
|
||||
// ensure isClientOpenAndUnlocked is updated when memState updates
|
||||
this.on('update', (memState) => {
|
||||
this.isClientOpenAndUnlocked = memState.isUnlocked && this._isClientOpen
|
||||
})
|
||||
|
||||
this.store.updateStructure({
|
||||
AppStateController: this.appStateController.store,
|
||||
@ -294,7 +286,6 @@ export default class MetamaskController extends EventEmitter {
|
||||
AddressBookController: this.addressBookController,
|
||||
CurrencyController: this.currencyRateController,
|
||||
NetworkController: this.networkController.store,
|
||||
InfuraController: this.infuraController.store,
|
||||
CachedBalancesController: this.cachedBalancesController.store,
|
||||
AlertController: this.alertController.store,
|
||||
OnboardingController: this.onboardingController.store,
|
||||
@ -320,7 +311,6 @@ export default class MetamaskController extends EventEmitter {
|
||||
PreferencesController: this.preferencesController.store,
|
||||
AddressBookController: this.addressBookController,
|
||||
CurrencyController: this.currencyRateController,
|
||||
InfuraController: this.infuraController.store,
|
||||
AlertController: this.alertController.store,
|
||||
OnboardingController: this.onboardingController.store,
|
||||
IncomingTransactionsController: this.incomingTransactionsController.store,
|
||||
@ -459,6 +449,9 @@ export default class MetamaskController extends EventEmitter {
|
||||
markPasswordForgotten: this.markPasswordForgotten.bind(this),
|
||||
unMarkPasswordForgotten: this.unMarkPasswordForgotten.bind(this),
|
||||
buyEth: this.buyEth.bind(this),
|
||||
safelistPhishingDomain: this.safelistPhishingDomain.bind(this),
|
||||
getRequestAccountTabIds: (cb) => cb(null, this.getRequestAccountTabIds()),
|
||||
getOpenMetamaskTabsIds: (cb) => cb(null, this.getOpenMetamaskTabsIds()),
|
||||
|
||||
// primary HD keyring management
|
||||
addNewAccount: nodeify(this.addNewAccount, this),
|
||||
@ -496,9 +489,6 @@ export default class MetamaskController extends EventEmitter {
|
||||
completeOnboarding: nodeify(preferencesController.completeOnboarding, preferencesController),
|
||||
addKnownMethodData: nodeify(preferencesController.addKnownMethodData, preferencesController),
|
||||
|
||||
// BlacklistController
|
||||
whitelistPhishingDomain: this.whitelistPhishingDomain.bind(this),
|
||||
|
||||
// AddressController
|
||||
setAddressBook: nodeify(this.addressBookController.set, this.addressBookController),
|
||||
removeFromAddressBook: this.addressBookController.delete.bind(this.addressBookController),
|
||||
@ -574,9 +564,6 @@ export default class MetamaskController extends EventEmitter {
|
||||
addPermittedAccount: nodeify(permissionsController.addPermittedAccount, permissionsController),
|
||||
removePermittedAccount: nodeify(permissionsController.removePermittedAccount, permissionsController),
|
||||
requestAccountsPermission: nodeify(permissionsController.requestAccountsPermission, permissionsController),
|
||||
|
||||
getRequestAccountTabIds: (cb) => cb(null, this.getRequestAccountTabIds()),
|
||||
getOpenMetamaskTabsIds: (cb) => cb(null, this.getOpenMetamaskTabsIds()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1448,7 +1435,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
setupUntrustedCommunication (connectionStream, sender) {
|
||||
const { usePhishDetect } = this.preferencesController.store.getState()
|
||||
const hostname = (new URL(sender.url)).hostname
|
||||
// Check if new connection is blacklisted if phishing detection is on
|
||||
// Check if new connection is blocked if phishing detection is on
|
||||
if (usePhishDetect && this.phishingController.test(hostname)) {
|
||||
log.debug('MetaMask - sending phishing warning for', hostname)
|
||||
this.sendPhishingWarning(connectionStream, hostname)
|
||||
@ -1487,7 +1474,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
* @private
|
||||
* @param {*} connectionStream - The duplex stream to the per-page script,
|
||||
* for sending the reload attempt to.
|
||||
* @param {string} hostname - The URL that triggered the suspicion.
|
||||
* @param {string} hostname - The hostname that triggered the suspicion.
|
||||
*/
|
||||
sendPhishingWarning (connectionStream, hostname) {
|
||||
const mux = setupMultiplex(connectionStream)
|
||||
@ -1538,7 +1525,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
setupProviderConnection (outStream, sender, isInternal) {
|
||||
const origin = isInternal
|
||||
? 'metamask'
|
||||
: (new URL(sender.url)).hostname
|
||||
: (new URL(sender.url)).origin
|
||||
let extensionId
|
||||
if (sender.id !== extension.runtime.id) {
|
||||
extensionId = sender.id
|
||||
@ -1577,7 +1564,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
/**
|
||||
* A method for creating a provider that is safely restricted for the requesting domain.
|
||||
* @param {Object} options - Provider engine options
|
||||
* @param {string} options.origin - The hostname of the sender
|
||||
* @param {string} options.origin - The origin of the sender
|
||||
* @param {string} options.location - The full URL of the sender
|
||||
* @param {extensionId} [options.extensionId] - The extension ID of the sender, if the sender is an external extension
|
||||
* @param {tabId} [options.tabId] - The tab ID of the sender - if the sender is within a tab
|
||||
@ -2032,20 +2019,9 @@ export default class MetamaskController extends EventEmitter {
|
||||
*/
|
||||
set isClientOpen (open) {
|
||||
this._isClientOpen = open
|
||||
this.isClientOpenAndUnlocked = this.isUnlocked() && open
|
||||
this.detectTokensController.isOpen = open
|
||||
}
|
||||
|
||||
/**
|
||||
* A method for activating the retrieval of price data,
|
||||
* which should only be fetched when the UI is visible.
|
||||
* @private
|
||||
* @param {boolean} active - True if price data should be getting fetched.
|
||||
*/
|
||||
set isClientOpenAndUnlocked (active) {
|
||||
this.tokenRatesController.isActive = active
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates RPC engine middleware for processing eth_signTypedData requests
|
||||
*
|
||||
@ -2056,10 +2032,10 @@ export default class MetamaskController extends EventEmitter {
|
||||
*/
|
||||
|
||||
/**
|
||||
* Adds a domain to the PhishingController whitelist
|
||||
* @param {string} hostname - the domain to whitelist
|
||||
* Adds a domain to the PhishingController safelist
|
||||
* @param {string} hostname - the domain to safelist
|
||||
*/
|
||||
whitelistPhishingDomain (hostname) {
|
||||
safelistPhishingDomain (hostname) {
|
||||
return this.phishingController.bypass(hostname)
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ function start () {
|
||||
|
||||
const continueLink = document.getElementById('unsafe-continue')
|
||||
continueLink.addEventListener('click', () => {
|
||||
metaMaskController.whitelistPhishingDomain(suspect.hostname)
|
||||
metaMaskController.safelistPhishingDomain(suspect.hostname)
|
||||
window.location.href = suspect.href
|
||||
})
|
||||
})
|
||||
|
@ -21,7 +21,6 @@ import { EventEmitter } from 'events'
|
||||
import Dnode from 'dnode'
|
||||
import Eth from 'ethjs'
|
||||
import EthQuery from 'eth-query'
|
||||
import urlUtil from 'url'
|
||||
import launchMetaMaskUi from '../../ui'
|
||||
import StreamProvider from 'web3-stream-provider'
|
||||
import { setupMultiplex } from './lib/stream-utils.js'
|
||||
@ -95,10 +94,9 @@ async function queryCurrentActiveTab (windowType) {
|
||||
extension.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
||||
const [activeTab] = tabs
|
||||
const { title, url } = activeTab
|
||||
const { hostname: origin, protocol } = url ? urlUtil.parse(url) : {}
|
||||
resolve({
|
||||
title, origin, protocol, url,
|
||||
})
|
||||
const { origin, protocol } = url ? new URL(url) : {}
|
||||
|
||||
resolve({ title, origin, protocol, url })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
## Add Custom Build to Chrome
|
||||
|
||||
![Load dev build](./load-dev-build-chrome.gif)
|
||||
|
||||
* Open `Settings` > `Extensions`.
|
||||
* Check "Developer mode".
|
||||
* Alternatively, use the URL `chrome://extensions/` in your address bar
|
||||
|
BIN
docs/load-dev-build-chrome.gif
Normal file
BIN
docs/load-dev-build-chrome.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 678 KiB |
2100
test/data/mock-tx-history.json
Normal file
2100
test/data/mock-tx-history.json
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -151,7 +151,7 @@ describe('MetaMask', function () {
|
||||
})
|
||||
|
||||
it('balance renders', async function () {
|
||||
const balance = await driver.findElement(By.css('[data-testid="wallet-balance"] .asset-list__primary-amount'))
|
||||
const balance = await driver.findElement(By.css('[data-testid="wallet-balance"] .list-item__heading'))
|
||||
await driver.wait(until.elementTextMatches(balance, /25\s*ETH/))
|
||||
await driver.delay(regularDelayMs)
|
||||
})
|
||||
|
@ -3,7 +3,6 @@ const webdriver = require('selenium-webdriver')
|
||||
|
||||
const { By, Key, until } = webdriver
|
||||
const {
|
||||
tinyDelayMs,
|
||||
regularDelayMs,
|
||||
largeDelayMs,
|
||||
} = require('./helpers')
|
||||
@ -278,7 +277,7 @@ describe('Using MetaMask with an existing account', function () {
|
||||
await driver.delay(regularDelayMs)
|
||||
})
|
||||
|
||||
it('should open the remove account modal', async function () {
|
||||
it('should see new account in account menu', async function () {
|
||||
const accountName = await driver.findElement(By.css('.selected-account__name'))
|
||||
assert.equal(await accountName.getText(), 'Account 5')
|
||||
await driver.delay(regularDelayMs)
|
||||
@ -289,8 +288,13 @@ describe('Using MetaMask with an existing account', function () {
|
||||
const accountListItems = await driver.findElements(By.css('.account-menu__account'))
|
||||
assert.equal(accountListItems.length, 5)
|
||||
|
||||
await driver.clickElement(By.css('.account-menu__account:last-of-type > .remove-account-icon'))
|
||||
await driver.delay(tinyDelayMs)
|
||||
await driver.clickPoint(By.css('.account-menu__icon'), 0, 0)
|
||||
})
|
||||
|
||||
it('should open the remove account modal', async function () {
|
||||
await driver.clickElement(By.css('[data-testid="account-options-menu-button"]'))
|
||||
|
||||
await driver.clickElement(By.css('[data-testid="account-options-menu__remove-account"]'))
|
||||
|
||||
await driver.findElement(By.css('.confirm-remove-account__account'))
|
||||
})
|
||||
@ -304,6 +308,8 @@ describe('Using MetaMask with an existing account', function () {
|
||||
assert.equal(await accountName.getText(), 'Account 1')
|
||||
await driver.delay(regularDelayMs)
|
||||
|
||||
await driver.clickElement(By.css('.account-menu__icon'))
|
||||
|
||||
const accountListItems = await driver.findElements(By.css('.account-menu__account'))
|
||||
assert.equal(accountListItems.length, 4)
|
||||
})
|
||||
|
@ -207,7 +207,7 @@ describe('MetaMask', function () {
|
||||
})
|
||||
|
||||
it('balance renders', async function () {
|
||||
const balance = await driver.findElement(By.css('[data-testid="wallet-balance"] .asset-list__primary-amount'))
|
||||
const balance = await driver.findElement(By.css('[data-testid="wallet-balance"] .list-item__heading'))
|
||||
await driver.wait(until.elementTextMatches(balance, /100\s*ETH/))
|
||||
await driver.delay(regularDelayMs)
|
||||
})
|
||||
@ -472,10 +472,8 @@ describe('MetaMask', function () {
|
||||
const txValue = await driver.findClickableElement(By.css('.transaction-list-item__primary-currency'))
|
||||
await txValue.click()
|
||||
const popoverCloseButton = await driver.findClickableElement(By.css('.popover-header__button'))
|
||||
const txGasPrices = await driver.findElements(By.css('.transaction-breakdown__value'))
|
||||
const txGasPriceLabels = await driver.findElements(By.css('.transaction-breakdown-row__title'))
|
||||
await driver.wait(until.elementTextMatches(txGasPrices[4], /^10$/), 10000)
|
||||
assert(txGasPriceLabels[2])
|
||||
const txGasPrice = await driver.findElement(By.css('[data-testid="transaction-breakdown__gas-price"]'))
|
||||
await driver.wait(until.elementTextMatches(txGasPrice, /^10$/), 10000)
|
||||
await popoverCloseButton.click()
|
||||
})
|
||||
})
|
||||
@ -1223,8 +1221,7 @@ describe('MetaMask', function () {
|
||||
|
||||
const confirmHideModal = await driver.findElement(By.css('span .modal'))
|
||||
|
||||
const byHideTokenConfirmationButton = By.css('.hide-token-confirmation__button')
|
||||
await driver.clickElement(byHideTokenConfirmationButton)
|
||||
await driver.clickElement(By.css('[data-testid="hide-token-confirmation__hide"]'))
|
||||
|
||||
await driver.wait(until.stalenessOf(confirmHideModal))
|
||||
})
|
||||
@ -1232,7 +1229,6 @@ describe('MetaMask', function () {
|
||||
|
||||
describe('Add existing token using search', function () {
|
||||
it('clicks on the Add Token button', async function () {
|
||||
await driver.clickElement(By.css('[data-testid="asset__back"]'))
|
||||
await driver.clickElement(By.xpath(`//button[contains(text(), 'Add Token')]`))
|
||||
await driver.delay(regularDelayMs)
|
||||
})
|
||||
|
@ -107,7 +107,7 @@ describe('MetaMask', function () {
|
||||
const address = content[1]
|
||||
assert.equal(await title.getText(), 'Signature Request')
|
||||
assert.equal(await name.getText(), 'Ether Mail')
|
||||
assert.equal(await origin.getText(), '127.0.0.1')
|
||||
assert.equal(await origin.getText(), 'http://127.0.0.1:8080')
|
||||
assert.equal(await address.getText(), publicAddress.slice(0, 8) + '...' + publicAddress.slice(publicAddress.length - 8))
|
||||
})
|
||||
|
||||
|
@ -96,7 +96,7 @@ describe('MetaMask', function () {
|
||||
})
|
||||
|
||||
it('balance renders', async function () {
|
||||
const balance = await driver.findElement(By.css('[data-testid="wallet-balance"] .asset-list__primary-amount'))
|
||||
const balance = await driver.findElement(By.css('[data-testid="wallet-balance"] .list-item__heading'))
|
||||
await driver.wait(until.elementTextMatches(balance, /25\s*ETH/))
|
||||
await driver.delay(regularDelayMs)
|
||||
})
|
||||
@ -202,7 +202,7 @@ describe('MetaMask', function () {
|
||||
})
|
||||
|
||||
it('balance renders', async function () {
|
||||
const balance = await driver2.findElement(By.css('[data-testid="wallet-balance"] .asset-list__primary-amount'))
|
||||
const balance = await driver2.findElement(By.css('[data-testid="wallet-balance"] .list-item__heading'))
|
||||
await driver2.wait(until.elementTextMatches(balance, /25\s*ETH/))
|
||||
await driver2.delay(regularDelayMs)
|
||||
})
|
||||
@ -223,7 +223,7 @@ describe('MetaMask', function () {
|
||||
|
||||
it('finds the blockies toggle turned on', async function () {
|
||||
await driver.delay(regularDelayMs)
|
||||
const toggleLabel = await driver.findElement(By.css('.toggle-button__status-label'))
|
||||
const toggleLabel = await driver.findElement(By.css('.toggle-button__status'))
|
||||
const toggleLabelText = await toggleLabel.getText()
|
||||
assert.equal(toggleLabelText, 'ON')
|
||||
})
|
||||
|
@ -1,66 +0,0 @@
|
||||
import assert from 'assert'
|
||||
import sinon from 'sinon'
|
||||
import InfuraController from '../../../../app/scripts/controllers/infura'
|
||||
|
||||
describe('infura-controller', function () {
|
||||
let infuraController, networkStatus
|
||||
const response = { 'mainnet': 'degraded', 'ropsten': 'ok', 'kovan': 'ok', 'rinkeby': 'down', 'goerli': 'ok' }
|
||||
|
||||
describe('Network status queries', function () {
|
||||
before(async function () {
|
||||
infuraController = new InfuraController()
|
||||
sinon.stub(infuraController, 'checkInfuraNetworkStatus').resolves(response)
|
||||
networkStatus = await infuraController.checkInfuraNetworkStatus()
|
||||
})
|
||||
|
||||
describe('Mainnet', function () {
|
||||
it('should have Mainnet', function () {
|
||||
assert.equal(Object.keys(networkStatus)[0], 'mainnet')
|
||||
})
|
||||
|
||||
it('should have a value for Mainnet status', function () {
|
||||
assert.equal(networkStatus.mainnet, 'degraded')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Ropsten', function () {
|
||||
it('should have Ropsten', function () {
|
||||
assert.equal(Object.keys(networkStatus)[1], 'ropsten')
|
||||
})
|
||||
|
||||
it('should have a value for Ropsten status', function () {
|
||||
assert.equal(networkStatus.ropsten, 'ok')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Kovan', function () {
|
||||
it('should have Kovan', function () {
|
||||
assert.equal(Object.keys(networkStatus)[2], 'kovan')
|
||||
})
|
||||
|
||||
it('should have a value for Kovan status', function () {
|
||||
assert.equal(networkStatus.kovan, 'ok')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Rinkeby', function () {
|
||||
it('should have Rinkeby', function () {
|
||||
assert.equal(Object.keys(networkStatus)[3], 'rinkeby')
|
||||
})
|
||||
|
||||
it('should have a value for Rinkeby status', function () {
|
||||
assert.equal(networkStatus.rinkeby, 'down')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Goerli', function () {
|
||||
it('should have Goerli', function () {
|
||||
assert.equal(Object.keys(networkStatus)[4], 'goerli')
|
||||
})
|
||||
|
||||
it('should have a value for Goerli status', function () {
|
||||
assert.equal(networkStatus.goerli, 'ok')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
@ -824,7 +824,7 @@ describe('MetaMaskController', function () {
|
||||
'mock tx params',
|
||||
{
|
||||
...message,
|
||||
origin: 'mycrypto.com',
|
||||
origin: 'http://mycrypto.com',
|
||||
tabId: 456,
|
||||
},
|
||||
]
|
||||
@ -865,7 +865,7 @@ describe('MetaMaskController', function () {
|
||||
'mock tx params',
|
||||
{
|
||||
...message,
|
||||
origin: 'mycrypto.com',
|
||||
origin: 'http://mycrypto.com',
|
||||
},
|
||||
]
|
||||
)
|
||||
|
@ -151,10 +151,10 @@ export const getNotifyAllDomains = (notifications = {}) => (notification) => {
|
||||
* - e.g. permissions, caveats, and permission requests
|
||||
*/
|
||||
|
||||
const ORIGINS = {
|
||||
a: 'foo.xyz',
|
||||
b: 'bar.abc',
|
||||
c: 'baz.def',
|
||||
const DOMAINS = {
|
||||
a: { origin: 'https://foo.xyz', host: 'foo.xyz' },
|
||||
b: { origin: 'https://bar.abc', host: 'bar.abc' },
|
||||
c: { origin: 'https://baz.def', host: 'baz.def' },
|
||||
}
|
||||
|
||||
const PERM_NAMES = {
|
||||
@ -446,6 +446,19 @@ export const getters = deepFreeze({
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
notifyAccountsChanged: {
|
||||
invalidOrigin: (origin) => {
|
||||
return {
|
||||
message: `Invalid origin: '${origin}'`,
|
||||
}
|
||||
},
|
||||
invalidAccounts: () => {
|
||||
return {
|
||||
message: 'Invalid accounts',
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
@ -477,18 +490,6 @@ export const getters = deepFreeze({
|
||||
result: accounts,
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a test notification that doesn't occur in practice.
|
||||
*
|
||||
* @returns {Object} A notification with the 'test_notification' method name
|
||||
*/
|
||||
test: () => {
|
||||
return {
|
||||
method: 'test_notification',
|
||||
result: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
@ -629,7 +630,7 @@ export const constants = deepFreeze({
|
||||
c: '3',
|
||||
},
|
||||
|
||||
ORIGINS: { ...ORIGINS },
|
||||
DOMAINS: { ...DOMAINS },
|
||||
|
||||
ACCOUNTS: { ...ACCOUNTS },
|
||||
|
||||
@ -647,7 +648,7 @@ export const constants = deepFreeze({
|
||||
|
||||
case1: [
|
||||
{
|
||||
[ORIGINS.a]: {
|
||||
[DOMAINS.a.origin]: {
|
||||
[PERM_NAMES.eth_accounts]: {
|
||||
lastApproved: 1,
|
||||
accounts: {
|
||||
@ -659,7 +660,7 @@ export const constants = deepFreeze({
|
||||
},
|
||||
},
|
||||
{
|
||||
[ORIGINS.a]: {
|
||||
[DOMAINS.a.origin]: {
|
||||
[PERM_NAMES.eth_accounts]: {
|
||||
lastApproved: 2,
|
||||
accounts: {
|
||||
@ -674,7 +675,7 @@ export const constants = deepFreeze({
|
||||
|
||||
case2: [
|
||||
{
|
||||
[ORIGINS.a]: {
|
||||
[DOMAINS.a.origin]: {
|
||||
[PERM_NAMES.eth_accounts]: {
|
||||
lastApproved: 1,
|
||||
accounts: {},
|
||||
@ -685,10 +686,10 @@ export const constants = deepFreeze({
|
||||
|
||||
case3: [
|
||||
{
|
||||
[ORIGINS.a]: {
|
||||
[DOMAINS.a.origin]: {
|
||||
[PERM_NAMES.test_method]: { lastApproved: 1 },
|
||||
},
|
||||
[ORIGINS.b]: {
|
||||
[DOMAINS.b.origin]: {
|
||||
[PERM_NAMES.eth_accounts]: {
|
||||
lastApproved: 1,
|
||||
accounts: {
|
||||
@ -696,7 +697,7 @@ export const constants = deepFreeze({
|
||||
},
|
||||
},
|
||||
},
|
||||
[ORIGINS.c]: {
|
||||
[DOMAINS.c.origin]: {
|
||||
[PERM_NAMES.test_method]: { lastApproved: 1 },
|
||||
[PERM_NAMES.eth_accounts]: {
|
||||
lastApproved: 1,
|
||||
@ -707,10 +708,10 @@ export const constants = deepFreeze({
|
||||
},
|
||||
},
|
||||
{
|
||||
[ORIGINS.a]: {
|
||||
[DOMAINS.a.origin]: {
|
||||
[PERM_NAMES.test_method]: { lastApproved: 2 },
|
||||
},
|
||||
[ORIGINS.b]: {
|
||||
[DOMAINS.b.origin]: {
|
||||
[PERM_NAMES.eth_accounts]: {
|
||||
lastApproved: 1,
|
||||
accounts: {
|
||||
@ -718,7 +719,7 @@ export const constants = deepFreeze({
|
||||
},
|
||||
},
|
||||
},
|
||||
[ORIGINS.c]: {
|
||||
[DOMAINS.c.origin]: {
|
||||
[PERM_NAMES.test_method]: { lastApproved: 1 },
|
||||
[PERM_NAMES.eth_accounts]: {
|
||||
lastApproved: 2,
|
||||
@ -733,7 +734,7 @@ export const constants = deepFreeze({
|
||||
|
||||
case4: [
|
||||
{
|
||||
[ORIGINS.a]: {
|
||||
[DOMAINS.a.origin]: {
|
||||
[PERM_NAMES.test_method]: {
|
||||
lastApproved: 1,
|
||||
},
|
||||
|
@ -37,15 +37,15 @@ const {
|
||||
ALL_ACCOUNTS,
|
||||
ACCOUNTS,
|
||||
DUMMY_ACCOUNT,
|
||||
ORIGINS,
|
||||
DOMAINS,
|
||||
PERM_NAMES,
|
||||
REQUEST_IDS,
|
||||
EXTRA_ACCOUNT,
|
||||
} = constants
|
||||
|
||||
const initNotifications = () => {
|
||||
return Object.values(ORIGINS).reduce((acc, domain) => {
|
||||
acc[domain] = []
|
||||
return Object.values(DOMAINS).reduce((acc, domain) => {
|
||||
acc[domain.origin] = []
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
@ -73,19 +73,19 @@ describe('permissions controller', function () {
|
||||
beforeEach(function () {
|
||||
permController = initPermController()
|
||||
grantPermissions(
|
||||
permController, ORIGINS.a,
|
||||
permController, DOMAINS.a.origin,
|
||||
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted)
|
||||
)
|
||||
grantPermissions(
|
||||
permController, ORIGINS.b,
|
||||
permController, DOMAINS.b.origin,
|
||||
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted)
|
||||
)
|
||||
})
|
||||
|
||||
it('gets permitted accounts for permitted origins', async function () {
|
||||
|
||||
const aAccounts = await permController.getAccounts(ORIGINS.a)
|
||||
const bAccounts = await permController.getAccounts(ORIGINS.b)
|
||||
const aAccounts = await permController.getAccounts(DOMAINS.a.origin)
|
||||
const bAccounts = await permController.getAccounts(DOMAINS.b.origin)
|
||||
|
||||
assert.deepEqual(
|
||||
aAccounts, [ACCOUNTS.a.primary],
|
||||
@ -98,7 +98,7 @@ describe('permissions controller', function () {
|
||||
})
|
||||
|
||||
it('does not get accounts for unpermitted origins', async function () {
|
||||
const cAccounts = await permController.getAccounts(ORIGINS.c)
|
||||
const cAccounts = await permController.getAccounts(DOMAINS.c.origin)
|
||||
assert.deepEqual(cAccounts, [], 'origin should have no accounts')
|
||||
})
|
||||
|
||||
@ -114,29 +114,29 @@ describe('permissions controller', function () {
|
||||
|
||||
const permController = initPermController()
|
||||
grantPermissions(
|
||||
permController, ORIGINS.a,
|
||||
permController, DOMAINS.a.origin,
|
||||
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted)
|
||||
)
|
||||
grantPermissions(
|
||||
permController, ORIGINS.b,
|
||||
permController, DOMAINS.b.origin,
|
||||
PERMS.finalizedRequests.test_method()
|
||||
)
|
||||
|
||||
assert.ok(
|
||||
permController.hasPermission(ORIGINS.a, 'eth_accounts'),
|
||||
permController.hasPermission(DOMAINS.a.origin, 'eth_accounts'),
|
||||
'should return true for granted permission'
|
||||
)
|
||||
assert.ok(
|
||||
permController.hasPermission(ORIGINS.b, 'test_method'),
|
||||
permController.hasPermission(DOMAINS.b.origin, 'test_method'),
|
||||
'should return true for granted permission'
|
||||
)
|
||||
|
||||
assert.ok(
|
||||
!permController.hasPermission(ORIGINS.a, 'test_method'),
|
||||
!permController.hasPermission(DOMAINS.a.origin, 'test_method'),
|
||||
'should return false for non-granted permission'
|
||||
)
|
||||
assert.ok(
|
||||
!permController.hasPermission(ORIGINS.b, 'eth_accounts'),
|
||||
!permController.hasPermission(DOMAINS.b.origin, 'eth_accounts'),
|
||||
'should return true for non-granted permission'
|
||||
)
|
||||
|
||||
@ -145,7 +145,7 @@ describe('permissions controller', function () {
|
||||
'should return false for unknown origin'
|
||||
)
|
||||
assert.ok(
|
||||
!permController.hasPermission(ORIGINS.b, 'foo'),
|
||||
!permController.hasPermission(DOMAINS.b.origin, 'foo'),
|
||||
'should return false for unknown permission'
|
||||
)
|
||||
})
|
||||
@ -159,21 +159,21 @@ describe('permissions controller', function () {
|
||||
const permController = initPermController(notifications)
|
||||
|
||||
grantPermissions(
|
||||
permController, ORIGINS.a,
|
||||
permController, DOMAINS.a.origin,
|
||||
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted)
|
||||
)
|
||||
grantPermissions(
|
||||
permController, ORIGINS.b,
|
||||
permController, DOMAINS.b.origin,
|
||||
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted)
|
||||
)
|
||||
grantPermissions(
|
||||
permController, ORIGINS.c,
|
||||
permController, DOMAINS.c.origin,
|
||||
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.c.permitted)
|
||||
)
|
||||
|
||||
let aAccounts = await permController.getAccounts(ORIGINS.a)
|
||||
let bAccounts = await permController.getAccounts(ORIGINS.b)
|
||||
let cAccounts = await permController.getAccounts(ORIGINS.c)
|
||||
let aAccounts = await permController.getAccounts(DOMAINS.a.origin)
|
||||
let bAccounts = await permController.getAccounts(DOMAINS.b.origin)
|
||||
let cAccounts = await permController.getAccounts(DOMAINS.c.origin)
|
||||
|
||||
|
||||
assert.deepEqual(
|
||||
@ -199,9 +199,9 @@ describe('permissions controller', function () {
|
||||
)
|
||||
})
|
||||
|
||||
aAccounts = await permController.getAccounts(ORIGINS.a)
|
||||
bAccounts = await permController.getAccounts(ORIGINS.b)
|
||||
cAccounts = await permController.getAccounts(ORIGINS.c)
|
||||
aAccounts = await permController.getAccounts(DOMAINS.a.origin)
|
||||
bAccounts = await permController.getAccounts(DOMAINS.b.origin)
|
||||
cAccounts = await permController.getAccounts(DOMAINS.c.origin)
|
||||
|
||||
assert.deepEqual(aAccounts, [], 'first origin should have no accounts')
|
||||
assert.deepEqual(bAccounts, [], 'second origin should have no accounts')
|
||||
@ -230,19 +230,19 @@ describe('permissions controller', function () {
|
||||
notifications = initNotifications()
|
||||
permController = initPermController(notifications)
|
||||
grantPermissions(
|
||||
permController, ORIGINS.a,
|
||||
permController, DOMAINS.a.origin,
|
||||
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted)
|
||||
)
|
||||
grantPermissions(
|
||||
permController, ORIGINS.b,
|
||||
permController, DOMAINS.b.origin,
|
||||
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted)
|
||||
)
|
||||
})
|
||||
|
||||
it('removes permissions for multiple domains', async function () {
|
||||
|
||||
let aAccounts = await permController.getAccounts(ORIGINS.a)
|
||||
let bAccounts = await permController.getAccounts(ORIGINS.b)
|
||||
let aAccounts = await permController.getAccounts(DOMAINS.a.origin)
|
||||
let bAccounts = await permController.getAccounts(DOMAINS.b.origin)
|
||||
|
||||
assert.deepEqual(
|
||||
aAccounts, [ACCOUNTS.a.primary],
|
||||
@ -254,22 +254,22 @@ describe('permissions controller', function () {
|
||||
)
|
||||
|
||||
permController.removePermissionsFor({
|
||||
[ORIGINS.a]: [PERM_NAMES.eth_accounts],
|
||||
[ORIGINS.b]: [PERM_NAMES.eth_accounts],
|
||||
[DOMAINS.a.origin]: [PERM_NAMES.eth_accounts],
|
||||
[DOMAINS.b.origin]: [PERM_NAMES.eth_accounts],
|
||||
})
|
||||
|
||||
aAccounts = await permController.getAccounts(ORIGINS.a)
|
||||
bAccounts = await permController.getAccounts(ORIGINS.b)
|
||||
aAccounts = await permController.getAccounts(DOMAINS.a.origin)
|
||||
bAccounts = await permController.getAccounts(DOMAINS.b.origin)
|
||||
|
||||
assert.deepEqual(aAccounts, [], 'first origin should have no accounts')
|
||||
assert.deepEqual(bAccounts, [], 'second origin should have no accounts')
|
||||
|
||||
assert.deepEqual(
|
||||
notifications[ORIGINS.a], [NOTIFICATIONS.removedAccounts()],
|
||||
notifications[DOMAINS.a.origin], [NOTIFICATIONS.removedAccounts()],
|
||||
'first origin should have correct notification'
|
||||
)
|
||||
assert.deepEqual(
|
||||
notifications[ORIGINS.b], [NOTIFICATIONS.removedAccounts()],
|
||||
notifications[DOMAINS.b.origin], [NOTIFICATIONS.removedAccounts()],
|
||||
'second origin should have correct notification'
|
||||
)
|
||||
|
||||
@ -282,10 +282,10 @@ describe('permissions controller', function () {
|
||||
it('only removes targeted permissions from single domain', async function () {
|
||||
|
||||
grantPermissions(
|
||||
permController, ORIGINS.b, PERMS.finalizedRequests.test_method()
|
||||
permController, DOMAINS.b.origin, PERMS.finalizedRequests.test_method()
|
||||
)
|
||||
|
||||
let bPermissions = permController.permissions.getPermissionsForDomain(ORIGINS.b)
|
||||
let bPermissions = permController.permissions.getPermissionsForDomain(DOMAINS.b.origin)
|
||||
|
||||
assert.ok(
|
||||
(
|
||||
@ -297,10 +297,10 @@ describe('permissions controller', function () {
|
||||
)
|
||||
|
||||
permController.removePermissionsFor({
|
||||
[ORIGINS.b]: [PERM_NAMES.test_method],
|
||||
[DOMAINS.b.origin]: [PERM_NAMES.test_method],
|
||||
})
|
||||
|
||||
bPermissions = permController.permissions.getPermissionsForDomain(ORIGINS.b)
|
||||
bPermissions = permController.permissions.getPermissionsForDomain(DOMAINS.b.origin)
|
||||
|
||||
assert.ok(
|
||||
(
|
||||
@ -314,11 +314,11 @@ describe('permissions controller', function () {
|
||||
it('removes permissions for a single domain, without affecting another', async function () {
|
||||
|
||||
permController.removePermissionsFor({
|
||||
[ORIGINS.b]: [PERM_NAMES.eth_accounts],
|
||||
[DOMAINS.b.origin]: [PERM_NAMES.eth_accounts],
|
||||
})
|
||||
|
||||
const aAccounts = await permController.getAccounts(ORIGINS.a)
|
||||
const bAccounts = await permController.getAccounts(ORIGINS.b)
|
||||
const aAccounts = await permController.getAccounts(DOMAINS.a.origin)
|
||||
const bAccounts = await permController.getAccounts(DOMAINS.b.origin)
|
||||
|
||||
assert.deepEqual(
|
||||
aAccounts, [ACCOUNTS.a.primary],
|
||||
@ -327,16 +327,16 @@ describe('permissions controller', function () {
|
||||
assert.deepEqual(bAccounts, [], 'second origin should have no accounts')
|
||||
|
||||
assert.deepEqual(
|
||||
notifications[ORIGINS.a], [],
|
||||
notifications[DOMAINS.a.origin], [],
|
||||
'first origin should have no notifications'
|
||||
)
|
||||
assert.deepEqual(
|
||||
notifications[ORIGINS.b], [NOTIFICATIONS.removedAccounts()],
|
||||
notifications[DOMAINS.b.origin], [NOTIFICATIONS.removedAccounts()],
|
||||
'second origin should have correct notification'
|
||||
)
|
||||
|
||||
assert.deepEqual(
|
||||
Object.keys(permController.permissions.getDomains()), [ORIGINS.a],
|
||||
Object.keys(permController.permissions.getDomains()), [DOMAINS.a.origin],
|
||||
'only first origin should remain'
|
||||
)
|
||||
})
|
||||
@ -345,16 +345,16 @@ describe('permissions controller', function () {
|
||||
|
||||
// it knows nothing of this origin
|
||||
permController.removePermissionsFor({
|
||||
[ORIGINS.c]: [PERM_NAMES.eth_accounts],
|
||||
[DOMAINS.c.origin]: [PERM_NAMES.eth_accounts],
|
||||
})
|
||||
|
||||
assert.deepEqual(
|
||||
notifications[ORIGINS.c], [NOTIFICATIONS.removedAccounts()],
|
||||
notifications[DOMAINS.c.origin], [NOTIFICATIONS.removedAccounts()],
|
||||
'unknown origin should have notification'
|
||||
)
|
||||
|
||||
const aAccounts = await permController.getAccounts(ORIGINS.a)
|
||||
const bAccounts = await permController.getAccounts(ORIGINS.b)
|
||||
const aAccounts = await permController.getAccounts(DOMAINS.a.origin)
|
||||
const bAccounts = await permController.getAccounts(DOMAINS.b.origin)
|
||||
|
||||
assert.deepEqual(
|
||||
aAccounts, [ACCOUNTS.a.primary],
|
||||
@ -367,7 +367,7 @@ describe('permissions controller', function () {
|
||||
|
||||
assert.deepEqual(
|
||||
Object.keys(permController.permissions.getDomains()),
|
||||
[ORIGINS.a, ORIGINS.b],
|
||||
[DOMAINS.a.origin, DOMAINS.b.origin],
|
||||
'should have correct domains'
|
||||
)
|
||||
})
|
||||
@ -380,11 +380,11 @@ describe('permissions controller', function () {
|
||||
beforeEach(function () {
|
||||
permController = initPermController()
|
||||
grantPermissions(
|
||||
permController, ORIGINS.a,
|
||||
permController, DOMAINS.a.origin,
|
||||
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted)
|
||||
)
|
||||
grantPermissions(
|
||||
permController, ORIGINS.b,
|
||||
permController, DOMAINS.b.origin,
|
||||
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted)
|
||||
)
|
||||
})
|
||||
@ -470,18 +470,18 @@ describe('permissions controller', function () {
|
||||
notifications = initNotifications()
|
||||
permController = initPermController(notifications)
|
||||
grantPermissions(
|
||||
permController, ORIGINS.a,
|
||||
permController, DOMAINS.a.origin,
|
||||
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted)
|
||||
)
|
||||
grantPermissions(
|
||||
permController, ORIGINS.b,
|
||||
permController, DOMAINS.b.origin,
|
||||
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted)
|
||||
)
|
||||
})
|
||||
|
||||
it('should throw if account is not a string', async function () {
|
||||
await assert.rejects(
|
||||
() => permController.addPermittedAccount(ORIGINS.a, {}),
|
||||
() => permController.addPermittedAccount(DOMAINS.a.origin, {}),
|
||||
ERRORS.validatePermittedAccounts.nonKeyringAccount({}),
|
||||
'should throw on non-string account param'
|
||||
)
|
||||
@ -489,7 +489,7 @@ describe('permissions controller', function () {
|
||||
|
||||
it('should throw if given account is not in keyring', async function () {
|
||||
await assert.rejects(
|
||||
() => permController.addPermittedAccount(ORIGINS.a, DUMMY_ACCOUNT),
|
||||
() => permController.addPermittedAccount(DOMAINS.a.origin, DUMMY_ACCOUNT),
|
||||
ERRORS.validatePermittedAccounts.nonKeyringAccount(DUMMY_ACCOUNT),
|
||||
'should throw on non-keyring account'
|
||||
)
|
||||
@ -505,7 +505,7 @@ describe('permissions controller', function () {
|
||||
|
||||
it('should throw if origin lacks any permissions', async function () {
|
||||
await assert.rejects(
|
||||
() => permController.addPermittedAccount(ORIGINS.c, EXTRA_ACCOUNT),
|
||||
() => permController.addPermittedAccount(DOMAINS.c.origin, EXTRA_ACCOUNT),
|
||||
ERRORS.addPermittedAccount.invalidOrigin(),
|
||||
'should throw on origin without permissions'
|
||||
)
|
||||
@ -513,12 +513,12 @@ describe('permissions controller', function () {
|
||||
|
||||
it('should throw if origin lacks eth_accounts permission', async function () {
|
||||
grantPermissions(
|
||||
permController, ORIGINS.c,
|
||||
permController, DOMAINS.c.origin,
|
||||
PERMS.finalizedRequests.test_method()
|
||||
)
|
||||
|
||||
await assert.rejects(
|
||||
() => permController.addPermittedAccount(ORIGINS.c, EXTRA_ACCOUNT),
|
||||
() => permController.addPermittedAccount(DOMAINS.c.origin, EXTRA_ACCOUNT),
|
||||
ERRORS.addPermittedAccount.noEthAccountsPermission(),
|
||||
'should throw on origin without eth_accounts permission'
|
||||
)
|
||||
@ -526,16 +526,16 @@ describe('permissions controller', function () {
|
||||
|
||||
it('should throw if account is already permitted', async function () {
|
||||
await assert.rejects(
|
||||
() => permController.addPermittedAccount(ORIGINS.a, ACCOUNTS.a.permitted[0]),
|
||||
() => permController.addPermittedAccount(DOMAINS.a.origin, ACCOUNTS.a.permitted[0]),
|
||||
ERRORS.addPermittedAccount.alreadyPermitted(),
|
||||
'should throw if account is already permitted'
|
||||
)
|
||||
})
|
||||
|
||||
it('should successfully add permitted account', async function () {
|
||||
await permController.addPermittedAccount(ORIGINS.a, EXTRA_ACCOUNT)
|
||||
await permController.addPermittedAccount(DOMAINS.a.origin, EXTRA_ACCOUNT)
|
||||
|
||||
const accounts = await permController._getPermittedAccounts(ORIGINS.a)
|
||||
const accounts = await permController._getPermittedAccounts(DOMAINS.a.origin)
|
||||
|
||||
assert.deepEqual(
|
||||
accounts, [...ACCOUNTS.a.permitted, EXTRA_ACCOUNT],
|
||||
@ -543,7 +543,7 @@ describe('permissions controller', function () {
|
||||
)
|
||||
|
||||
assert.deepEqual(
|
||||
notifications[ORIGINS.a][0],
|
||||
notifications[DOMAINS.a.origin][0],
|
||||
NOTIFICATIONS.newAccounts([ACCOUNTS.a.primary]),
|
||||
'origin should have correct notification'
|
||||
)
|
||||
@ -557,18 +557,18 @@ describe('permissions controller', function () {
|
||||
notifications = initNotifications()
|
||||
permController = initPermController(notifications)
|
||||
grantPermissions(
|
||||
permController, ORIGINS.a,
|
||||
permController, DOMAINS.a.origin,
|
||||
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted)
|
||||
)
|
||||
grantPermissions(
|
||||
permController, ORIGINS.b,
|
||||
permController, DOMAINS.b.origin,
|
||||
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.b.permitted)
|
||||
)
|
||||
})
|
||||
|
||||
it('should throw if account is not a string', async function () {
|
||||
await assert.rejects(
|
||||
() => permController.removePermittedAccount(ORIGINS.a, {}),
|
||||
() => permController.removePermittedAccount(DOMAINS.a.origin, {}),
|
||||
ERRORS.validatePermittedAccounts.nonKeyringAccount({}),
|
||||
'should throw on non-string account param'
|
||||
)
|
||||
@ -576,7 +576,7 @@ describe('permissions controller', function () {
|
||||
|
||||
it('should throw if given account is not in keyring', async function () {
|
||||
await assert.rejects(
|
||||
() => permController.removePermittedAccount(ORIGINS.a, DUMMY_ACCOUNT),
|
||||
() => permController.removePermittedAccount(DOMAINS.a.origin, DUMMY_ACCOUNT),
|
||||
ERRORS.validatePermittedAccounts.nonKeyringAccount(DUMMY_ACCOUNT),
|
||||
'should throw on non-keyring account'
|
||||
)
|
||||
@ -592,7 +592,7 @@ describe('permissions controller', function () {
|
||||
|
||||
it('should throw if origin lacks any permissions', async function () {
|
||||
await assert.rejects(
|
||||
() => permController.removePermittedAccount(ORIGINS.c, EXTRA_ACCOUNT),
|
||||
() => permController.removePermittedAccount(DOMAINS.c.origin, EXTRA_ACCOUNT),
|
||||
ERRORS.removePermittedAccount.invalidOrigin(),
|
||||
'should throw on origin without permissions'
|
||||
)
|
||||
@ -600,12 +600,12 @@ describe('permissions controller', function () {
|
||||
|
||||
it('should throw if origin lacks eth_accounts permission', async function () {
|
||||
grantPermissions(
|
||||
permController, ORIGINS.c,
|
||||
permController, DOMAINS.c.origin,
|
||||
PERMS.finalizedRequests.test_method()
|
||||
)
|
||||
|
||||
await assert.rejects(
|
||||
() => permController.removePermittedAccount(ORIGINS.c, EXTRA_ACCOUNT),
|
||||
() => permController.removePermittedAccount(DOMAINS.c.origin, EXTRA_ACCOUNT),
|
||||
ERRORS.removePermittedAccount.noEthAccountsPermission(),
|
||||
'should throw on origin without eth_accounts permission'
|
||||
)
|
||||
@ -613,16 +613,16 @@ describe('permissions controller', function () {
|
||||
|
||||
it('should throw if account is not permitted', async function () {
|
||||
await assert.rejects(
|
||||
() => permController.removePermittedAccount(ORIGINS.b, ACCOUNTS.c.permitted[0]),
|
||||
() => permController.removePermittedAccount(DOMAINS.b.origin, ACCOUNTS.c.permitted[0]),
|
||||
ERRORS.removePermittedAccount.notPermitted(),
|
||||
'should throw if account is not permitted'
|
||||
)
|
||||
})
|
||||
|
||||
it('should successfully remove permitted account', async function () {
|
||||
await permController.removePermittedAccount(ORIGINS.a, ACCOUNTS.a.permitted[1])
|
||||
await permController.removePermittedAccount(DOMAINS.a.origin, ACCOUNTS.a.permitted[1])
|
||||
|
||||
const accounts = await permController._getPermittedAccounts(ORIGINS.a)
|
||||
const accounts = await permController._getPermittedAccounts(DOMAINS.a.origin)
|
||||
|
||||
assert.deepEqual(
|
||||
accounts, ACCOUNTS.a.permitted.filter((acc) => acc !== ACCOUNTS.a.permitted[1]),
|
||||
@ -630,16 +630,16 @@ describe('permissions controller', function () {
|
||||
)
|
||||
|
||||
assert.deepEqual(
|
||||
notifications[ORIGINS.a][0],
|
||||
notifications[DOMAINS.a.origin][0],
|
||||
NOTIFICATIONS.newAccounts([ACCOUNTS.a.primary]),
|
||||
'origin should have correct notification'
|
||||
)
|
||||
})
|
||||
|
||||
it('should remove eth_accounts permission if removing only permitted account', async function () {
|
||||
await permController.removePermittedAccount(ORIGINS.b, ACCOUNTS.b.permitted[0])
|
||||
await permController.removePermittedAccount(DOMAINS.b.origin, ACCOUNTS.b.permitted[0])
|
||||
|
||||
const accounts = await permController.getAccounts(ORIGINS.b)
|
||||
const accounts = await permController.getAccounts(DOMAINS.b.origin)
|
||||
|
||||
assert.deepEqual(
|
||||
accounts, [],
|
||||
@ -647,13 +647,13 @@ describe('permissions controller', function () {
|
||||
)
|
||||
|
||||
const permission = await permController.permissions.getPermission(
|
||||
ORIGINS.b, PERM_NAMES.eth_accounts
|
||||
DOMAINS.b.origin, PERM_NAMES.eth_accounts
|
||||
)
|
||||
|
||||
assert.equal(permission, undefined, 'origin should not have eth_accounts permission')
|
||||
|
||||
assert.deepEqual(
|
||||
notifications[ORIGINS.b][0],
|
||||
notifications[DOMAINS.b.origin][0],
|
||||
NOTIFICATIONS.removedAccounts(),
|
||||
'origin should have correct notification'
|
||||
)
|
||||
@ -744,11 +744,11 @@ describe('permissions controller', function () {
|
||||
preferences,
|
||||
})
|
||||
grantPermissions(
|
||||
permController, ORIGINS.b,
|
||||
permController, DOMAINS.b.origin,
|
||||
PERMS.finalizedRequests.eth_accounts([...ACCOUNTS.a.permitted, EXTRA_ACCOUNT])
|
||||
)
|
||||
grantPermissions(
|
||||
permController, ORIGINS.c,
|
||||
permController, DOMAINS.c.origin,
|
||||
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted)
|
||||
)
|
||||
})
|
||||
@ -774,11 +774,11 @@ describe('permissions controller', function () {
|
||||
await onPreferencesUpdate({ selectedAddress: DUMMY_ACCOUNT })
|
||||
|
||||
assert.deepEqual(
|
||||
notifications[ORIGINS.b], [],
|
||||
notifications[DOMAINS.b.origin], [],
|
||||
'should not have emitted notification'
|
||||
)
|
||||
assert.deepEqual(
|
||||
notifications[ORIGINS.c], [],
|
||||
notifications[DOMAINS.c.origin], [],
|
||||
'should not have emitted notification'
|
||||
)
|
||||
})
|
||||
@ -792,12 +792,12 @@ describe('permissions controller', function () {
|
||||
await onPreferencesUpdate({ selectedAddress: ACCOUNTS.a.permitted[0] })
|
||||
|
||||
assert.deepEqual(
|
||||
notifications[ORIGINS.b],
|
||||
notifications[DOMAINS.b.origin],
|
||||
[NOTIFICATIONS.newAccounts([ACCOUNTS.a.primary])],
|
||||
'should not have emitted notification'
|
||||
)
|
||||
assert.deepEqual(
|
||||
notifications[ORIGINS.c],
|
||||
notifications[DOMAINS.c.origin],
|
||||
[NOTIFICATIONS.newAccounts([ACCOUNTS.a.primary])],
|
||||
'should not have emitted notification'
|
||||
)
|
||||
@ -812,12 +812,12 @@ describe('permissions controller', function () {
|
||||
await onPreferencesUpdate({ selectedAddress: EXTRA_ACCOUNT })
|
||||
|
||||
assert.deepEqual(
|
||||
notifications[ORIGINS.b],
|
||||
notifications[DOMAINS.b.origin],
|
||||
[NOTIFICATIONS.newAccounts([EXTRA_ACCOUNT])],
|
||||
'should have emitted notification'
|
||||
)
|
||||
assert.deepEqual(
|
||||
notifications[ORIGINS.c], [],
|
||||
notifications[DOMAINS.c.origin], [],
|
||||
'should not have emitted notification'
|
||||
)
|
||||
})
|
||||
@ -831,12 +831,12 @@ describe('permissions controller', function () {
|
||||
await onPreferencesUpdate({ selectedAddress: ACCOUNTS.a.permitted[1] })
|
||||
|
||||
assert.deepEqual(
|
||||
notifications[ORIGINS.b],
|
||||
notifications[DOMAINS.b.origin],
|
||||
[NOTIFICATIONS.newAccounts([ACCOUNTS.a.permitted[1]])],
|
||||
'should have emitted notification'
|
||||
)
|
||||
assert.deepEqual(
|
||||
notifications[ORIGINS.c],
|
||||
notifications[DOMAINS.c.origin],
|
||||
[NOTIFICATIONS.newAccounts([ACCOUNTS.c.primary])],
|
||||
'should have emitted notification'
|
||||
)
|
||||
@ -1113,7 +1113,7 @@ describe('permissions controller', function () {
|
||||
let middleware
|
||||
assert.doesNotThrow(
|
||||
() => {
|
||||
middleware = permController.createMiddleware({ origin: ORIGINS.a })
|
||||
middleware = permController.createMiddleware({ origin: DOMAINS.a.origin })
|
||||
},
|
||||
'should not throw'
|
||||
)
|
||||
@ -1137,7 +1137,7 @@ describe('permissions controller', function () {
|
||||
assert.doesNotThrow(
|
||||
() => {
|
||||
middleware = permController.createMiddleware({
|
||||
origin: ORIGINS.a,
|
||||
origin: DOMAINS.a.origin,
|
||||
extensionId,
|
||||
})
|
||||
},
|
||||
@ -1157,13 +1157,13 @@ describe('permissions controller', function () {
|
||||
const metadataStore = permController.store.getState()[METADATA_STORE_KEY]
|
||||
|
||||
assert.deepEqual(
|
||||
metadataStore[ORIGINS.a], { extensionId, lastUpdated: 1 },
|
||||
metadataStore[DOMAINS.a.origin], { extensionId, lastUpdated: 1 },
|
||||
'metadata should be stored'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('notifyDomain', function () {
|
||||
describe('notifyAccountsChanged', function () {
|
||||
|
||||
let notifications, permController
|
||||
|
||||
@ -1173,11 +1173,11 @@ describe('permissions controller', function () {
|
||||
sinon.spy(permController.permissionsLog, 'updateAccountsHistory')
|
||||
})
|
||||
|
||||
it('notifyDomain handles accountsChanged', async function () {
|
||||
it('notifyAccountsChanged records history and sends notification', async function () {
|
||||
|
||||
permController.notifyDomain(
|
||||
ORIGINS.a,
|
||||
NOTIFICATIONS.newAccounts(ACCOUNTS.a.permitted),
|
||||
permController.notifyAccountsChanged(
|
||||
DOMAINS.a.origin,
|
||||
ACCOUNTS.a.permitted,
|
||||
)
|
||||
|
||||
assert.ok(
|
||||
@ -1186,25 +1186,51 @@ describe('permissions controller', function () {
|
||||
)
|
||||
|
||||
assert.deepEqual(
|
||||
notifications[ORIGINS.a],
|
||||
notifications[DOMAINS.a.origin],
|
||||
[ NOTIFICATIONS.newAccounts(ACCOUNTS.a.permitted) ],
|
||||
'origin should have correct notification'
|
||||
)
|
||||
})
|
||||
|
||||
it('notifyDomain handles notifications other than accountsChanged', async function () {
|
||||
it('notifyAccountsChanged throws on invalid origin', async function () {
|
||||
|
||||
permController.notifyDomain(ORIGINS.a, NOTIFICATIONS.test())
|
||||
|
||||
assert.ok(
|
||||
permController.permissionsLog.updateAccountsHistory.notCalled,
|
||||
'permissionsLog.updateAccountsHistory should not have been called'
|
||||
assert.throws(
|
||||
() => permController.notifyAccountsChanged(
|
||||
4,
|
||||
ACCOUNTS.a.permitted,
|
||||
),
|
||||
ERRORS.notifyAccountsChanged.invalidOrigin(4),
|
||||
'should throw expected error for non-string origin'
|
||||
)
|
||||
|
||||
assert.deepEqual(
|
||||
notifications[ORIGINS.a],
|
||||
[ NOTIFICATIONS.test() ],
|
||||
'origin should have correct notification'
|
||||
assert.throws(
|
||||
() => permController.notifyAccountsChanged(
|
||||
'',
|
||||
ACCOUNTS.a.permitted,
|
||||
),
|
||||
ERRORS.notifyAccountsChanged.invalidOrigin(''),
|
||||
'should throw expected error for empty string origin'
|
||||
)
|
||||
})
|
||||
|
||||
it('notifyAccountsChanged throws on invalid accounts', async function () {
|
||||
|
||||
assert.throws(
|
||||
() => permController.notifyAccountsChanged(
|
||||
DOMAINS.a.origin,
|
||||
4,
|
||||
),
|
||||
ERRORS.notifyAccountsChanged.invalidAccounts(),
|
||||
'should throw expected error for truthy non-array accounts'
|
||||
)
|
||||
|
||||
assert.throws(
|
||||
() => permController.notifyAccountsChanged(
|
||||
DOMAINS.a.origin,
|
||||
null,
|
||||
),
|
||||
ERRORS.notifyAccountsChanged.invalidAccounts(),
|
||||
'should throw expected error for falsy non-array accounts'
|
||||
)
|
||||
})
|
||||
})
|
||||
@ -1236,13 +1262,13 @@ describe('permissions controller', function () {
|
||||
|
||||
permController.store.getState = sinon.fake.returns({
|
||||
[METADATA_STORE_KEY]: {
|
||||
[ORIGINS.a]: {
|
||||
[DOMAINS.a.origin]: {
|
||||
foo: 'bar',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
permController.addDomainMetadata(ORIGINS.b, { foo: 'bar' })
|
||||
permController.addDomainMetadata(DOMAINS.b.origin, { foo: 'bar' })
|
||||
|
||||
assert.ok(
|
||||
permController.store.getState.called,
|
||||
@ -1255,11 +1281,12 @@ describe('permissions controller', function () {
|
||||
assert.deepEqual(
|
||||
permController._setDomainMetadata.lastCall.args,
|
||||
[{
|
||||
[ORIGINS.a]: {
|
||||
[DOMAINS.a.origin]: {
|
||||
foo: 'bar',
|
||||
},
|
||||
[ORIGINS.b]: {
|
||||
[DOMAINS.b.origin]: {
|
||||
foo: 'bar',
|
||||
host: DOMAINS.b.host,
|
||||
lastUpdated: 1,
|
||||
},
|
||||
}]
|
||||
@ -1270,16 +1297,16 @@ describe('permissions controller', function () {
|
||||
|
||||
permController.store.getState = sinon.fake.returns({
|
||||
[METADATA_STORE_KEY]: {
|
||||
[ORIGINS.a]: {
|
||||
[DOMAINS.a.origin]: {
|
||||
foo: 'bar',
|
||||
},
|
||||
[ORIGINS.b]: {
|
||||
[DOMAINS.b.origin]: {
|
||||
bar: 'baz',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
permController.addDomainMetadata(ORIGINS.b, { foo: 'bar' })
|
||||
permController.addDomainMetadata(DOMAINS.b.origin, { foo: 'bar' })
|
||||
|
||||
assert.ok(
|
||||
permController.store.getState.called,
|
||||
@ -1292,12 +1319,13 @@ describe('permissions controller', function () {
|
||||
assert.deepEqual(
|
||||
permController._setDomainMetadata.lastCall.args,
|
||||
[{
|
||||
[ORIGINS.a]: {
|
||||
[DOMAINS.a.origin]: {
|
||||
foo: 'bar',
|
||||
},
|
||||
[ORIGINS.b]: {
|
||||
[DOMAINS.b.origin]: {
|
||||
foo: 'bar',
|
||||
bar: 'baz',
|
||||
host: DOMAINS.b.host,
|
||||
lastUpdated: 1,
|
||||
},
|
||||
}]
|
||||
@ -1321,7 +1349,7 @@ describe('permissions controller', function () {
|
||||
permController._pendingSiteMetadata.add(origin)
|
||||
})
|
||||
|
||||
permController.addDomainMetadata(ORIGINS.a, { foo: 'bar' })
|
||||
permController.addDomainMetadata(DOMAINS.a.origin, { foo: 'bar' })
|
||||
|
||||
assert.ok(
|
||||
permController.store.getState.called,
|
||||
@ -1330,8 +1358,9 @@ describe('permissions controller', function () {
|
||||
|
||||
const expectedMetadata = {
|
||||
...mockMetadata,
|
||||
[ORIGINS.a]: {
|
||||
[DOMAINS.a.origin]: {
|
||||
foo: 'bar',
|
||||
host: DOMAINS.a.host,
|
||||
lastUpdated: 1,
|
||||
},
|
||||
}
|
||||
@ -1359,12 +1388,12 @@ describe('permissions controller', function () {
|
||||
it('trims domain metadata for domains without permissions', function () {
|
||||
|
||||
const metadataArg = {
|
||||
[ORIGINS.a]: {},
|
||||
[ORIGINS.b]: {},
|
||||
[DOMAINS.a.origin]: {},
|
||||
[DOMAINS.b.origin]: {},
|
||||
}
|
||||
|
||||
permController.permissions.getDomains = sinon.fake.returns({
|
||||
[ORIGINS.a]: {},
|
||||
[DOMAINS.a.origin]: {},
|
||||
})
|
||||
|
||||
const metadataResult = permController._trimDomainMetadata(metadataArg)
|
||||
@ -1376,7 +1405,7 @@ describe('permissions controller', function () {
|
||||
assert.deepEqual(
|
||||
metadataResult,
|
||||
{
|
||||
[ORIGINS.a]: {},
|
||||
[DOMAINS.a.origin]: {},
|
||||
},
|
||||
'should have produced expected state'
|
||||
)
|
||||
@ -1404,7 +1433,7 @@ describe('permissions controller', function () {
|
||||
it('_addPendingApproval: should throw if adding origin twice', function () {
|
||||
|
||||
const id = nanoid()
|
||||
const origin = ORIGINS.a
|
||||
const origin = DOMAINS.a
|
||||
|
||||
permController._addPendingApproval(id, origin, noop, noop)
|
||||
|
||||
|
@ -29,7 +29,7 @@ const {
|
||||
const {
|
||||
ACCOUNTS,
|
||||
EXPECTED_HISTORIES,
|
||||
ORIGINS,
|
||||
DOMAINS,
|
||||
PERM_NAMES,
|
||||
REQUEST_IDS,
|
||||
RESTRICTED_METHODS,
|
||||
@ -86,7 +86,7 @@ describe('permissions log', function () {
|
||||
|
||||
// test_method, success
|
||||
|
||||
req = RPC_REQUESTS.test_method(ORIGINS.a)
|
||||
req = RPC_REQUESTS.test_method(DOMAINS.a.origin)
|
||||
req.id = REQUEST_IDS.a
|
||||
res = { foo: 'bar' }
|
||||
|
||||
@ -103,7 +103,7 @@ describe('permissions log', function () {
|
||||
|
||||
// eth_accounts, failure
|
||||
|
||||
req = RPC_REQUESTS.eth_accounts(ORIGINS.b)
|
||||
req = RPC_REQUESTS.eth_accounts(DOMAINS.b.origin)
|
||||
req.id = REQUEST_IDS.b
|
||||
res = { error: new Error('Unauthorized.') }
|
||||
|
||||
@ -120,7 +120,7 @@ describe('permissions log', function () {
|
||||
|
||||
// eth_requestAccounts, success
|
||||
|
||||
req = RPC_REQUESTS.eth_requestAccounts(ORIGINS.c)
|
||||
req = RPC_REQUESTS.eth_requestAccounts(DOMAINS.c.origin)
|
||||
req.id = REQUEST_IDS.c
|
||||
res = { result: ACCOUNTS.c.permitted }
|
||||
|
||||
@ -137,7 +137,7 @@ describe('permissions log', function () {
|
||||
|
||||
// test_method, no response
|
||||
|
||||
req = RPC_REQUESTS.test_method(ORIGINS.a)
|
||||
req = RPC_REQUESTS.test_method(DOMAINS.a.origin)
|
||||
req.id = REQUEST_IDS.a
|
||||
res = null
|
||||
|
||||
@ -170,7 +170,7 @@ describe('permissions log', function () {
|
||||
const id2 = nanoid()
|
||||
const id3 = nanoid()
|
||||
|
||||
const req = RPC_REQUESTS.test_method(ORIGINS.a)
|
||||
const req = RPC_REQUESTS.test_method(DOMAINS.a.origin)
|
||||
|
||||
// get make requests
|
||||
req.id = id1
|
||||
@ -230,7 +230,7 @@ describe('permissions log', function () {
|
||||
|
||||
it('handles a lack of response', function () {
|
||||
|
||||
let req = RPC_REQUESTS.test_method(ORIGINS.a)
|
||||
let req = RPC_REQUESTS.test_method(DOMAINS.a.origin)
|
||||
req.id = REQUEST_IDS.a
|
||||
let res = { foo: 'bar' }
|
||||
|
||||
@ -247,7 +247,7 @@ describe('permissions log', function () {
|
||||
)
|
||||
|
||||
// next request should be handled as normal
|
||||
req = RPC_REQUESTS.eth_accounts(ORIGINS.b)
|
||||
req = RPC_REQUESTS.eth_accounts(DOMAINS.b.origin)
|
||||
req.id = REQUEST_IDS.b
|
||||
res = { result: ACCOUNTS.b.permitted }
|
||||
|
||||
@ -272,9 +272,9 @@ describe('permissions log', function () {
|
||||
assert.equal(log.length, 0, 'log should be empty')
|
||||
|
||||
const res = { foo: 'bar' }
|
||||
const req1 = RPC_REQUESTS.wallet_sendDomainMetadata(ORIGINS.c, 'foobar')
|
||||
const req2 = RPC_REQUESTS.custom(ORIGINS.b, 'eth_getBlockNumber')
|
||||
const req3 = RPC_REQUESTS.custom(ORIGINS.b, 'net_version')
|
||||
const req1 = RPC_REQUESTS.wallet_sendDomainMetadata(DOMAINS.c.origin, 'foobar')
|
||||
const req2 = RPC_REQUESTS.custom(DOMAINS.b.origin, 'eth_getBlockNumber')
|
||||
const req3 = RPC_REQUESTS.custom(DOMAINS.b.origin, 'net_version')
|
||||
|
||||
logMiddleware(req1, res)
|
||||
logMiddleware(req2, res)
|
||||
@ -286,7 +286,7 @@ describe('permissions log', function () {
|
||||
|
||||
it('enforces log limit', function () {
|
||||
|
||||
const req = RPC_REQUESTS.test_method(ORIGINS.a)
|
||||
const req = RPC_REQUESTS.test_method(DOMAINS.a.origin)
|
||||
const res = { foo: 'bar' }
|
||||
|
||||
// max out log
|
||||
@ -352,7 +352,7 @@ describe('permissions log', function () {
|
||||
let permHistory
|
||||
|
||||
const req = RPC_REQUESTS.requestPermission(
|
||||
ORIGINS.a, PERM_NAMES.test_method
|
||||
DOMAINS.a.origin, PERM_NAMES.test_method
|
||||
)
|
||||
const res = { result: [ PERMS.granted.test_method() ] }
|
||||
|
||||
@ -371,7 +371,7 @@ describe('permissions log', function () {
|
||||
'history should have single origin'
|
||||
)
|
||||
assert.ok(
|
||||
Boolean(permHistory[ORIGINS.a]),
|
||||
Boolean(permHistory[DOMAINS.a.origin]),
|
||||
'history should have expected origin'
|
||||
)
|
||||
})
|
||||
@ -379,7 +379,7 @@ describe('permissions log', function () {
|
||||
it('ignores malformed permissions requests', function () {
|
||||
|
||||
const req = RPC_REQUESTS.requestPermission(
|
||||
ORIGINS.a, PERM_NAMES.test_method
|
||||
DOMAINS.a.origin, PERM_NAMES.test_method
|
||||
)
|
||||
delete req.params
|
||||
const res = { result: [ PERMS.granted.test_method() ] }
|
||||
@ -395,7 +395,7 @@ describe('permissions log', function () {
|
||||
let permHistory
|
||||
|
||||
const req = RPC_REQUESTS.requestPermission(
|
||||
ORIGINS.a, PERM_NAMES.eth_accounts
|
||||
DOMAINS.a.origin, PERM_NAMES.eth_accounts
|
||||
)
|
||||
const res = {
|
||||
result: [ PERMS.granted.eth_accounts(ACCOUNTS.a.permitted) ],
|
||||
@ -433,7 +433,7 @@ describe('permissions log', function () {
|
||||
it('handles eth_accounts response without caveats', async function () {
|
||||
|
||||
const req = RPC_REQUESTS.requestPermission(
|
||||
ORIGINS.a, PERM_NAMES.eth_accounts
|
||||
DOMAINS.a.origin, PERM_NAMES.eth_accounts
|
||||
)
|
||||
const res = {
|
||||
result: [ PERMS.granted.eth_accounts(ACCOUNTS.a.permitted) ],
|
||||
@ -453,7 +453,7 @@ describe('permissions log', function () {
|
||||
it('handles extra caveats for eth_accounts', async function () {
|
||||
|
||||
const req = RPC_REQUESTS.requestPermission(
|
||||
ORIGINS.a, PERM_NAMES.eth_accounts
|
||||
DOMAINS.a.origin, PERM_NAMES.eth_accounts
|
||||
)
|
||||
const res = {
|
||||
result: [ PERMS.granted.eth_accounts(ACCOUNTS.a.permitted) ],
|
||||
@ -476,7 +476,7 @@ describe('permissions log', function () {
|
||||
it('handles unrequested permissions on the response', async function () {
|
||||
|
||||
const req = RPC_REQUESTS.requestPermission(
|
||||
ORIGINS.a, PERM_NAMES.eth_accounts
|
||||
DOMAINS.a.origin, PERM_NAMES.eth_accounts
|
||||
)
|
||||
const res = {
|
||||
result: [
|
||||
@ -499,7 +499,7 @@ describe('permissions log', function () {
|
||||
it('does not update history if no new permissions are approved', async function () {
|
||||
|
||||
let req = RPC_REQUESTS.requestPermission(
|
||||
ORIGINS.a, PERM_NAMES.test_method
|
||||
DOMAINS.a.origin, PERM_NAMES.test_method
|
||||
)
|
||||
let res = {
|
||||
result: [
|
||||
@ -522,7 +522,7 @@ describe('permissions log', function () {
|
||||
clock.tick(1)
|
||||
|
||||
req = RPC_REQUESTS.requestPermission(
|
||||
ORIGINS.a, PERM_NAMES.eth_accounts
|
||||
DOMAINS.a.origin, PERM_NAMES.eth_accounts
|
||||
)
|
||||
res = {
|
||||
result: [
|
||||
@ -553,7 +553,7 @@ describe('permissions log', function () {
|
||||
// first origin
|
||||
round1.push({
|
||||
req: RPC_REQUESTS.requestPermission(
|
||||
ORIGINS.a, PERM_NAMES.test_method
|
||||
DOMAINS.a.origin, PERM_NAMES.test_method
|
||||
),
|
||||
res: {
|
||||
result: [ PERMS.granted.test_method() ],
|
||||
@ -563,7 +563,7 @@ describe('permissions log', function () {
|
||||
// second origin
|
||||
round1.push({
|
||||
req: RPC_REQUESTS.requestPermission(
|
||||
ORIGINS.b, PERM_NAMES.eth_accounts
|
||||
DOMAINS.b.origin, PERM_NAMES.eth_accounts
|
||||
),
|
||||
res: {
|
||||
result: [ PERMS.granted.eth_accounts(ACCOUNTS.b.permitted) ],
|
||||
@ -572,7 +572,7 @@ describe('permissions log', function () {
|
||||
|
||||
// third origin
|
||||
round1.push({
|
||||
req: RPC_REQUESTS.requestPermissions(ORIGINS.c, {
|
||||
req: RPC_REQUESTS.requestPermissions(DOMAINS.c.origin, {
|
||||
[PERM_NAMES.test_method]: {},
|
||||
[PERM_NAMES.eth_accounts]: {},
|
||||
}),
|
||||
@ -611,7 +611,7 @@ describe('permissions log', function () {
|
||||
// first origin
|
||||
round2.push({
|
||||
req: RPC_REQUESTS.requestPermission(
|
||||
ORIGINS.a, PERM_NAMES.test_method
|
||||
DOMAINS.a.origin, PERM_NAMES.test_method
|
||||
),
|
||||
res: {
|
||||
result: [ PERMS.granted.test_method() ],
|
||||
@ -622,7 +622,7 @@ describe('permissions log', function () {
|
||||
|
||||
// third origin
|
||||
round2.push({
|
||||
req: RPC_REQUESTS.requestPermissions(ORIGINS.c, {
|
||||
req: RPC_REQUESTS.requestPermissions(DOMAINS.c.origin, {
|
||||
[PERM_NAMES.eth_accounts]: {},
|
||||
}),
|
||||
res: {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { strict as assert } from 'assert'
|
||||
import { useFakeTimers } from 'sinon'
|
||||
import sinon from 'sinon'
|
||||
|
||||
import {
|
||||
METADATA_STORE_KEY,
|
||||
@ -30,7 +30,7 @@ const {
|
||||
|
||||
const {
|
||||
ACCOUNTS,
|
||||
ORIGINS,
|
||||
DOMAINS,
|
||||
PERM_NAMES,
|
||||
} = constants
|
||||
|
||||
@ -58,14 +58,15 @@ describe('permissions middleware', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
permController = initPermController()
|
||||
permController.notifyAccountsChanged = sinon.fake()
|
||||
})
|
||||
|
||||
it('grants permissions on user approval', async function () {
|
||||
|
||||
const aMiddleware = getPermissionsMiddleware(permController, ORIGINS.a)
|
||||
const aMiddleware = getPermissionsMiddleware(permController, DOMAINS.a.origin)
|
||||
|
||||
const req = RPC_REQUESTS.requestPermission(
|
||||
ORIGINS.a, PERM_NAMES.eth_accounts
|
||||
DOMAINS.a.origin, PERM_NAMES.eth_accounts
|
||||
)
|
||||
const res = {}
|
||||
|
||||
@ -98,25 +99,32 @@ describe('permissions middleware', function () {
|
||||
validatePermission(
|
||||
res.result[0],
|
||||
PERM_NAMES.eth_accounts,
|
||||
ORIGINS.a,
|
||||
DOMAINS.a.origin,
|
||||
CAVEATS.eth_accounts(ACCOUNTS.a.permitted)
|
||||
)
|
||||
|
||||
const aAccounts = await permController.getAccounts(ORIGINS.a)
|
||||
const aAccounts = await permController.getAccounts(DOMAINS.a.origin)
|
||||
assert.deepEqual(
|
||||
aAccounts, [ACCOUNTS.a.primary],
|
||||
'origin should have correct accounts'
|
||||
)
|
||||
|
||||
assert.ok(
|
||||
permController.notifyAccountsChanged.calledOnceWith(
|
||||
DOMAINS.a.origin, aAccounts,
|
||||
),
|
||||
'expected notification call should have been made'
|
||||
)
|
||||
})
|
||||
|
||||
it('handles serial approved requests that overwrite existing permissions', async function () {
|
||||
|
||||
const aMiddleware = getPermissionsMiddleware(permController, ORIGINS.a)
|
||||
const aMiddleware = getPermissionsMiddleware(permController, DOMAINS.a.origin)
|
||||
|
||||
// create first request
|
||||
|
||||
const req1 = RPC_REQUESTS.requestPermission(
|
||||
ORIGINS.a, PERM_NAMES.eth_accounts
|
||||
DOMAINS.a.origin, PERM_NAMES.eth_accounts
|
||||
)
|
||||
const res1 = {}
|
||||
|
||||
@ -147,16 +155,23 @@ describe('permissions middleware', function () {
|
||||
validatePermission(
|
||||
res1.result[0],
|
||||
PERM_NAMES.eth_accounts,
|
||||
ORIGINS.a,
|
||||
DOMAINS.a.origin,
|
||||
CAVEATS.eth_accounts(ACCOUNTS.a.permitted)
|
||||
)
|
||||
|
||||
const accounts1 = await permController.getAccounts(ORIGINS.a)
|
||||
const accounts1 = await permController.getAccounts(DOMAINS.a.origin)
|
||||
assert.deepEqual(
|
||||
accounts1, [ACCOUNTS.a.primary],
|
||||
'origin should have correct accounts'
|
||||
)
|
||||
|
||||
assert.ok(
|
||||
permController.notifyAccountsChanged.calledOnceWith(
|
||||
DOMAINS.a.origin, accounts1,
|
||||
),
|
||||
'expected notification call should have been made'
|
||||
)
|
||||
|
||||
// create second request
|
||||
|
||||
const requestedPerms2 = {
|
||||
@ -165,7 +180,7 @@ describe('permissions middleware', function () {
|
||||
}
|
||||
|
||||
const req2 = RPC_REQUESTS.requestPermissions(
|
||||
ORIGINS.a, { ...requestedPerms2 }
|
||||
DOMAINS.a.origin, { ...requestedPerms2 }
|
||||
)
|
||||
const res2 = {}
|
||||
|
||||
@ -196,29 +211,41 @@ describe('permissions middleware', function () {
|
||||
validatePermission(
|
||||
res2.result[0],
|
||||
PERM_NAMES.eth_accounts,
|
||||
ORIGINS.a,
|
||||
DOMAINS.a.origin,
|
||||
CAVEATS.eth_accounts(ACCOUNTS.b.permitted)
|
||||
)
|
||||
|
||||
validatePermission(
|
||||
res2.result[1],
|
||||
PERM_NAMES.test_method,
|
||||
ORIGINS.a,
|
||||
DOMAINS.a.origin,
|
||||
)
|
||||
|
||||
const accounts2 = await permController.getAccounts(ORIGINS.a)
|
||||
const accounts2 = await permController.getAccounts(DOMAINS.a.origin)
|
||||
assert.deepEqual(
|
||||
accounts2, [ACCOUNTS.b.primary],
|
||||
'origin should have correct accounts'
|
||||
)
|
||||
|
||||
assert.equal(
|
||||
permController.notifyAccountsChanged.callCount, 2,
|
||||
'should have called notification method 2 times in total'
|
||||
)
|
||||
|
||||
assert.ok(
|
||||
permController.notifyAccountsChanged.lastCall.calledWith(
|
||||
DOMAINS.a.origin, accounts2,
|
||||
),
|
||||
'expected notification call should have been made'
|
||||
)
|
||||
})
|
||||
|
||||
it('rejects permissions on user rejection', async function () {
|
||||
|
||||
const aMiddleware = getPermissionsMiddleware(permController, ORIGINS.a)
|
||||
const aMiddleware = getPermissionsMiddleware(permController, DOMAINS.a.origin)
|
||||
|
||||
const req = RPC_REQUESTS.requestPermission(
|
||||
ORIGINS.a, PERM_NAMES.eth_accounts
|
||||
DOMAINS.a.origin, PERM_NAMES.eth_accounts
|
||||
)
|
||||
const res = {}
|
||||
|
||||
@ -248,18 +275,23 @@ describe('permissions middleware', function () {
|
||||
'response should have expected error and no result'
|
||||
)
|
||||
|
||||
const aAccounts = await permController.getAccounts(ORIGINS.a)
|
||||
const aAccounts = await permController.getAccounts(DOMAINS.a.origin)
|
||||
assert.deepEqual(
|
||||
aAccounts, [], 'origin should have have correct accounts'
|
||||
)
|
||||
|
||||
assert.ok(
|
||||
permController.notifyAccountsChanged.notCalled,
|
||||
'should not have called notification method'
|
||||
)
|
||||
})
|
||||
|
||||
it('rejects requests with unknown permissions', async function () {
|
||||
|
||||
const aMiddleware = getPermissionsMiddleware(permController, ORIGINS.a)
|
||||
const aMiddleware = getPermissionsMiddleware(permController, DOMAINS.a.origin)
|
||||
|
||||
const req = RPC_REQUESTS.requestPermissions(
|
||||
ORIGINS.a, {
|
||||
DOMAINS.a.origin, {
|
||||
...PERMS.requests.does_not_exist(),
|
||||
...PERMS.requests.test_method(),
|
||||
}
|
||||
@ -288,6 +320,11 @@ describe('permissions middleware', function () {
|
||||
),
|
||||
'response should have expected error and no result'
|
||||
)
|
||||
|
||||
assert.ok(
|
||||
permController.notifyAccountsChanged.notCalled,
|
||||
'should not have called notification method'
|
||||
)
|
||||
})
|
||||
|
||||
it('accepts only a single pending permissions request per origin', async function () {
|
||||
@ -296,13 +333,13 @@ describe('permissions middleware', function () {
|
||||
|
||||
// two middlewares for two origins
|
||||
|
||||
const aMiddleware = getPermissionsMiddleware(permController, ORIGINS.a)
|
||||
const bMiddleware = getPermissionsMiddleware(permController, ORIGINS.b)
|
||||
const aMiddleware = getPermissionsMiddleware(permController, DOMAINS.a.origin)
|
||||
const bMiddleware = getPermissionsMiddleware(permController, DOMAINS.b.origin)
|
||||
|
||||
// create and start processing first request for first origin
|
||||
|
||||
const reqA1 = RPC_REQUESTS.requestPermission(
|
||||
ORIGINS.a, PERM_NAMES.test_method
|
||||
DOMAINS.a.origin, PERM_NAMES.test_method
|
||||
)
|
||||
const resA1 = {}
|
||||
|
||||
@ -314,7 +351,7 @@ describe('permissions middleware', function () {
|
||||
// create and start processing first request for second origin
|
||||
|
||||
const reqB1 = RPC_REQUESTS.requestPermission(
|
||||
ORIGINS.b, PERM_NAMES.test_method
|
||||
DOMAINS.b.origin, PERM_NAMES.test_method
|
||||
)
|
||||
const resB1 = {}
|
||||
|
||||
@ -332,7 +369,7 @@ describe('permissions middleware', function () {
|
||||
// which should throw
|
||||
|
||||
const reqA2 = RPC_REQUESTS.requestPermission(
|
||||
ORIGINS.a, PERM_NAMES.test_method
|
||||
DOMAINS.a.origin, PERM_NAMES.test_method
|
||||
)
|
||||
const resA2 = {}
|
||||
|
||||
@ -402,9 +439,9 @@ describe('permissions middleware', function () {
|
||||
|
||||
it('prevents restricted method access for unpermitted domain', async function () {
|
||||
|
||||
const aMiddleware = getPermissionsMiddleware(permController, ORIGINS.a)
|
||||
const aMiddleware = getPermissionsMiddleware(permController, DOMAINS.a.origin)
|
||||
|
||||
const req = RPC_REQUESTS.test_method(ORIGINS.a)
|
||||
const req = RPC_REQUESTS.test_method(DOMAINS.a.origin)
|
||||
const res = {}
|
||||
|
||||
const expectedError = ERRORS.rpcCap.unauthorized()
|
||||
@ -426,11 +463,11 @@ describe('permissions middleware', function () {
|
||||
|
||||
it('allows restricted method access for permitted domain', async function () {
|
||||
|
||||
const bMiddleware = getPermissionsMiddleware(permController, ORIGINS.b)
|
||||
const bMiddleware = getPermissionsMiddleware(permController, DOMAINS.b.origin)
|
||||
|
||||
grantPermissions(permController, ORIGINS.b, PERMS.finalizedRequests.test_method())
|
||||
grantPermissions(permController, DOMAINS.b.origin, PERMS.finalizedRequests.test_method())
|
||||
|
||||
const req = RPC_REQUESTS.test_method(ORIGINS.b, true)
|
||||
const req = RPC_REQUESTS.test_method(DOMAINS.b.origin, true)
|
||||
const res = {}
|
||||
|
||||
await assert.doesNotReject(
|
||||
@ -455,9 +492,9 @@ describe('permissions middleware', function () {
|
||||
|
||||
it('returns empty array for non-permitted domain', async function () {
|
||||
|
||||
const aMiddleware = getPermissionsMiddleware(permController, ORIGINS.a)
|
||||
const aMiddleware = getPermissionsMiddleware(permController, DOMAINS.a.origin)
|
||||
|
||||
const req = RPC_REQUESTS.eth_accounts(ORIGINS.a)
|
||||
const req = RPC_REQUESTS.eth_accounts(DOMAINS.a.origin)
|
||||
const res = {}
|
||||
|
||||
await assert.doesNotReject(
|
||||
@ -477,14 +514,14 @@ describe('permissions middleware', function () {
|
||||
|
||||
it('returns correct accounts for permitted domain', async function () {
|
||||
|
||||
const aMiddleware = getPermissionsMiddleware(permController, ORIGINS.a)
|
||||
const aMiddleware = getPermissionsMiddleware(permController, DOMAINS.a.origin)
|
||||
|
||||
grantPermissions(
|
||||
permController, ORIGINS.a,
|
||||
permController, DOMAINS.a.origin,
|
||||
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.a.permitted)
|
||||
)
|
||||
|
||||
const req = RPC_REQUESTS.eth_accounts(ORIGINS.a)
|
||||
const req = RPC_REQUESTS.eth_accounts(DOMAINS.a.origin)
|
||||
const res = {}
|
||||
|
||||
await assert.doesNotReject(
|
||||
@ -515,9 +552,9 @@ describe('permissions middleware', function () {
|
||||
|
||||
const userApprovalPromise = getUserApprovalPromise(permController)
|
||||
|
||||
const aMiddleware = getPermissionsMiddleware(permController, ORIGINS.a)
|
||||
const aMiddleware = getPermissionsMiddleware(permController, DOMAINS.a.origin)
|
||||
|
||||
const req = RPC_REQUESTS.eth_requestAccounts(ORIGINS.a)
|
||||
const req = RPC_REQUESTS.eth_requestAccounts(DOMAINS.a.origin)
|
||||
const res = {}
|
||||
|
||||
const pendingApproval = assert.doesNotReject(
|
||||
@ -540,7 +577,7 @@ describe('permissions middleware', function () {
|
||||
// wait for permission to be granted
|
||||
await pendingApproval
|
||||
|
||||
const perms = permController.permissions.getPermissionsForDomain(ORIGINS.a)
|
||||
const perms = permController.permissions.getPermissionsForDomain(DOMAINS.a.origin)
|
||||
|
||||
assert.equal(
|
||||
perms.length, 1,
|
||||
@ -550,7 +587,7 @@ describe('permissions middleware', function () {
|
||||
validatePermission(
|
||||
perms[0],
|
||||
PERM_NAMES.eth_accounts,
|
||||
ORIGINS.a,
|
||||
DOMAINS.a.origin,
|
||||
CAVEATS.eth_accounts(ACCOUNTS.a.permitted)
|
||||
)
|
||||
|
||||
@ -566,7 +603,7 @@ describe('permissions middleware', function () {
|
||||
)
|
||||
|
||||
// we should also be able to get the accounts independently
|
||||
const aAccounts = await permController.getAccounts(ORIGINS.a)
|
||||
const aAccounts = await permController.getAccounts(DOMAINS.a.origin)
|
||||
assert.deepEqual(
|
||||
aAccounts, [ACCOUNTS.a.primary], 'origin should have have correct accounts'
|
||||
)
|
||||
@ -576,9 +613,9 @@ describe('permissions middleware', function () {
|
||||
|
||||
const userApprovalPromise = getUserApprovalPromise(permController)
|
||||
|
||||
const aMiddleware = getPermissionsMiddleware(permController, ORIGINS.a)
|
||||
const aMiddleware = getPermissionsMiddleware(permController, DOMAINS.a.origin)
|
||||
|
||||
const req = RPC_REQUESTS.eth_requestAccounts(ORIGINS.a)
|
||||
const req = RPC_REQUESTS.eth_requestAccounts(DOMAINS.a.origin)
|
||||
const res = {}
|
||||
|
||||
const expectedError = ERRORS.rejectPermissionsRequest.rejection()
|
||||
@ -609,7 +646,7 @@ describe('permissions middleware', function () {
|
||||
'response should have expected error and no result'
|
||||
)
|
||||
|
||||
const aAccounts = await permController.getAccounts(ORIGINS.a)
|
||||
const aAccounts = await permController.getAccounts(DOMAINS.a.origin)
|
||||
assert.deepEqual(
|
||||
aAccounts, [], 'origin should have have correct accounts'
|
||||
)
|
||||
@ -617,14 +654,14 @@ describe('permissions middleware', function () {
|
||||
|
||||
it('directly returns accounts for permitted domain', async function () {
|
||||
|
||||
const cMiddleware = getPermissionsMiddleware(permController, ORIGINS.c)
|
||||
const cMiddleware = getPermissionsMiddleware(permController, DOMAINS.c.origin)
|
||||
|
||||
grantPermissions(
|
||||
permController, ORIGINS.c,
|
||||
permController, DOMAINS.c.origin,
|
||||
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.c.permitted)
|
||||
)
|
||||
|
||||
const req = RPC_REQUESTS.eth_requestAccounts(ORIGINS.c)
|
||||
const req = RPC_REQUESTS.eth_requestAccounts(DOMAINS.c.origin)
|
||||
const res = {}
|
||||
|
||||
await assert.doesNotReject(
|
||||
@ -651,14 +688,14 @@ describe('permissions middleware', function () {
|
||||
|
||||
permController.getUnlockPromise = () => unlockPromise
|
||||
|
||||
const cMiddleware = getPermissionsMiddleware(permController, ORIGINS.c)
|
||||
const cMiddleware = getPermissionsMiddleware(permController, DOMAINS.c.origin)
|
||||
|
||||
grantPermissions(
|
||||
permController, ORIGINS.c,
|
||||
permController, DOMAINS.c.origin,
|
||||
PERMS.finalizedRequests.eth_accounts(ACCOUNTS.c.permitted)
|
||||
)
|
||||
|
||||
const req = RPC_REQUESTS.eth_requestAccounts(ORIGINS.c)
|
||||
const req = RPC_REQUESTS.eth_requestAccounts(DOMAINS.c.origin)
|
||||
const res = {}
|
||||
|
||||
// this will block until we resolve the unlock Promise
|
||||
@ -695,7 +732,7 @@ describe('permissions middleware', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
permController = initPermController()
|
||||
clock = useFakeTimers(1)
|
||||
clock = sinon.useFakeTimers(1)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
@ -706,9 +743,9 @@ describe('permissions middleware', function () {
|
||||
|
||||
const name = 'BAZ'
|
||||
|
||||
const cMiddleware = getPermissionsMiddleware(permController, ORIGINS.c)
|
||||
const cMiddleware = getPermissionsMiddleware(permController, DOMAINS.c.origin)
|
||||
|
||||
const req = RPC_REQUESTS.wallet_sendDomainMetadata(ORIGINS.c, name)
|
||||
const req = RPC_REQUESTS.wallet_sendDomainMetadata(DOMAINS.c.origin, name)
|
||||
const res = {}
|
||||
|
||||
await assert.doesNotReject(
|
||||
@ -722,7 +759,13 @@ describe('permissions middleware', function () {
|
||||
|
||||
assert.deepEqual(
|
||||
metadataStore,
|
||||
{ [ORIGINS.c]: { name, lastUpdated: 1 } },
|
||||
{
|
||||
[DOMAINS.c.origin]: {
|
||||
name,
|
||||
host: DOMAINS.c.host,
|
||||
lastUpdated: 1,
|
||||
},
|
||||
},
|
||||
'metadata should have been added to store'
|
||||
)
|
||||
})
|
||||
@ -733,9 +776,9 @@ describe('permissions middleware', function () {
|
||||
|
||||
const name = 'BAZ'
|
||||
|
||||
const cMiddleware = getPermissionsMiddleware(permController, ORIGINS.c, extensionId)
|
||||
const cMiddleware = getPermissionsMiddleware(permController, DOMAINS.c.origin, extensionId)
|
||||
|
||||
const req = RPC_REQUESTS.wallet_sendDomainMetadata(ORIGINS.c, name)
|
||||
const req = RPC_REQUESTS.wallet_sendDomainMetadata(DOMAINS.c.origin, name)
|
||||
const res = {}
|
||||
|
||||
await assert.doesNotReject(
|
||||
@ -749,7 +792,7 @@ describe('permissions middleware', function () {
|
||||
|
||||
assert.deepEqual(
|
||||
metadataStore,
|
||||
{ [ORIGINS.c]: { name, extensionId, lastUpdated: 1 } },
|
||||
{ [DOMAINS.c.origin]: { name, extensionId, lastUpdated: 1 } },
|
||||
'metadata should have been added to store'
|
||||
)
|
||||
})
|
||||
@ -758,9 +801,9 @@ describe('permissions middleware', function () {
|
||||
|
||||
const name = null
|
||||
|
||||
const cMiddleware = getPermissionsMiddleware(permController, ORIGINS.c)
|
||||
const cMiddleware = getPermissionsMiddleware(permController, DOMAINS.c.origin)
|
||||
|
||||
const req = RPC_REQUESTS.wallet_sendDomainMetadata(ORIGINS.c, name)
|
||||
const req = RPC_REQUESTS.wallet_sendDomainMetadata(DOMAINS.c.origin, name)
|
||||
const res = {}
|
||||
|
||||
await assert.doesNotReject(
|
||||
@ -780,9 +823,9 @@ describe('permissions middleware', function () {
|
||||
|
||||
it('should not record domain metadata if no metadata', async function () {
|
||||
|
||||
const cMiddleware = getPermissionsMiddleware(permController, ORIGINS.c)
|
||||
const cMiddleware = getPermissionsMiddleware(permController, DOMAINS.c.origin)
|
||||
|
||||
const req = RPC_REQUESTS.wallet_sendDomainMetadata(ORIGINS.c)
|
||||
const req = RPC_REQUESTS.wallet_sendDomainMetadata(DOMAINS.c.origin)
|
||||
delete req.domainMetadata
|
||||
const res = {}
|
||||
|
||||
|
@ -13,8 +13,11 @@ describe('TokenRatesController', function () {
|
||||
|
||||
it('should poll on correct interval', async function () {
|
||||
const stub = sinon.stub(global, 'setInterval')
|
||||
new TokenRatesController({ interval: 1337 }) // eslint-disable-line no-new
|
||||
const rateController = new TokenRatesController() // eslint-disable-line no-new
|
||||
rateController.start(1337)
|
||||
|
||||
assert.strictEqual(stub.getCall(0).args[1], 1337)
|
||||
stub.restore()
|
||||
rateController.stop()
|
||||
})
|
||||
})
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { strict as assert } from 'assert'
|
||||
import { throwIfAccountIsBlacklisted } from '../../../../../app/scripts/controllers/transactions/lib/recipient-blacklist-checker'
|
||||
import { throwIfAccountIsBlocked } from '../../../../../app/scripts/controllers/transactions/lib/recipient-blocklist-checker'
|
||||
import { ROPSTEN_NETWORK_ID, RINKEBY_NETWORK_ID, KOVAN_NETWORK_ID, GOERLI_NETWORK_ID } from '../../../../../app/scripts/controllers/network/enums'
|
||||
|
||||
describe('Recipient Blacklist Checker', function () {
|
||||
describe('#throwIfAccountIsBlacklisted', function () {
|
||||
describe('Recipient Blocklist Checker', function () {
|
||||
describe('#throwIfAccountIsBlocked', function () {
|
||||
// Accounts from Ganache's original default seed phrase
|
||||
const publicAccounts = [
|
||||
'0x627306090abab3a6e1400e9345bc60c78a8bef57',
|
||||
@ -22,7 +22,7 @@ describe('Recipient Blacklist Checker', function () {
|
||||
const networks = [ROPSTEN_NETWORK_ID, RINKEBY_NETWORK_ID, KOVAN_NETWORK_ID, GOERLI_NETWORK_ID]
|
||||
for (const networkId of networks) {
|
||||
for (const account of publicAccounts) {
|
||||
assert.doesNotThrow(() => throwIfAccountIsBlacklisted(networkId, account))
|
||||
assert.doesNotThrow(() => throwIfAccountIsBlocked(networkId, account))
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -30,7 +30,7 @@ describe('Recipient Blacklist Checker', function () {
|
||||
it('fails on mainnet', function () {
|
||||
for (const account of publicAccounts) {
|
||||
assert.throws(
|
||||
() => throwIfAccountIsBlacklisted(1, account),
|
||||
() => throwIfAccountIsBlocked(1, account),
|
||||
{ message: 'Recipient is a public account' },
|
||||
)
|
||||
}
|
||||
@ -38,14 +38,14 @@ describe('Recipient Blacklist Checker', function () {
|
||||
|
||||
it('fails for public account - uppercase', function () {
|
||||
assert.throws(
|
||||
() => throwIfAccountIsBlacklisted(1, '0X0D1D4E623D10F9FBA5DB95830F7D3839406C6AF2'),
|
||||
() => throwIfAccountIsBlocked(1, '0X0D1D4E623D10F9FBA5DB95830F7D3839406C6AF2'),
|
||||
{ message: 'Recipient is a public account' },
|
||||
)
|
||||
})
|
||||
|
||||
it('fails for public account - lowercase', function () {
|
||||
assert.throws(
|
||||
() => throwIfAccountIsBlacklisted(1, '0x0d1d4e623d10f9fba5db95830f7d3839406c6af2'),
|
||||
() => throwIfAccountIsBlocked(1, '0x0d1d4e623d10f9fba5db95830f7d3839406c6af2'),
|
||||
{ message: 'Recipient is a public account' },
|
||||
)
|
||||
})
|
@ -5,7 +5,7 @@ import {
|
||||
replayHistory,
|
||||
generateHistoryEntry,
|
||||
} from '../../../../../app/scripts/controllers/transactions/lib/tx-state-history-helpers'
|
||||
import testVault from '../../../../data/v17-long-history.json'
|
||||
import testData from '../../../../data/mock-tx-history.json'
|
||||
|
||||
describe('Transaction state history helper', function () {
|
||||
describe('#snapshotFromTxMeta', function () {
|
||||
@ -33,7 +33,7 @@ describe('Transaction state history helper', function () {
|
||||
|
||||
describe('#migrateFromSnapshotsToDiffs', function () {
|
||||
it('migrates history to diffs and can recover original values', function () {
|
||||
testVault.data.TransactionController.transactions.forEach((tx) => {
|
||||
testData.TransactionsController.transactions.forEach((tx) => {
|
||||
const newHistory = migrateFromSnapshotsToDiffs(tx.history)
|
||||
newHistory.forEach((newEntry, index) => {
|
||||
if (index === 0) {
|
||||
|
@ -7,7 +7,6 @@ import InputAdornment from '@material-ui/core/InputAdornment'
|
||||
import { Menu, Item, Divider, CloseArea } from '../dropdowns/components/menu'
|
||||
import { ENVIRONMENT_TYPE_POPUP } from '../../../../../app/scripts/lib/enums'
|
||||
import { getEnvironmentType } from '../../../../../app/scripts/lib/util'
|
||||
import Tooltip from '../../ui/tooltip'
|
||||
import Identicon from '../../ui/identicon'
|
||||
import IconWithFallBack from '../../ui/icon-with-fallback'
|
||||
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
|
||||
@ -38,7 +37,6 @@ export default class AccountMenu extends Component {
|
||||
lockMetamask: PropTypes.func,
|
||||
selectedAddress: PropTypes.string,
|
||||
showAccountDetail: PropTypes.func,
|
||||
showRemoveAccountConfirmationModal: PropTypes.func,
|
||||
toggleAccountMenu: PropTypes.func,
|
||||
addressConnectedDomainMap: PropTypes.object,
|
||||
originOfCurrentTab: PropTypes.string,
|
||||
@ -176,6 +174,7 @@ export default class AccountMenu extends Component {
|
||||
type={PRIMARY}
|
||||
/>
|
||||
</div>
|
||||
{ this.renderKeyringType(keyring) }
|
||||
{ iconAndNameForOpenDomain
|
||||
? (
|
||||
<div className="account-menu__icon-list">
|
||||
@ -184,45 +183,11 @@ export default class AccountMenu extends Component {
|
||||
)
|
||||
: null
|
||||
}
|
||||
{ this.renderKeyringType(keyring) }
|
||||
{ this.renderRemoveAccount(keyring, identity) }
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
renderRemoveAccount (keyring, identity) {
|
||||
const { t } = this.context
|
||||
|
||||
// Sometimes keyrings aren't loaded yet
|
||||
if (!keyring) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Any account that's not from the HD wallet Keyring can be removed
|
||||
const { type } = keyring
|
||||
const isRemovable = type !== 'HD Key Tree'
|
||||
|
||||
return isRemovable && (
|
||||
<Tooltip
|
||||
title={t('removeAccount')}
|
||||
position="bottom"
|
||||
>
|
||||
<a
|
||||
className="remove-account-icon"
|
||||
onClick={(e) => this.removeAccount(e, identity)}
|
||||
/>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
removeAccount (e, identity) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const { showRemoveAccountConfirmationModal } = this.props
|
||||
showRemoveAccountConfirmationModal(identity)
|
||||
}
|
||||
|
||||
renderKeyringType (keyring) {
|
||||
const { t } = this.context
|
||||
|
||||
|
@ -7,7 +7,6 @@ import {
|
||||
hideSidebar,
|
||||
lockMetamask,
|
||||
hideWarning,
|
||||
showModal,
|
||||
} from '../../../store/actions'
|
||||
import {
|
||||
getAddressConnectedDomainMap,
|
||||
@ -54,9 +53,6 @@ function mapDispatchToProps (dispatch) {
|
||||
dispatch(hideSidebar())
|
||||
dispatch(toggleAccountMenu())
|
||||
},
|
||||
showRemoveAccountConfirmationModal: (identity) => {
|
||||
return dispatch(showModal({ name: 'CONFIRM_REMOVE_ACCOUNT', identity }))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,6 +78,7 @@
|
||||
|
||||
.keyring-label {
|
||||
margin-top: 5px;
|
||||
margin-right: 10px;
|
||||
background-color: $dusty-gray;
|
||||
color: $black;
|
||||
font-weight: normal;
|
||||
|
@ -99,19 +99,6 @@ describe('Account Menu', function () {
|
||||
const importedAccount = wrapper.find('.keyring-label.allcaps')
|
||||
assert.equal(importedAccount.text(), 'imported')
|
||||
})
|
||||
|
||||
it('remove account', function () {
|
||||
const removeAccount = wrapper.find('.remove-account-icon')
|
||||
removeAccount.simulate('click', {
|
||||
preventDefault: () => {},
|
||||
stopPropagation: () => {},
|
||||
})
|
||||
|
||||
assert(props.showRemoveAccountConfirmationModal.calledOnce)
|
||||
assert.deepEqual(props.showRemoveAccountConfirmationModal.getCall(0).args[0],
|
||||
{ address: '0xImportedAddress', balance: '0x0', name: 'Imported Account 1' }
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Log Out', function () {
|
||||
|
@ -2,9 +2,12 @@ import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import Identicon from '../../ui/identicon'
|
||||
import ListItem from '../../ui/list-item'
|
||||
import Tooltip from '../../ui/tooltip-v2'
|
||||
import InfoIcon from '../../ui/icon/info-icon.component'
|
||||
|
||||
|
||||
const AssetListItem = ({
|
||||
children,
|
||||
className,
|
||||
'data-testid': dataTestId,
|
||||
iconClassName,
|
||||
@ -12,32 +15,54 @@ const AssetListItem = ({
|
||||
tokenAddress,
|
||||
tokenImage,
|
||||
warning,
|
||||
primary,
|
||||
secondary,
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={classnames('asset-list-item__container', className)}
|
||||
data-testid={dataTestId}
|
||||
onClick={onClick}
|
||||
>
|
||||
<Identicon
|
||||
className={iconClassName}
|
||||
diameter={32}
|
||||
address={tokenAddress}
|
||||
image={tokenImage}
|
||||
/>
|
||||
<div
|
||||
className="asset-list-item__balance"
|
||||
const titleIcon = warning
|
||||
? (
|
||||
<Tooltip
|
||||
wrapperClassName="asset-list-item__warning-tooltip"
|
||||
interactive
|
||||
position="bottom"
|
||||
html={warning}
|
||||
>
|
||||
{ children }
|
||||
</div>
|
||||
{ warning }
|
||||
<i className="fas fa-chevron-right asset-list-item__chevron-right" />
|
||||
</div>
|
||||
<InfoIcon severity="warning" />
|
||||
</Tooltip>
|
||||
)
|
||||
: null
|
||||
|
||||
const midContent = warning
|
||||
? (
|
||||
<>
|
||||
<InfoIcon severity="warning" />
|
||||
<div className="asset-list-item__warning">{warning}</div>
|
||||
</>
|
||||
)
|
||||
: null
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
className={classnames('asset-list-item', className)}
|
||||
data-testid={dataTestId}
|
||||
title={primary}
|
||||
titleIcon={titleIcon}
|
||||
subtitle={secondary}
|
||||
onClick={onClick}
|
||||
icon={(
|
||||
<Identicon
|
||||
className={iconClassName}
|
||||
diameter={32}
|
||||
address={tokenAddress}
|
||||
image={tokenImage}
|
||||
/>
|
||||
)}
|
||||
midContent={midContent}
|
||||
rightContent={<i className="fas fa-chevron-right asset-list-item__chevron-right" />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
AssetListItem.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
className: PropTypes.string,
|
||||
'data-testid': PropTypes.string,
|
||||
iconClassName: PropTypes.string,
|
||||
@ -45,6 +70,8 @@ AssetListItem.propTypes = {
|
||||
tokenAddress: PropTypes.string,
|
||||
tokenImage: PropTypes.string,
|
||||
warning: PropTypes.node,
|
||||
primary: PropTypes.string,
|
||||
secondary: PropTypes.string,
|
||||
}
|
||||
|
||||
AssetListItem.defaultProps = {
|
||||
|
@ -1,26 +1,29 @@
|
||||
.asset-list-item {
|
||||
&__container {
|
||||
display: flex;
|
||||
padding: 24px 16px;
|
||||
align-items: center;
|
||||
border-top: 1px solid $mercury;
|
||||
border-bottom: 1px solid $mercury;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: $Grey-000;
|
||||
}
|
||||
}
|
||||
|
||||
&__balance {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 15px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__chevron-right {
|
||||
color: $Grey-500;
|
||||
}
|
||||
|
||||
.list-item__right-content {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.list-item__subheading {
|
||||
margin-top: 6px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&__warning {
|
||||
flex: 1;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
&__warning-tooltip {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.list-item__mid-content {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,11 +6,11 @@ import AddTokenButton from '../add-token-button'
|
||||
import TokenList from '../token-list'
|
||||
import { ADD_TOKEN_ROUTE } from '../../../helpers/constants/routes'
|
||||
import AssetListItem from '../asset-list-item'
|
||||
import CurrencyDisplay from '../../ui/currency-display'
|
||||
import { PRIMARY, SECONDARY } from '../../../helpers/constants/common'
|
||||
import { useMetricEvent } from '../../../hooks/useMetricEvent'
|
||||
import { useUserPreferencedCurrency } from '../../../hooks/useUserPreferencedCurrency'
|
||||
import { getCurrentAccountWithSendEtherInfo, getNativeCurrency, getShouldShowFiat } from '../../../selectors'
|
||||
import { useCurrencyDisplay } from '../../../hooks/useCurrencyDisplay'
|
||||
|
||||
const AssetList = ({ onClickAsset }) => {
|
||||
const history = useHistory()
|
||||
@ -41,29 +41,24 @@ const AssetList = ({ onClickAsset }) => {
|
||||
numberOfDecimals: secondaryNumberOfDecimals,
|
||||
} = useUserPreferencedCurrency(SECONDARY, { ethNumberOfDecimals: 4 })
|
||||
|
||||
const [primaryCurrencyDisplay] = useCurrencyDisplay(
|
||||
selectedAccountBalance,
|
||||
{ numberOfDecimals: primaryNumberOfDecimals, currency: primaryCurrency }
|
||||
)
|
||||
|
||||
const [secondaryCurrencyDisplay] = useCurrencyDisplay(
|
||||
selectedAccountBalance,
|
||||
{ numberOfDecimals: secondaryNumberOfDecimals, currency: secondaryCurrency }
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<AssetListItem
|
||||
onClick={() => onClickAsset(nativeCurrency)}
|
||||
data-testid="wallet-balance"
|
||||
>
|
||||
<CurrencyDisplay
|
||||
className="asset-list__primary-amount"
|
||||
currency={primaryCurrency}
|
||||
numberOfDecimals={primaryNumberOfDecimals}
|
||||
value={selectedAccountBalance}
|
||||
/>
|
||||
{
|
||||
showFiat && (
|
||||
<CurrencyDisplay
|
||||
className="asset-list__secondary-amount"
|
||||
currency={secondaryCurrency}
|
||||
numberOfDecimals={secondaryNumberOfDecimals}
|
||||
value={selectedAccountBalance}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</AssetListItem>
|
||||
primary={primaryCurrencyDisplay}
|
||||
secondary={showFiat ? secondaryCurrencyDisplay : undefined}
|
||||
/>
|
||||
<TokenList
|
||||
onTokenClick={(tokenAddress) => {
|
||||
onClickAsset(tokenAddress)
|
||||
|
@ -1,13 +0,0 @@
|
||||
.asset-list {
|
||||
&__primary-amount {
|
||||
color: $Black-100;
|
||||
font-size: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
&__secondary-amount {
|
||||
color: $Grey-500;
|
||||
margin-top: 6px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
@ -42,9 +42,15 @@ export default class ConnectedAccountsListItem extends PureComponent {
|
||||
<p>
|
||||
<strong className="connected-accounts-list__account-name">{name}</strong>
|
||||
</p>
|
||||
<p className="connected-accounts-list__account-status">
|
||||
{status}
|
||||
</p>
|
||||
{
|
||||
status
|
||||
? (
|
||||
<p className="connected-accounts-list__account-status">
|
||||
{status}
|
||||
</p>
|
||||
)
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{options}
|
||||
|
@ -24,7 +24,7 @@ export default class ConnectedAccountsList extends PureComponent {
|
||||
connectedAccounts: PropTypes.arrayOf(PropTypes.shape({
|
||||
address: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
lastActive: PropTypes.number.isRequired,
|
||||
lastActive: PropTypes.number,
|
||||
})).isRequired,
|
||||
permissions: PropTypes.arrayOf(PropTypes.shape({
|
||||
key: PropTypes.string.isRequired,
|
||||
@ -97,38 +97,49 @@ export default class ConnectedAccountsList extends PureComponent {
|
||||
<>
|
||||
<main className="connected-accounts-list">
|
||||
{this.renderUnconnectedAccount()}
|
||||
{connectedAccounts.map(({ address, name, lastActive }, index) => (
|
||||
<ConnectedAccountsListItem
|
||||
key={address}
|
||||
address={address}
|
||||
name={`${name} (…${address.substr(-4, 4)})`}
|
||||
status={index === 0 ? t('primary') : `${t('lastActive')}: ${DateTime.fromMillis(lastActive).toISODate()}`}
|
||||
options={(
|
||||
<ConnectedAccountsListOptions
|
||||
onHideOptions={this.hideAccountOptions}
|
||||
onShowOptions={this.showAccountOptions.bind(null, address)}
|
||||
show={accountWithOptionsShown === address}
|
||||
>
|
||||
{
|
||||
address === selectedAddress ? null : (
|
||||
{
|
||||
connectedAccounts.map(({ address, name, lastActive }, index) => {
|
||||
let status
|
||||
if (index === 0) {
|
||||
status = t('primary')
|
||||
} else if (lastActive) {
|
||||
status = `${t('lastActive')}: ${DateTime.fromMillis(lastActive).toISODate()}`
|
||||
}
|
||||
|
||||
return (
|
||||
<ConnectedAccountsListItem
|
||||
key={address}
|
||||
address={address}
|
||||
name={`${name} (…${address.substr(-4, 4)})`}
|
||||
status={status}
|
||||
options={(
|
||||
<ConnectedAccountsListOptions
|
||||
onHideOptions={this.hideAccountOptions}
|
||||
onShowOptions={this.showAccountOptions.bind(null, address)}
|
||||
show={accountWithOptionsShown === address}
|
||||
>
|
||||
{
|
||||
address === selectedAddress ? null : (
|
||||
<MenuItem
|
||||
iconClassName="fas fa-random"
|
||||
onClick={this.switchAccount}
|
||||
>
|
||||
{t('switchToThisAccount')}
|
||||
</MenuItem>
|
||||
)
|
||||
}
|
||||
<MenuItem
|
||||
iconClassName="fas fa-random"
|
||||
onClick={this.switchAccount}
|
||||
iconClassName="disconnect-icon"
|
||||
onClick={this.disconnectAccount}
|
||||
>
|
||||
{t('switchToThisAccount')}
|
||||
{t('disconnectThisAccount')}
|
||||
</MenuItem>
|
||||
)
|
||||
}
|
||||
<MenuItem
|
||||
iconClassName="disconnect-icon"
|
||||
onClick={this.disconnectAccount}
|
||||
>
|
||||
{t('disconnectThisAccount')}
|
||||
</MenuItem>
|
||||
</ConnectedAccountsListOptions>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</ConnectedAccountsListOptions>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
</main>
|
||||
<ConnectedAccountsListPermissions permissions={permissions} />
|
||||
</>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import IconWithFallBack from '../../ui/icon-with-fallback'
|
||||
import { stripHttpSchemes } from '../../../helpers/utils/util'
|
||||
|
||||
export default class ConnectedSitesList extends Component {
|
||||
static contextTypes = {
|
||||
@ -11,9 +12,11 @@ export default class ConnectedSitesList extends Component {
|
||||
connectedDomains: PropTypes.arrayOf(PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
key: PropTypes.string,
|
||||
origin: PropTypes.string,
|
||||
host: PropTypes.string,
|
||||
})).isRequired,
|
||||
onDisconnect: PropTypes.func.isRequired,
|
||||
domainHostCount: PropTypes.objectOf(PropTypes.number).isRequired,
|
||||
}
|
||||
|
||||
render () {
|
||||
@ -23,25 +26,31 @@ export default class ConnectedSitesList extends Component {
|
||||
return (
|
||||
<main className="connected-sites-list__content-rows">
|
||||
{ connectedDomains.map((domain) => (
|
||||
<div key={domain.key} className="connected-sites-list__content-row">
|
||||
<div key={domain.origin} className="connected-sites-list__content-row">
|
||||
<div className="connected-sites-list__domain-info">
|
||||
<IconWithFallBack icon={domain.icon} name={domain.name} />
|
||||
<span className="connected-sites-list__domain-name" title={domain.extensionId || domain.key}>
|
||||
{
|
||||
domain.extensionId
|
||||
? t('externalExtension')
|
||||
: domain.key
|
||||
}
|
||||
<span className="connected-sites-list__domain-name" title={domain.extensionId || domain.origin}>
|
||||
{this.getDomainDisplayName(domain)}
|
||||
</span>
|
||||
</div>
|
||||
<i
|
||||
className="fas fa-trash-alt connected-sites-list__trash"
|
||||
title={t('disconnect')}
|
||||
onClick={() => onDisconnect(domain.key)}
|
||||
onClick={() => onDisconnect(domain.origin)}
|
||||
/>
|
||||
</div>
|
||||
)) }
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
getDomainDisplayName (domain) {
|
||||
if (domain.extensionId) {
|
||||
return this.context.t('externalExtension')
|
||||
}
|
||||
|
||||
return this.props.domainHostCount[domain.host] > 1
|
||||
? domain.origin
|
||||
: stripHttpSchemes(domain.origin)
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,6 @@
|
||||
|
||||
@import 'app-header/index';
|
||||
|
||||
@import 'asset-list/asset-list';
|
||||
|
||||
@import 'asset-list-item/asset-list-item';
|
||||
|
||||
@import '../ui/breadcrumbs/index';
|
||||
|
@ -117,8 +117,9 @@ export default function AccountOptionsMenu ({ anchorElement, onClose }) {
|
||||
isRemovable
|
||||
? (
|
||||
<MenuItem
|
||||
data-testid="account-options-menu__remove-account"
|
||||
onClick={() => {
|
||||
dispatch(showModal({ name: 'CONFIRM_REMOVE_ACCOUNT', selectedIdentity }))
|
||||
dispatch(showModal({ name: 'CONFIRM_REMOVE_ACCOUNT', identity: selectedIdentity }))
|
||||
onClose()
|
||||
}}
|
||||
iconClassName="fas fa-trash-alt"
|
||||
|
@ -65,12 +65,14 @@ class HideTokenConfirmationModal extends Component {
|
||||
<div className="hide-token-confirmation__buttons">
|
||||
<button
|
||||
className="btn-default hide-token-confirmation__button btn--large"
|
||||
data-testid="hide-token-confirmation__cancel"
|
||||
onClick={() => hideModal()}
|
||||
>
|
||||
{this.context.t('cancel')}
|
||||
</button>
|
||||
<button
|
||||
className="btn-secondary hide-token-confirmation__button btn--large"
|
||||
data-testid="hide-token-confirmation__hide"
|
||||
onClick={() => hideToken(address)}
|
||||
>
|
||||
{this.context.t('hide')}
|
||||
|
@ -4,7 +4,7 @@ import ethUtil from 'ethereumjs-util'
|
||||
import classnames from 'classnames'
|
||||
import { ObjectInspector } from 'react-inspector'
|
||||
|
||||
import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../../app/scripts/lib/enums'
|
||||
import { ENVIRONMENT_TYPE_NOTIFICATION, MESSAGE_TYPE } from '../../../../../app/scripts/lib/enums'
|
||||
import { getEnvironmentType } from '../../../../../app/scripts/lib/util'
|
||||
import Identicon from '../../ui/identicon'
|
||||
import AccountListItem from '../../../pages/send/account-list-item/account-list-item.component'
|
||||
@ -208,11 +208,11 @@ export default class SignatureRequestOriginal extends Component {
|
||||
const { txData } = this.props
|
||||
const { type, msgParams: { data } } = txData
|
||||
|
||||
if (type === 'personal_sign') {
|
||||
if (type === MESSAGE_TYPE.PERSONAL_SIGN) {
|
||||
rows = [{ name: this.context.t('message'), value: this.msgHexToText(data) }]
|
||||
} else if (type === 'eth_signTypedData') {
|
||||
} else if (type === MESSAGE_TYPE.ETH_SIGN_TYPED_DATA) {
|
||||
rows = data
|
||||
} else if (type === 'eth_sign') {
|
||||
} else if (type === MESSAGE_TYPE.ETH_SIGN) {
|
||||
rows = [{ name: this.context.t('message'), value: data }]
|
||||
notice = this.context.t('signNotice')
|
||||
}
|
||||
@ -223,12 +223,12 @@ export default class SignatureRequestOriginal extends Component {
|
||||
{ this.renderRequestInfo() }
|
||||
<div
|
||||
className={classnames('request-signature__notice', {
|
||||
'request-signature__warning': type === 'eth_sign',
|
||||
'request-signature__warning': type === MESSAGE_TYPE.ETH_SIGN,
|
||||
})}
|
||||
>
|
||||
{ notice }
|
||||
{
|
||||
type === 'eth_sign'
|
||||
type === MESSAGE_TYPE.ETH_SIGN
|
||||
? (
|
||||
<span
|
||||
className="request-signature__help-link"
|
||||
|
@ -2,6 +2,7 @@ import { connect } from 'react-redux'
|
||||
import { compose } from 'redux'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
|
||||
import { MESSAGE_TYPE } from '../../../../../app/scripts/lib/enums'
|
||||
import { goHome } from '../../../store/actions'
|
||||
import {
|
||||
accountsWithSendEtherInfoSelector,
|
||||
@ -50,13 +51,13 @@ function mergeProps (stateProps, dispatchProps, ownProps) {
|
||||
|
||||
let cancel
|
||||
let sign
|
||||
if (type === 'personal_sign') {
|
||||
if (type === MESSAGE_TYPE.PERSONAL_SIGN) {
|
||||
cancel = cancelPersonalMessage
|
||||
sign = signPersonalMessage
|
||||
} else if (type === 'eth_signTypedData') {
|
||||
} else if (type === MESSAGE_TYPE.ETH_SIGN_TYPED_DATA) {
|
||||
cancel = cancelTypedMessage
|
||||
sign = signTypedMessage
|
||||
} else if (type === 'eth_sign') {
|
||||
} else if (type === MESSAGE_TYPE.ETH_SIGN) {
|
||||
cancel = cancelMessage
|
||||
sign = signMessage
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
accountsWithSendEtherInfoSelector,
|
||||
} from '../../../selectors'
|
||||
import { getAccountByAddress } from '../../../helpers/utils/util'
|
||||
import { MESSAGE_TYPE } from '../../../../../app/scripts/lib/enums'
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
@ -38,13 +39,13 @@ function mergeProps (stateProps, dispatchProps, ownProps) {
|
||||
let cancel
|
||||
let sign
|
||||
|
||||
if (type === 'personal_sign') {
|
||||
if (type === MESSAGE_TYPE.PERSONAL_SIGN) {
|
||||
cancel = cancelPersonalMessage
|
||||
sign = signPersonalMessage
|
||||
} else if (type === 'eth_signTypedData') {
|
||||
} else if (type === MESSAGE_TYPE.ETH_SIGN_TYPED_DATA) {
|
||||
cancel = cancelTypedMessage
|
||||
sign = signTypedMessage
|
||||
} else if (type === 'eth_sign') {
|
||||
} else if (type === MESSAGE_TYPE.ETH_SIGN) {
|
||||
cancel = cancelMessage
|
||||
sign = signMessage
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
export { default } from './token-cell.container'
|
||||
export { default } from './token-cell'
|
||||
|
@ -1,111 +0,0 @@
|
||||
import classnames from 'classnames'
|
||||
import PropTypes from 'prop-types'
|
||||
import React, { Component } from 'react'
|
||||
import { conversionUtil, multiplyCurrencies } from '../../../helpers/utils/conversion-util'
|
||||
import Tooltip from '../../ui/tooltip-v2'
|
||||
import { I18nContext } from '../../../contexts/i18n'
|
||||
import AssetListItem from '../asset-list-item'
|
||||
|
||||
export default class TokenCell extends Component {
|
||||
static contextType = I18nContext
|
||||
|
||||
static propTypes = {
|
||||
address: PropTypes.string,
|
||||
outdatedBalance: PropTypes.bool,
|
||||
symbol: PropTypes.string,
|
||||
string: PropTypes.string,
|
||||
contractExchangeRates: PropTypes.object,
|
||||
conversionRate: PropTypes.number,
|
||||
currentCurrency: PropTypes.string,
|
||||
image: PropTypes.string,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
userAddress: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
outdatedBalance: false,
|
||||
}
|
||||
|
||||
render () {
|
||||
const t = this.context
|
||||
const {
|
||||
address,
|
||||
symbol,
|
||||
string,
|
||||
contractExchangeRates,
|
||||
conversionRate,
|
||||
onClick,
|
||||
currentCurrency,
|
||||
image,
|
||||
outdatedBalance,
|
||||
userAddress,
|
||||
} = this.props
|
||||
let currentTokenToFiatRate
|
||||
let currentTokenInFiat
|
||||
let formattedFiat = ''
|
||||
|
||||
if (contractExchangeRates[address]) {
|
||||
currentTokenToFiatRate = multiplyCurrencies(
|
||||
contractExchangeRates[address],
|
||||
conversionRate
|
||||
)
|
||||
currentTokenInFiat = conversionUtil(string, {
|
||||
fromNumericBase: 'dec',
|
||||
fromCurrency: symbol,
|
||||
toCurrency: currentCurrency.toUpperCase(),
|
||||
numberOfDecimals: 2,
|
||||
conversionRate: currentTokenToFiatRate,
|
||||
})
|
||||
formattedFiat = currentTokenInFiat.toString() === '0'
|
||||
? ''
|
||||
: `${currentTokenInFiat} ${currentCurrency.toUpperCase()}`
|
||||
}
|
||||
|
||||
const showFiat = Boolean(currentTokenInFiat) && currentCurrency.toUpperCase() !== symbol
|
||||
|
||||
const warning = outdatedBalance
|
||||
? (
|
||||
<Tooltip
|
||||
interactive
|
||||
position="bottom"
|
||||
html={(
|
||||
<div className="token-cell__outdated-tooltip">
|
||||
{ t('troubleTokenBalances') }
|
||||
<a
|
||||
href={`https://ethplorer.io/address/${userAddress}`}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
style={{ color: '#F7861C' }}
|
||||
>
|
||||
{ t('here') }
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<i className={classnames(['fa', 'fa-exclamation-circle', 'token-cell__outdated-icon'])} />
|
||||
</Tooltip>
|
||||
)
|
||||
: null
|
||||
|
||||
return (
|
||||
<AssetListItem
|
||||
className={classnames('token-cell', { 'token-cell--outdated': outdatedBalance })}
|
||||
iconClassName="token-cell__icon"
|
||||
onClick={onClick.bind(null, address)}
|
||||
tokenAddress={address}
|
||||
tokenImage={image}
|
||||
warning={warning}
|
||||
>
|
||||
<div className="token-cell__balance-wrapper">
|
||||
<div className="token-cell__token-balance">{string || 0}</div>
|
||||
<div className="token-cell__token-symbol">{symbol}</div>
|
||||
{showFiat && (
|
||||
<div className="token-cell__fiat-amount">
|
||||
{formattedFiat}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</AssetListItem>
|
||||
)
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
import { connect } from 'react-redux'
|
||||
import TokenCell from './token-cell.component'
|
||||
import { getSelectedAddress } from '../../../selectors'
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
contractExchangeRates: state.metamask.contractExchangeRates,
|
||||
conversionRate: state.metamask.conversionRate,
|
||||
currentCurrency: state.metamask.currentCurrency,
|
||||
userAddress: getSelectedAddress(state),
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(TokenCell)
|
88
ui/app/components/app/token-cell/token-cell.js
Normal file
88
ui/app/components/app/token-cell/token-cell.js
Normal file
@ -0,0 +1,88 @@
|
||||
import classnames from 'classnames'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import { conversionUtil, multiplyCurrencies } from '../../../helpers/utils/conversion-util'
|
||||
import AssetListItem from '../asset-list-item'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { getTokenExchangeRates, getConversionRate, getCurrentCurrency, getSelectedAddress } from '../../../selectors'
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext'
|
||||
import { formatCurrency } from '../../../helpers/utils/confirm-tx.util'
|
||||
|
||||
export default function TokenCell ({ address, outdatedBalance, symbol, string, image, onClick }) {
|
||||
const contractExchangeRates = useSelector(getTokenExchangeRates)
|
||||
const conversionRate = useSelector(getConversionRate)
|
||||
const currentCurrency = useSelector(getCurrentCurrency)
|
||||
const userAddress = useSelector(getSelectedAddress)
|
||||
const t = useI18nContext()
|
||||
|
||||
let currentTokenToFiatRate
|
||||
let currentTokenInFiat
|
||||
let formattedFiat = ''
|
||||
|
||||
|
||||
// if the conversionRate is 0 eg: currently unknown
|
||||
// or the contract exchange rate is currently unknown
|
||||
// the effective currentTokenToFiatRate is 0 and erroneous.
|
||||
// Skipping this entire block will result in fiat not being
|
||||
// shown to the user, instead of a fiat value of 0 for a non-zero
|
||||
// token amount.
|
||||
if (conversionRate > 0 && contractExchangeRates[address]) {
|
||||
currentTokenToFiatRate = multiplyCurrencies(
|
||||
contractExchangeRates[address],
|
||||
conversionRate
|
||||
)
|
||||
currentTokenInFiat = conversionUtil(string, {
|
||||
fromNumericBase: 'dec',
|
||||
fromCurrency: symbol,
|
||||
toCurrency: currentCurrency.toUpperCase(),
|
||||
numberOfDecimals: 2,
|
||||
conversionRate: currentTokenToFiatRate,
|
||||
})
|
||||
formattedFiat = `${formatCurrency(currentTokenInFiat, currentCurrency)} ${currentCurrency.toUpperCase()}`
|
||||
}
|
||||
|
||||
const showFiat = Boolean(currentTokenInFiat) && currentCurrency.toUpperCase() !== symbol
|
||||
|
||||
const warning = outdatedBalance
|
||||
? (
|
||||
<span>
|
||||
{ t('troubleTokenBalances') }
|
||||
<a
|
||||
href={`https://ethplorer.io/address/${userAddress}`}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
style={{ color: '#F7861C' }}
|
||||
>
|
||||
{ t('here') }
|
||||
</a>
|
||||
</span>
|
||||
)
|
||||
: null
|
||||
|
||||
return (
|
||||
<AssetListItem
|
||||
className={classnames('token-cell', { 'token-cell--outdated': outdatedBalance })}
|
||||
iconClassName="token-cell__icon"
|
||||
onClick={onClick.bind(null, address)}
|
||||
tokenAddress={address}
|
||||
tokenImage={image}
|
||||
warning={warning}
|
||||
primary={`${string || 0} ${symbol}`}
|
||||
secondary={showFiat ? formattedFiat : undefined}
|
||||
/>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
TokenCell.propTypes = {
|
||||
address: PropTypes.string,
|
||||
outdatedBalance: PropTypes.bool,
|
||||
symbol: PropTypes.string,
|
||||
string: PropTypes.string,
|
||||
image: PropTypes.string,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
TokenCell.defaultProps = {
|
||||
outdatedBalance: false,
|
||||
}
|
@ -1,95 +1,5 @@
|
||||
$wallet-balance-breakpoint: 890px;
|
||||
$wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (max-width: #{$wallet-balance-breakpoint})";
|
||||
|
||||
.token-cell {
|
||||
position: relative;
|
||||
|
||||
&__token-balance {
|
||||
margin-right: 4px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
&__token-balance, &__token-symbol {
|
||||
font-size: 16px;
|
||||
flex: 0 0 auto;
|
||||
color: $Black-100;
|
||||
}
|
||||
|
||||
&__fiat-amount {
|
||||
margin-top: 6px;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
text-transform: uppercase;
|
||||
&--outdated .list-item__heading {
|
||||
color: $Grey-500;
|
||||
}
|
||||
|
||||
&--outdated &__icon {
|
||||
opacity: 0.5
|
||||
}
|
||||
&--outdated &__balance-wrapper {
|
||||
opacity: 0.5
|
||||
}
|
||||
|
||||
&__balance-wrapper {
|
||||
flex: 1;
|
||||
flex-flow: row wrap;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__outdated-icon {
|
||||
color: $warning-yellow;
|
||||
display: block;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
&__outdated-tooltip {
|
||||
width: 260px;
|
||||
}
|
||||
}
|
||||
|
||||
.token-menu-dropdown {
|
||||
width: 80%;
|
||||
position: absolute;
|
||||
top: 52px;
|
||||
right: 25px;
|
||||
z-index: 2000;
|
||||
|
||||
@media #{$wallet-balance-breakpoint-range} {
|
||||
right: 18px;
|
||||
}
|
||||
|
||||
&__close-area {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 2100;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&__container {
|
||||
padding: 16px;
|
||||
z-index: 2200;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&__option {
|
||||
color: $white;
|
||||
font-family: Roboto;
|
||||
font-size: 16px;
|
||||
line-height: 21px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
@ -59,16 +59,12 @@ describe('Token Cell', function () {
|
||||
assert.equal(wrapper.find(Identicon).prop('image'), './test-image')
|
||||
})
|
||||
|
||||
it('renders token balance', function () {
|
||||
assert.equal(wrapper.find('.token-cell__token-balance').text(), '5.000')
|
||||
})
|
||||
|
||||
it('renders token symbol', function () {
|
||||
assert.equal(wrapper.find('.token-cell__token-symbol').text(), 'TEST')
|
||||
it('renders token balance and symbol', function () {
|
||||
assert.equal(wrapper.find('.list-item__heading').text(), '5.000 TEST ')
|
||||
})
|
||||
|
||||
it('renders converted fiat amount', function () {
|
||||
assert.equal(wrapper.find('.token-cell__fiat-amount').text(), '0.52 USD')
|
||||
assert.equal(wrapper.find('.list-item__subheading').text(), '$0.52 USD')
|
||||
})
|
||||
|
||||
it('calls onClick when clicked', function () {
|
||||
|
@ -1 +1 @@
|
||||
export { default } from './token-list.container'
|
||||
export { default } from './token-list'
|
||||
|
@ -1,148 +0,0 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import TokenTracker from '@metamask/eth-token-tracker'
|
||||
import { isEqual } from 'lodash'
|
||||
import contracts from 'eth-contract-metadata'
|
||||
|
||||
import { I18nContext } from '../../../contexts/i18n'
|
||||
import TokenCell from '../token-cell'
|
||||
|
||||
const defaultTokens = []
|
||||
for (const address in contracts) {
|
||||
const contract = contracts[address]
|
||||
if (contract.erc20) {
|
||||
contract.address = address
|
||||
defaultTokens.push(contract)
|
||||
}
|
||||
}
|
||||
|
||||
class TokenList extends Component {
|
||||
static contextType = I18nContext
|
||||
|
||||
static propTypes = {
|
||||
assetImages: PropTypes.object.isRequired,
|
||||
network: PropTypes.string.isRequired,
|
||||
onTokenClick: PropTypes.func.isRequired,
|
||||
tokens: PropTypes.array.isRequired,
|
||||
userAddress: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
|
||||
this.state = {
|
||||
error: null,
|
||||
tokensLoading: false,
|
||||
tokensWithBalances: [],
|
||||
}
|
||||
}
|
||||
|
||||
constructTokenTracker () {
|
||||
const { network, tokens, userAddress } = this.props
|
||||
if (!tokens || !tokens.length) {
|
||||
this.setState({
|
||||
tokensLoading: false,
|
||||
tokensWithBalances: [],
|
||||
})
|
||||
return
|
||||
}
|
||||
this.setState({ tokensLoading: true })
|
||||
|
||||
if (!userAddress || network === 'loading' || !global.ethereumProvider) {
|
||||
return
|
||||
}
|
||||
|
||||
const updateBalances = (tokensWithBalances) => {
|
||||
this.setState({
|
||||
error: null,
|
||||
tokensLoading: false,
|
||||
tokensWithBalances,
|
||||
})
|
||||
}
|
||||
const showError = (error) => {
|
||||
this.setState({
|
||||
error,
|
||||
tokensLoading: false,
|
||||
})
|
||||
}
|
||||
|
||||
this.tokenTracker = new TokenTracker({
|
||||
userAddress,
|
||||
provider: global.ethereumProvider,
|
||||
tokens: tokens,
|
||||
pollingInterval: 8000,
|
||||
})
|
||||
|
||||
this.tokenTracker.on('update', updateBalances)
|
||||
this.tokenTracker.on('error', showError)
|
||||
this.tokenTracker.updateBalances()
|
||||
}
|
||||
|
||||
stopTokenTracker () {
|
||||
if (this.tokenTracker) {
|
||||
this.tokenTracker.stop()
|
||||
this.tokenTracker.removeAllListeners('update')
|
||||
this.tokenTracker.removeAllListeners('error')
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.constructTokenTracker()
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
const { network, tokens, userAddress } = this.props
|
||||
if (
|
||||
isEqual(tokens, prevProps.tokens) &&
|
||||
userAddress === prevProps.userAddress &&
|
||||
network === prevProps.network
|
||||
) {
|
||||
return
|
||||
}
|
||||
this.stopTokenTracker()
|
||||
this.constructTokenTracker()
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.stopTokenTracker()
|
||||
}
|
||||
|
||||
render () {
|
||||
const t = this.context
|
||||
const { error, tokensLoading, tokensWithBalances } = this.state
|
||||
const { assetImages, network, onTokenClick } = this.props
|
||||
if (network === 'loading' || tokensLoading) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
height: '250px',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '30px',
|
||||
}}
|
||||
>
|
||||
{t('loadingTokens')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{tokensWithBalances.map((tokenData, index) => {
|
||||
tokenData.image = assetImages[tokenData.address]
|
||||
return (
|
||||
<TokenCell
|
||||
key={index}
|
||||
{...tokenData}
|
||||
outdatedBalance={Boolean(error)}
|
||||
onClick={onTokenClick}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default TokenList
|
@ -1,21 +0,0 @@
|
||||
import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import { getSelectedAddress } from '../../../selectors'
|
||||
import TokenList from './token-list.component'
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
network: state.metamask.network,
|
||||
tokens: state.metamask.tokens,
|
||||
userAddress: getSelectedAddress(state),
|
||||
assetImages: state.metamask.assetImages,
|
||||
}
|
||||
}
|
||||
|
||||
const TokenListContainer = connect(mapStateToProps)(TokenList)
|
||||
|
||||
TokenListContainer.propTypes = {
|
||||
onTokenClick: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default TokenListContainer
|
66
ui/app/components/app/token-list/token-list.js
Normal file
66
ui/app/components/app/token-list/token-list.js
Normal file
@ -0,0 +1,66 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import contracts from 'eth-contract-metadata'
|
||||
import { isEqual } from 'lodash'
|
||||
|
||||
import TokenCell from '../token-cell'
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext'
|
||||
import { useTokenTracker } from '../../../hooks/useTokenTracker'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { getAssetImages } from '../../../selectors'
|
||||
import { getTokens } from '../../../ducks/metamask/metamask'
|
||||
|
||||
const defaultTokens = []
|
||||
for (const address in contracts) {
|
||||
const contract = contracts[address]
|
||||
if (contract.erc20) {
|
||||
contract.address = address
|
||||
defaultTokens.push(contract)
|
||||
}
|
||||
}
|
||||
|
||||
export default function TokenList ({ onTokenClick }) {
|
||||
const t = useI18nContext()
|
||||
const assetImages = useSelector(getAssetImages)
|
||||
// use `isEqual` comparison function because the token array is serialized
|
||||
// from the background so it has a new reference with each background update,
|
||||
// even if the tokens haven't changed
|
||||
const tokens = useSelector(getTokens, isEqual)
|
||||
const { loading, error, tokensWithBalances } = useTokenTracker(tokens)
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
height: '250px',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '30px',
|
||||
}}
|
||||
>
|
||||
{t('loadingTokens')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{tokensWithBalances.map((tokenData, index) => {
|
||||
tokenData.image = assetImages[tokenData.address]
|
||||
return (
|
||||
<TokenCell
|
||||
key={index}
|
||||
{...tokenData}
|
||||
outdatedBalance={Boolean(error)}
|
||||
onClick={onTokenClick}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
TokenList.propTypes = {
|
||||
onTokenClick: PropTypes.func.isRequired,
|
||||
}
|
@ -85,6 +85,7 @@ export default class TransactionBreakdown extends PureComponent {
|
||||
? (
|
||||
<CurrencyDisplay
|
||||
className="transaction-breakdown__value"
|
||||
data-testid="transaction-breakdown__gas-price"
|
||||
currency={nativeCurrency}
|
||||
denomination={GWEI}
|
||||
value={gasPrice}
|
||||
|
@ -10,6 +10,8 @@
|
||||
}
|
||||
|
||||
&__secondary-currency {
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
color: $Grey-500;
|
||||
}
|
||||
|
||||
@ -46,5 +48,8 @@
|
||||
white-space: nowrap;
|
||||
line-height: 1rem;
|
||||
}
|
||||
&:empty {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-rows: auto;
|
||||
padding-top: 8px;
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
&__empty-text {
|
||||
|
@ -31,9 +31,8 @@ const TokenOverview = ({ className, token }) => {
|
||||
balance={(
|
||||
<div className="token-overview__balance">
|
||||
<TokenBalance
|
||||
token={token}
|
||||
withSymbol
|
||||
className="token-overview__primary-balance"
|
||||
token={token}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
@ -7,6 +7,7 @@ import { useCurrencyDisplay } from '../../../hooks/useCurrencyDisplay'
|
||||
export default function CurrencyDisplay ({
|
||||
value,
|
||||
displayValue,
|
||||
'data-testid': dataTestId,
|
||||
style,
|
||||
className,
|
||||
prefix,
|
||||
@ -30,6 +31,7 @@ export default function CurrencyDisplay ({
|
||||
return (
|
||||
<div
|
||||
className={classnames('currency-display-component', className)}
|
||||
data-testid={dataTestId}
|
||||
style={style}
|
||||
title={(!hideTitle && title) || null}
|
||||
>
|
||||
@ -49,6 +51,7 @@ export default function CurrencyDisplay ({
|
||||
CurrencyDisplay.propTypes = {
|
||||
className: PropTypes.string,
|
||||
currency: PropTypes.string,
|
||||
'data-testid': PropTypes.string,
|
||||
denomination: PropTypes.oneOf([GWEI]),
|
||||
displayValue: PropTypes.string,
|
||||
hideLabel: PropTypes.bool,
|
||||
|
@ -8,23 +8,28 @@
|
||||
border-top: 1px solid $mercury;
|
||||
border-bottom: 1px solid $mercury;
|
||||
color: $Black-100;
|
||||
display: grid;
|
||||
grid-template-columns: 0fr repeat(11, 1fr);
|
||||
grid-template-areas:
|
||||
'icon head head head head head head head right right right right'
|
||||
'icon sub sub sub sub sub sub sub right right right right'
|
||||
'. actions actions actions actions actions actions actions right right right right';
|
||||
align-items: start;
|
||||
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
|
||||
&__icon > * {
|
||||
margin: 8px 14px 0 0;
|
||||
}
|
||||
|
||||
&__col {
|
||||
align-self: flex-start;
|
||||
&-main {
|
||||
flex-grow: 1;
|
||||
&__icon {
|
||||
grid-area: icon;
|
||||
align-self: center;
|
||||
> * {
|
||||
margin: 0 16px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__actions {
|
||||
grid-area: actions;
|
||||
}
|
||||
|
||||
&__heading {
|
||||
grid-area: head;
|
||||
font-size: 16px;
|
||||
line-height: 160%;
|
||||
position: relative;
|
||||
@ -32,7 +37,6 @@
|
||||
&-wrap {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-left: 8px;
|
||||
@ -40,13 +44,38 @@
|
||||
}
|
||||
|
||||
&__subheading {
|
||||
grid-area: sub;
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
color: $Grey-500;
|
||||
margin-top: 4px;
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__mid-content {
|
||||
grid-area: mid;
|
||||
font-size: 12px;
|
||||
color: $Grey-500;
|
||||
}
|
||||
|
||||
&__right-content {
|
||||
margin: 0 0 0 auto;
|
||||
grid-area: right;
|
||||
text-align: right;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
@media (max-width: 575px) {
|
||||
&__mid-content {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
grid-template-areas:
|
||||
'icon head head head head mid mid mid mid right right right'
|
||||
'icon sub sub sub sub mid mid mid mid right right right'
|
||||
'. actions actions actions actions mid mid mid mid right right right';
|
||||
}
|
||||
}
|
||||
|
@ -2,35 +2,50 @@ import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
|
||||
export default function ListItem ({ title, subtitle, onClick, subtitleStatus, children, titleIcon, icon, rightContent, className }) {
|
||||
export default function ListItem ({
|
||||
title,
|
||||
subtitle,
|
||||
onClick,
|
||||
subtitleStatus,
|
||||
children,
|
||||
titleIcon,
|
||||
icon,
|
||||
rightContent,
|
||||
midContent,
|
||||
className,
|
||||
'data-testid': dataTestId,
|
||||
}) {
|
||||
const primaryClassName = classnames('list-item', className)
|
||||
|
||||
return (
|
||||
<div className={primaryClassName} onClick={onClick}>
|
||||
<div className={primaryClassName} onClick={onClick} data-testid={dataTestId}>
|
||||
{icon && (
|
||||
<div className="list-item__col list-item__icon">
|
||||
<div className="list-item__icon">
|
||||
{icon}
|
||||
</div>
|
||||
)}
|
||||
<div className="list-item__col list-item__col-main">
|
||||
<h2 className="list-item__heading">
|
||||
{ title } {titleIcon && (
|
||||
<span className="list-item__heading-wrap">
|
||||
{titleIcon}
|
||||
</span>
|
||||
)}
|
||||
</h2>
|
||||
<h3 className="list-item__subheading">
|
||||
{subtitleStatus}{subtitle}
|
||||
</h3>
|
||||
{children && (
|
||||
<div className="list-item__more">
|
||||
{ children }
|
||||
</div>
|
||||
<h2 className="list-item__heading">
|
||||
{ title } {titleIcon && (
|
||||
<span className="list-item__heading-wrap">
|
||||
{titleIcon}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</h2>
|
||||
<h3 className="list-item__subheading">
|
||||
{subtitleStatus}{subtitle}
|
||||
</h3>
|
||||
{children && (
|
||||
<div className="list-item__actions">
|
||||
{ children }
|
||||
</div>
|
||||
)}
|
||||
{midContent && (
|
||||
<div className="list-item__mid-content">
|
||||
{midContent}
|
||||
</div>
|
||||
)}
|
||||
{rightContent && (
|
||||
<div className="list-item__col list-item__right-content">
|
||||
<div className="list-item__right-content">
|
||||
{rightContent}
|
||||
</div>
|
||||
)}
|
||||
@ -46,6 +61,8 @@ ListItem.propTypes = {
|
||||
children: PropTypes.node,
|
||||
icon: PropTypes.node,
|
||||
rightContent: PropTypes.node,
|
||||
midContent: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
'data-testid': PropTypes.string,
|
||||
}
|
||||
|
@ -1,16 +1,19 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
|
||||
export default class Tabs extends Component {
|
||||
static defaultProps = {
|
||||
defaultActiveTabName: null,
|
||||
onTabClick: null,
|
||||
tabsClassName: undefined,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
defaultActiveTabName: PropTypes.string,
|
||||
onTabClick: PropTypes.func,
|
||||
children: PropTypes.node.isRequired,
|
||||
tabsClassName: PropTypes.string,
|
||||
}
|
||||
|
||||
state = {
|
||||
@ -62,9 +65,10 @@ export default class Tabs extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { tabsClassName } = this.props
|
||||
return (
|
||||
<div className="tabs">
|
||||
<ul className="tabs__list">
|
||||
<ul className={classnames('tabs__list', tabsClassName)}>
|
||||
{ this.renderTabs() }
|
||||
</ul>
|
||||
<div className="tabs__content">
|
||||
|
@ -1,7 +1,8 @@
|
||||
.toggle-button {
|
||||
display: flex;
|
||||
$self: &;
|
||||
|
||||
&__status-label {
|
||||
&__status {
|
||||
font-family: Roboto;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
@ -10,5 +11,28 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-transform: uppercase;
|
||||
display: grid;
|
||||
}
|
||||
}
|
||||
|
||||
&__label-off, &__label-on {
|
||||
grid-area: 1 / 1 / 1 / 1;
|
||||
}
|
||||
|
||||
&__label-off {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
&__label-on {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
&--off {
|
||||
#{ $self }__label-off {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#{ $self }__label-on {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,8 +48,10 @@ const colors = {
|
||||
const ToggleButton = (props) => {
|
||||
const { value, onToggle, offLabel, onLabel } = props
|
||||
|
||||
const modifier = value ? 'on' : 'off'
|
||||
|
||||
return (
|
||||
<div className="toggle-button">
|
||||
<div className={`toggle-button toggle-button--${modifier}`}>
|
||||
<ReactToggleButton
|
||||
value={value}
|
||||
onToggle={onToggle}
|
||||
@ -60,7 +62,10 @@ const ToggleButton = (props) => {
|
||||
thumbAnimateRange={[3, 18]}
|
||||
colors={colors}
|
||||
/>
|
||||
<div className="toggle-button__status-label">{ value ? onLabel : offLabel }</div>
|
||||
<div className="toggle-button__status">
|
||||
<span className="toggle-button__label-off">{offLabel}</span>
|
||||
<span className="toggle-button__label-on">{onLabel}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
export { default } from './token-balance.container'
|
||||
export { default } from './token-balance'
|
||||
|
@ -1,23 +0,0 @@
|
||||
import React, { PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import CurrencyDisplay from '../currency-display'
|
||||
|
||||
export default class TokenBalance extends PureComponent {
|
||||
static propTypes = {
|
||||
string: PropTypes.string,
|
||||
symbol: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
}
|
||||
|
||||
render () {
|
||||
const { className, string, symbol } = this.props
|
||||
|
||||
return (
|
||||
<CurrencyDisplay
|
||||
className={className}
|
||||
displayValue={string}
|
||||
suffix={symbol}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
import { connect } from 'react-redux'
|
||||
import { compose } from 'redux'
|
||||
import withTokenTracker from '../../../helpers/higher-order-components/with-token-tracker'
|
||||
import TokenBalance from './token-balance.component'
|
||||
import { getSelectedAddress } from '../../../selectors'
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
userAddress: getSelectedAddress(state),
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(
|
||||
connect(mapStateToProps),
|
||||
withTokenTracker
|
||||
)(TokenBalance)
|
30
ui/app/components/ui/token-balance/token-balance.js
Normal file
30
ui/app/components/ui/token-balance/token-balance.js
Normal file
@ -0,0 +1,30 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import CurrencyDisplay from '../currency-display'
|
||||
import { useTokenTracker } from '../../../hooks/useTokenTracker'
|
||||
|
||||
export default function TokenBalance ({ className, token }) {
|
||||
const { tokensWithBalances } = useTokenTracker([token])
|
||||
|
||||
const { string, symbol } = tokensWithBalances[0] || {}
|
||||
return (
|
||||
<CurrencyDisplay
|
||||
className={className}
|
||||
displayValue={string || ''}
|
||||
suffix={symbol || ''}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
TokenBalance.propTypes = {
|
||||
className: PropTypes.string,
|
||||
token: PropTypes.shape({
|
||||
address: PropTypes.string.isRequired,
|
||||
decimals: PropTypes.number,
|
||||
symbol: PropTypes.string,
|
||||
}).isRequired,
|
||||
}
|
||||
|
||||
TokenBalance.defaultProps = {
|
||||
className: undefined,
|
||||
}
|
@ -1 +0,0 @@
|
||||
export { default } from './with-token-tracker.component'
|
@ -1,44 +0,0 @@
|
||||
import React from 'react'
|
||||
import assert from 'assert'
|
||||
import { shallow } from 'enzyme'
|
||||
import withTokenTracker from '../with-token-tracker.component'
|
||||
import TokenBalance from '../../../../components/ui/token-balance/token-balance.component'
|
||||
// import sinon from 'sinon'
|
||||
import TokenTracker from '@metamask/eth-token-tracker'
|
||||
|
||||
const { createTestProviderTools } = require('../../../../../../test/stub/provider')
|
||||
|
||||
const provider = createTestProviderTools({ scaffold: {} }).provider
|
||||
|
||||
describe('WithTokenTracker HOC', function () {
|
||||
let wrapper
|
||||
|
||||
beforeEach(function () {
|
||||
const TokenTracker = withTokenTracker(TokenBalance)
|
||||
wrapper = shallow(
|
||||
<TokenTracker
|
||||
userAddress="0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
|
||||
token={
|
||||
{
|
||||
address: 'test',
|
||||
}
|
||||
}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
it('#setError', function () {
|
||||
wrapper.instance().setError('test')
|
||||
assert.equal(wrapper.props().error, 'test')
|
||||
})
|
||||
|
||||
it('#updateBalance', function () {
|
||||
wrapper.instance().tracker = new TokenTracker({
|
||||
provider,
|
||||
})
|
||||
wrapper.instance().updateBalance([{ string: 'test string', symbol: 'test symbol' }])
|
||||
assert.equal(wrapper.props().string, 'test string')
|
||||
assert.equal(wrapper.props().symbol, 'test symbol')
|
||||
})
|
||||
|
||||
})
|
@ -1,101 +0,0 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import TokenTracker from '@metamask/eth-token-tracker'
|
||||
|
||||
export default function withTokenTracker (WrappedComponent) {
|
||||
return class TokenTrackerWrappedComponent extends Component {
|
||||
static propTypes = {
|
||||
userAddress: PropTypes.string.isRequired,
|
||||
token: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
state = {
|
||||
string: '',
|
||||
symbol: '',
|
||||
balance: '',
|
||||
error: null,
|
||||
}
|
||||
|
||||
tracker = null
|
||||
|
||||
componentDidMount () {
|
||||
this.createFreshTokenTracker()
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
const { userAddress: newAddress, token: { address: newTokenAddress } } = this.props
|
||||
const { userAddress: oldAddress, token: { address: oldTokenAddress } } = prevProps
|
||||
|
||||
if ((oldAddress === newAddress) && (oldTokenAddress === newTokenAddress)) {
|
||||
return
|
||||
}
|
||||
|
||||
if ((!oldAddress || !newAddress) && (!oldTokenAddress || !newTokenAddress)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.createFreshTokenTracker()
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.removeListeners()
|
||||
}
|
||||
|
||||
createFreshTokenTracker () {
|
||||
this.removeListeners()
|
||||
|
||||
if (!global.ethereumProvider) {
|
||||
return
|
||||
}
|
||||
|
||||
const { userAddress, token } = this.props
|
||||
|
||||
this.tracker = new TokenTracker({
|
||||
userAddress,
|
||||
provider: global.ethereumProvider,
|
||||
tokens: [token],
|
||||
pollingInterval: 8000,
|
||||
})
|
||||
|
||||
this.tracker.on('update', this.updateBalance)
|
||||
this.tracker.on('error', this.setError)
|
||||
|
||||
this.tracker.updateBalances()
|
||||
.then(() => this.updateBalance(this.tracker.serialize()))
|
||||
.catch((error) => this.setState({ error: error.message }))
|
||||
}
|
||||
|
||||
setError = (error) => {
|
||||
this.setState({ error })
|
||||
}
|
||||
|
||||
updateBalance = (tokens = []) => {
|
||||
if (!this.tracker.running) {
|
||||
return
|
||||
}
|
||||
const [{ string, symbol, balance }] = tokens
|
||||
this.setState({ string, symbol, error: null, balance })
|
||||
}
|
||||
|
||||
removeListeners () {
|
||||
if (this.tracker) {
|
||||
this.tracker.stop()
|
||||
this.tracker.removeListener('update', this.updateBalance)
|
||||
this.tracker.removeListener('error', this.setError)
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { balance, string, symbol, error } = this.state
|
||||
return (
|
||||
<WrappedComponent
|
||||
{ ...this.props }
|
||||
string={string}
|
||||
symbol={symbol}
|
||||
tokenTrackerBalance={balance}
|
||||
error={error}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ import {
|
||||
TRANSACTION_TYPE_CANCEL,
|
||||
TRANSACTION_STATUS_CONFIRMED,
|
||||
} from '../../../../app/scripts/controllers/transactions/enums'
|
||||
import { MESSAGE_TYPE } from '../../../../app/scripts/lib/enums'
|
||||
import prefixForNetwork from '../../../lib/etherscan-prefix-for-network'
|
||||
import fetchWithCache from './fetch-with-cache'
|
||||
|
||||
@ -137,9 +138,9 @@ export function getTransactionActionKey (transaction) {
|
||||
}
|
||||
|
||||
if (msgParams) {
|
||||
if (type === 'eth_decrypt') {
|
||||
if (type === MESSAGE_TYPE.ETH_DECRYPT) {
|
||||
return DECRYPT_REQUEST_KEY
|
||||
} else if (type === 'eth_getEncryptionPublicKey') {
|
||||
} else if (type === MESSAGE_TYPE.ETH_GET_ENCRYPTION_PUBLIC_KEY) {
|
||||
return ENCRYPTION_PUBLIC_KEY_REQUEST_KEY
|
||||
} else {
|
||||
return SIGNATURE_REQUEST_KEY
|
||||
|
@ -297,3 +297,15 @@ export function isValidAddressHead (address) {
|
||||
export function getAccountByAddress (accounts = [], targetAddress) {
|
||||
return accounts.find(({ address }) => address === targetAddress)
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips the following schemes from URL strings:
|
||||
* - http
|
||||
* - https
|
||||
*
|
||||
* @param {string} urlString - The URL string to strip the scheme from.
|
||||
* @returns {string} The URL string, without the scheme, if it was stripped.
|
||||
*/
|
||||
export function stripHttpSchemes (urlString) {
|
||||
return urlString.replace(/^https?:\/\//u, '')
|
||||
}
|
||||
|
88
ui/app/hooks/useTokenTracker.js
Normal file
88
ui/app/hooks/useTokenTracker.js
Normal file
@ -0,0 +1,88 @@
|
||||
import { useState, useEffect, useRef, useCallback } from 'react'
|
||||
import TokenTracker from '@metamask/eth-token-tracker'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { getCurrentNetwork, getSelectedAddress } from '../selectors'
|
||||
|
||||
|
||||
export function useTokenTracker (tokens) {
|
||||
const network = useSelector(getCurrentNetwork)
|
||||
const userAddress = useSelector(getSelectedAddress)
|
||||
|
||||
const [loading, setLoading] = useState(() => tokens?.length >= 0)
|
||||
const [tokensWithBalances, setTokensWithBalances] = useState([])
|
||||
const [error, setError] = useState(null)
|
||||
const tokenTracker = useRef(null)
|
||||
|
||||
const updateBalances = useCallback((tokensWithBalances) => {
|
||||
setTokensWithBalances(tokensWithBalances)
|
||||
setLoading(false)
|
||||
setError(null)
|
||||
}, [])
|
||||
|
||||
const showError = useCallback((error) => {
|
||||
setError(error)
|
||||
setLoading(false)
|
||||
}, [])
|
||||
|
||||
const teardownTracker = useCallback(() => {
|
||||
if (tokenTracker.current) {
|
||||
tokenTracker.current.stop()
|
||||
tokenTracker.current.removeAllListeners('update')
|
||||
tokenTracker.current.removeAllListeners('error')
|
||||
tokenTracker.current = null
|
||||
}
|
||||
}, [])
|
||||
|
||||
const buildTracker = useCallback((address, tokenList) => {
|
||||
// clear out previous tracker, if it exists.
|
||||
teardownTracker()
|
||||
tokenTracker.current = new TokenTracker({
|
||||
userAddress: address,
|
||||
provider: global.ethereumProvider,
|
||||
tokens: tokenList,
|
||||
pollingInterval: 8000,
|
||||
})
|
||||
|
||||
tokenTracker.current.on('update', updateBalances)
|
||||
tokenTracker.current.on('error', showError)
|
||||
tokenTracker.current.updateBalances()
|
||||
}, [updateBalances, showError, teardownTracker])
|
||||
|
||||
// Effect to remove the tracker when the component is removed from DOM
|
||||
// Do not overload this effect with additional dependencies. teardownTracker
|
||||
// is the only dependency here, which itself has no dependencies and will
|
||||
// never update. The lack of dependencies that change is what confirms
|
||||
// that this effect only runs on mount/unmount
|
||||
useEffect(() => {
|
||||
return teardownTracker
|
||||
}, [teardownTracker])
|
||||
|
||||
|
||||
// Effect to set loading state and initialize tracker when values change
|
||||
useEffect(() => {
|
||||
// This effect will only run initially and when:
|
||||
// 1. network is updated,
|
||||
// 2. userAddress is changed,
|
||||
// 3. token list is updated and not equal to previous list
|
||||
// in any of these scenarios, we should indicate to the user that their token
|
||||
// values are in the process of updating by setting loading state.
|
||||
setLoading(true)
|
||||
|
||||
if (!userAddress || network === 'loading' || !global.ethereumProvider) {
|
||||
// If we do not have enough information to build a TokenTracker, we exit early
|
||||
// When the values above change, the effect will be restarted. We also teardown
|
||||
// tracker because inevitably this effect will run again momentarily.
|
||||
teardownTracker()
|
||||
return
|
||||
}
|
||||
|
||||
if (tokens.length === 0) {
|
||||
// sets loading state to false and token list to empty
|
||||
updateBalances([])
|
||||
}
|
||||
|
||||
buildTracker(userAddress, tokens)
|
||||
}, [userAddress, network, tokens, updateBalances, buildTracker])
|
||||
|
||||
return { loading, tokensWithBalances, error }
|
||||
}
|
@ -315,7 +315,7 @@ class AddToken extends Component {
|
||||
title={this.context.t('addTokens')}
|
||||
tabsComponent={this.renderTabs()}
|
||||
onSubmit={() => this.handleNext()}
|
||||
disabled={this.hasError() || !this.hasSelected()}
|
||||
disabled={Boolean(this.hasError()) || !this.hasSelected()}
|
||||
onCancel={() => {
|
||||
clearPendingTokens()
|
||||
history.push(mostRecentOverviewPage)
|
||||
|
@ -19,11 +19,11 @@
|
||||
.asset-breadcrumb {
|
||||
font-size: 14px;
|
||||
color: $Black-100;
|
||||
background-color: inherit;
|
||||
|
||||
&__chevron {
|
||||
padding: 0 10px 0 2px;
|
||||
font-size: 16px;
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
&__asset {
|
||||
|
@ -3,8 +3,8 @@ import PropTypes from 'prop-types'
|
||||
|
||||
const AssetBreadcrumb = ({ accountName, assetName, onBack }) => {
|
||||
return (
|
||||
<div className="asset-breadcrumb">
|
||||
<button className="fas fa-chevron-left asset-breadcrumb__chevron" data-testid="asset__back" onClick={onBack} />
|
||||
<button className="asset-breadcrumb" onClick={onBack} >
|
||||
<i className="fas fa-chevron-left asset-breadcrumb__chevron" data-testid="asset__back" />
|
||||
<span>
|
||||
{accountName}
|
||||
</span>
|
||||
@ -12,7 +12,7 @@ const AssetBreadcrumb = ({ accountName, assetName, onBack }) => {
|
||||
<span className="asset-breadcrumb__asset">
|
||||
{ assetName }
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,115 +0,0 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import ConfirmTransactionBase from '../confirm-transaction-base'
|
||||
import ConfirmApproveContent from './confirm-approve-content'
|
||||
import { getCustomTxParamsData } from './confirm-approve.util'
|
||||
import {
|
||||
calcTokenAmount,
|
||||
} from '../../helpers/utils/token-util'
|
||||
|
||||
export default class ConfirmApprove extends Component {
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
tokenAddress: PropTypes.string,
|
||||
toAddress: PropTypes.string,
|
||||
tokenAmount: PropTypes.string,
|
||||
tokenSymbol: PropTypes.string,
|
||||
fiatTransactionTotal: PropTypes.string,
|
||||
ethTransactionTotal: PropTypes.string,
|
||||
contractExchangeRate: PropTypes.number,
|
||||
conversionRate: PropTypes.number,
|
||||
currentCurrency: PropTypes.string,
|
||||
showCustomizeGasModal: PropTypes.func,
|
||||
showEditApprovalPermissionModal: PropTypes.func,
|
||||
origin: PropTypes.string,
|
||||
siteImage: PropTypes.string,
|
||||
tokenTrackerBalance: PropTypes.string,
|
||||
data: PropTypes.string,
|
||||
decimals: PropTypes.number,
|
||||
txData: PropTypes.object,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
tokenAmount: '0',
|
||||
}
|
||||
|
||||
state = {
|
||||
customPermissionAmount: '',
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
const { tokenAmount } = this.props
|
||||
|
||||
if (tokenAmount !== prevProps.tokenAmount) {
|
||||
this.setState({ customPermissionAmount: tokenAmount })
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
toAddress,
|
||||
tokenAddress,
|
||||
tokenSymbol,
|
||||
tokenAmount,
|
||||
showCustomizeGasModal,
|
||||
showEditApprovalPermissionModal,
|
||||
origin,
|
||||
siteImage,
|
||||
tokenTrackerBalance,
|
||||
data,
|
||||
decimals,
|
||||
txData,
|
||||
currentCurrency,
|
||||
ethTransactionTotal,
|
||||
fiatTransactionTotal,
|
||||
...restProps
|
||||
} = this.props
|
||||
const { customPermissionAmount } = this.state
|
||||
|
||||
const tokensText = `${Number(tokenAmount)} ${tokenSymbol}`
|
||||
|
||||
const tokenBalance = tokenTrackerBalance
|
||||
? calcTokenAmount(tokenTrackerBalance, decimals).toString(10)
|
||||
: ''
|
||||
|
||||
const customData = customPermissionAmount
|
||||
? getCustomTxParamsData(data, { customPermissionAmount, decimals })
|
||||
: null
|
||||
|
||||
return (
|
||||
<ConfirmTransactionBase
|
||||
toAddress={toAddress}
|
||||
identiconAddress={tokenAddress}
|
||||
showAccountInHeader
|
||||
title={tokensText}
|
||||
contentComponent={(
|
||||
<ConfirmApproveContent
|
||||
decimals={decimals}
|
||||
siteImage={siteImage}
|
||||
setCustomAmount={(newAmount) => {
|
||||
this.setState({ customPermissionAmount: newAmount })
|
||||
}}
|
||||
customTokenAmount={String(customPermissionAmount)}
|
||||
tokenAmount={tokenAmount}
|
||||
origin={origin}
|
||||
tokenSymbol={tokenSymbol}
|
||||
tokenBalance={tokenBalance}
|
||||
showCustomizeGasModal={() => showCustomizeGasModal(txData)}
|
||||
showEditApprovalPermissionModal={showEditApprovalPermissionModal}
|
||||
data={customData || data}
|
||||
toAddress={toAddress}
|
||||
currentCurrency={currentCurrency}
|
||||
ethTransactionTotal={ethTransactionTotal}
|
||||
fiatTransactionTotal={fiatTransactionTotal}
|
||||
/>
|
||||
)}
|
||||
hideSenderToRecipient
|
||||
customTxParamsData={customData}
|
||||
{...restProps}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
import { connect } from 'react-redux'
|
||||
import { compose } from 'redux'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import {
|
||||
contractExchangeRateSelector,
|
||||
transactionFeeSelector,
|
||||
} from '../../selectors'
|
||||
import { getTokens } from '../../ducks/metamask/metamask'
|
||||
import { showModal } from '../../store/actions'
|
||||
import {
|
||||
getTokenData,
|
||||
} from '../../helpers/utils/transactions.util'
|
||||
import withTokenTracker from '../../helpers/higher-order-components/with-token-tracker'
|
||||
import {
|
||||
calcTokenAmount,
|
||||
getTokenToAddress,
|
||||
getTokenValue,
|
||||
} from '../../helpers/utils/token-util'
|
||||
import ConfirmApprove from './confirm-approve.component'
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const { match: { params = {} } } = ownProps
|
||||
const { id: paramsTransactionId } = params
|
||||
const {
|
||||
confirmTransaction,
|
||||
metamask: {
|
||||
currentCurrency,
|
||||
conversionRate,
|
||||
currentNetworkTxList,
|
||||
domainMetadata = {},
|
||||
selectedAddress,
|
||||
},
|
||||
} = state
|
||||
|
||||
const {
|
||||
txData: { id: transactionId, txParams: { to: tokenAddress, data } = {} } = {},
|
||||
} = confirmTransaction
|
||||
|
||||
const transaction = (
|
||||
currentNetworkTxList.find(({ id }) => id === (Number(paramsTransactionId) ||
|
||||
transactionId)) || {}
|
||||
)
|
||||
|
||||
const {
|
||||
ethTransactionTotal,
|
||||
fiatTransactionTotal,
|
||||
} = transactionFeeSelector(state, transaction)
|
||||
const tokens = getTokens(state)
|
||||
const currentToken = tokens && tokens.find(({ address }) => tokenAddress === address)
|
||||
const { decimals, symbol: tokenSymbol } = currentToken || {}
|
||||
|
||||
const tokenData = getTokenData(data)
|
||||
const tokenValue = tokenData && getTokenValue(tokenData.params)
|
||||
const toAddress = tokenData && getTokenToAddress(tokenData.params)
|
||||
const tokenAmount = tokenData && calcTokenAmount(tokenValue, decimals).toString(10)
|
||||
const contractExchangeRate = contractExchangeRateSelector(state)
|
||||
|
||||
const { origin } = transaction
|
||||
const formattedOrigin = origin
|
||||
? origin[0].toUpperCase() + origin.slice(1)
|
||||
: ''
|
||||
|
||||
const { icon: siteImage = '' } = domainMetadata[origin] || {}
|
||||
return {
|
||||
toAddress,
|
||||
tokenAddress,
|
||||
tokenAmount,
|
||||
currentCurrency,
|
||||
conversionRate,
|
||||
contractExchangeRate,
|
||||
fiatTransactionTotal,
|
||||
ethTransactionTotal,
|
||||
tokenSymbol,
|
||||
siteImage,
|
||||
token: { address: tokenAddress },
|
||||
userAddress: selectedAddress,
|
||||
origin: formattedOrigin,
|
||||
data,
|
||||
decimals: Number(decimals),
|
||||
txData: transaction,
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
showCustomizeGasModal: (txData) => dispatch(showModal({ name: 'CUSTOMIZE_GAS', txData })),
|
||||
showEditApprovalPermissionModal: ({
|
||||
customTokenAmount,
|
||||
decimals,
|
||||
origin,
|
||||
setCustomAmount,
|
||||
tokenAmount,
|
||||
tokenBalance,
|
||||
tokenSymbol,
|
||||
}) => dispatch(showModal({
|
||||
name: 'EDIT_APPROVAL_PERMISSION',
|
||||
customTokenAmount,
|
||||
decimals,
|
||||
origin,
|
||||
setCustomAmount,
|
||||
tokenAmount,
|
||||
tokenBalance,
|
||||
tokenSymbol,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withRouter,
|
||||
connect(mapStateToProps, mapDispatchToProps),
|
||||
withTokenTracker,
|
||||
)(ConfirmApprove)
|
||||
|
141
ui/app/pages/confirm-approve/confirm-approve.js
Normal file
141
ui/app/pages/confirm-approve/confirm-approve.js
Normal file
@ -0,0 +1,141 @@
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import ConfirmTransactionBase from '../confirm-transaction-base'
|
||||
import ConfirmApproveContent from './confirm-approve-content'
|
||||
import { getCustomTxParamsData } from './confirm-approve.util'
|
||||
import { showModal } from '../../store/actions'
|
||||
import {
|
||||
getTokenData,
|
||||
} from '../../helpers/utils/transactions.util'
|
||||
import {
|
||||
calcTokenAmount,
|
||||
getTokenToAddress,
|
||||
getTokenValue,
|
||||
} from '../../helpers/utils/token-util'
|
||||
import { useTokenTracker } from '../../hooks/useTokenTracker'
|
||||
import { getTokens } from '../../ducks/metamask/metamask'
|
||||
import {
|
||||
transactionFeeSelector,
|
||||
txDataSelector,
|
||||
} from '../../selectors/confirm-transaction'
|
||||
import { getCurrentCurrency, getDomainMetadata } from '../../selectors/selectors'
|
||||
import { currentNetworkTxListSelector } from '../../selectors/transactions'
|
||||
|
||||
export default function ConfirmApprove () {
|
||||
const dispatch = useDispatch()
|
||||
const { id: paramsTransactionId } = useParams()
|
||||
const {
|
||||
id: transactionId,
|
||||
txParams: {
|
||||
to: tokenAddress,
|
||||
data,
|
||||
} = {},
|
||||
} = useSelector(txDataSelector)
|
||||
|
||||
const currentCurrency = useSelector(getCurrentCurrency)
|
||||
const currentNetworkTxList = useSelector(currentNetworkTxListSelector)
|
||||
const domainMetadata = useSelector(getDomainMetadata)
|
||||
const tokens = useSelector(getTokens)
|
||||
|
||||
const transaction = (
|
||||
currentNetworkTxList.find(({ id }) => id === (Number(paramsTransactionId) || transactionId)) || {}
|
||||
)
|
||||
const {
|
||||
ethTransactionTotal,
|
||||
fiatTransactionTotal,
|
||||
} = useSelector((state) => transactionFeeSelector(state, transaction))
|
||||
|
||||
const currentToken = (tokens && tokens.find(({ address }) => tokenAddress === address)) || { address: tokenAddress }
|
||||
|
||||
const { tokensWithBalances } = useTokenTracker([currentToken])
|
||||
const tokenTrackerBalance = tokensWithBalances[0]?.balance || ''
|
||||
|
||||
const tokenSymbol = currentToken?.symbol
|
||||
const decimals = Number(currentToken?.decimals)
|
||||
const tokenData = getTokenData(data)
|
||||
const tokenValue = tokenData && getTokenValue(tokenData.params)
|
||||
const toAddress = tokenData && getTokenToAddress(tokenData.params)
|
||||
const tokenAmount = tokenData && calcTokenAmount(tokenValue, decimals).toString(10)
|
||||
|
||||
const [customPermissionAmount, setCustomPermissionAmount] = useState('')
|
||||
|
||||
const previousTokenAmount = useRef(tokenAmount)
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (customPermissionAmount && previousTokenAmount.current !== tokenAmount) {
|
||||
setCustomPermissionAmount(tokenAmount)
|
||||
}
|
||||
previousTokenAmount.current = tokenAmount
|
||||
},
|
||||
[customPermissionAmount, tokenAmount]
|
||||
)
|
||||
|
||||
const { origin } = transaction
|
||||
const formattedOrigin = origin
|
||||
? origin[0].toUpperCase() + origin.slice(1)
|
||||
: ''
|
||||
const txData = transaction
|
||||
|
||||
const { icon: siteImage = '' } = domainMetadata[origin] || {}
|
||||
|
||||
const tokensText = `${Number(tokenAmount)} ${tokenSymbol}`
|
||||
const tokenBalance = tokenTrackerBalance
|
||||
? calcTokenAmount(tokenTrackerBalance, decimals).toString(10)
|
||||
: ''
|
||||
const customData = customPermissionAmount
|
||||
? getCustomTxParamsData(data, { customPermissionAmount, decimals })
|
||||
: null
|
||||
|
||||
return (
|
||||
<ConfirmTransactionBase
|
||||
toAddress={toAddress}
|
||||
identiconAddress={tokenAddress}
|
||||
showAccountInHeader
|
||||
title={tokensText}
|
||||
contentComponent={(
|
||||
<ConfirmApproveContent
|
||||
decimals={decimals}
|
||||
siteImage={siteImage}
|
||||
setCustomAmount={setCustomPermissionAmount}
|
||||
customTokenAmount={String(customPermissionAmount)}
|
||||
tokenAmount={tokenAmount}
|
||||
origin={formattedOrigin}
|
||||
tokenSymbol={tokenSymbol}
|
||||
tokenBalance={tokenBalance}
|
||||
showCustomizeGasModal={() => dispatch(showModal({ name: 'CUSTOMIZE_GAS', txData }))}
|
||||
showEditApprovalPermissionModal={
|
||||
({
|
||||
customTokenAmount,
|
||||
decimals,
|
||||
origin,
|
||||
setCustomAmount,
|
||||
tokenAmount,
|
||||
tokenBalance,
|
||||
tokenSymbol,
|
||||
}) => dispatch(
|
||||
showModal({
|
||||
name: 'EDIT_APPROVAL_PERMISSION',
|
||||
customTokenAmount,
|
||||
decimals,
|
||||
origin,
|
||||
setCustomAmount,
|
||||
tokenAmount,
|
||||
tokenBalance,
|
||||
tokenSymbol,
|
||||
})
|
||||
)
|
||||
}
|
||||
data={customData || data}
|
||||
toAddress={toAddress}
|
||||
currentCurrency={currentCurrency}
|
||||
ethTransactionTotal={ethTransactionTotal}
|
||||
fiatTransactionTotal={fiatTransactionTotal}
|
||||
/>
|
||||
)}
|
||||
hideSenderToRecipient
|
||||
customTxParamsData={customData}
|
||||
/>
|
||||
)
|
||||
}
|
@ -1 +1 @@
|
||||
export { default } from './confirm-approve.container'
|
||||
export { default } from './confirm-approve'
|
||||
|
@ -21,6 +21,7 @@ import {
|
||||
DEPLOY_CONTRACT_ACTION_KEY,
|
||||
SEND_ETHER_ACTION_KEY,
|
||||
} from '../../helpers/constants/transactions'
|
||||
import { MESSAGE_TYPE } from '../../../../app/scripts/lib/enums'
|
||||
|
||||
export default class ConfirmTransactionSwitch extends Component {
|
||||
static propTypes = {
|
||||
@ -74,9 +75,9 @@ export default class ConfirmTransactionSwitch extends Component {
|
||||
return this.redirectToTransaction()
|
||||
} else if (txData.msgParams) {
|
||||
let pathname = `${CONFIRM_TRANSACTION_ROUTE}/${txData.id}${SIGNATURE_REQUEST_PATH}`
|
||||
if (txData.type === 'eth_decrypt') {
|
||||
if (txData.type === MESSAGE_TYPE.ETH_DECRYPT) {
|
||||
pathname = `${CONFIRM_TRANSACTION_ROUTE}/${txData.id}${DECRYPT_MESSAGE_REQUEST_PATH}`
|
||||
} else if (txData.type === 'eth_getEncryptionPublicKey') {
|
||||
} else if (txData.type === MESSAGE_TYPE.ETH_GET_ENCRYPTION_PUBLIC_KEY) {
|
||||
pathname = `${CONFIRM_TRANSACTION_ROUTE}/${txData.id}${ENCRYPTION_PUBLIC_KEY_REQUEST_PATH}`
|
||||
}
|
||||
return <Redirect to={{ pathname }} />
|
||||
|
@ -11,6 +11,7 @@ import SignatureRequest from '../../components/app/signature-request'
|
||||
import SignatureRequestOriginal from '../../components/app/signature-request-original'
|
||||
import Loading from '../../components/ui/loading-screen'
|
||||
import { getMostRecentOverviewPage } from '../../ducks/history/history'
|
||||
import { MESSAGE_TYPE } from '../../../../app/scripts/lib/enums'
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { metamask, appState } = state
|
||||
@ -111,7 +112,7 @@ class ConfirmTxScreen extends Component {
|
||||
|
||||
signatureSelect (type, version) {
|
||||
// Temporarily direct only v3 and v4 requests to new code.
|
||||
if (type === 'eth_signTypedData' && (version === 'V3' || version === 'V4')) {
|
||||
if (type === MESSAGE_TYPE.ETH_SIGN_TYPED_DATA && (version === 'V3' || version === 'V4')) {
|
||||
return SignatureRequest
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,7 @@ export default class ConnectedAccounts extends PureComponent {
|
||||
|
||||
return (
|
||||
<Popover
|
||||
title={isActiveTabExtension ? t('currentExtension') : activeTabOrigin}
|
||||
title={isActiveTabExtension ? t('currentExtension') : new URL(activeTabOrigin).host}
|
||||
subtitle={connectedAccounts.length ? connectedAccountsDescription : t('connectedAccountsEmptyDescription')}
|
||||
onClose={() => history.push(mostRecentOverviewPage)}
|
||||
footerClassName="connected-accounts__footer"
|
||||
|
@ -17,6 +17,7 @@ export default class ConnectedSites extends Component {
|
||||
accountLabel: PropTypes.string.isRequired,
|
||||
closePopover: PropTypes.func.isRequired,
|
||||
connectedDomains: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
domainHostCount: PropTypes.objectOf(PropTypes.number).isRequired,
|
||||
disconnectAllAccounts: PropTypes.func.isRequired,
|
||||
disconnectAccount: PropTypes.func.isRequired,
|
||||
getOpenMetamaskTabsIds: PropTypes.func.isRequired,
|
||||
@ -69,6 +70,7 @@ export default class ConnectedSites extends Component {
|
||||
renderConnectedSitesList () {
|
||||
return (
|
||||
<ConnectedSitesList
|
||||
domainHostCount={this.props.domainHostCount}
|
||||
connectedDomains={this.props.connectedDomains}
|
||||
onDisconnect={this.setPendingDisconnect}
|
||||
/>
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
getCurrentAccountWithSendEtherInfo,
|
||||
getOriginOfCurrentTab,
|
||||
getPermissionDomains,
|
||||
getPermissionsMetadataHostCounts,
|
||||
getPermittedAccountsByOrigin,
|
||||
getSelectedAddress,
|
||||
} from '../../selectors'
|
||||
@ -40,6 +41,7 @@ const mapStateToProps = (state) => {
|
||||
accountLabel: getCurrentAccountWithSendEtherInfo(state).name,
|
||||
connectedDomains,
|
||||
domains: getPermissionDomains(state),
|
||||
domainHostCount: getPermissionsMetadataHostCounts(state),
|
||||
mostRecentOverviewPage: getMostRecentOverviewPage(state),
|
||||
permittedAccountsByOrigin,
|
||||
selectedAddress,
|
||||
|
@ -31,23 +31,7 @@ export default class ImportWithSeedPhrase extends PureComponent {
|
||||
termsChecked: false,
|
||||
}
|
||||
|
||||
parseSeedPhrase = (seedPhrase) => {
|
||||
if (!seedPhrase) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const trimmed = seedPhrase.trim()
|
||||
if (!trimmed) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const words = trimmed.toLowerCase().match(/\w+/g)
|
||||
if (!words) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return words.join(' ')
|
||||
}
|
||||
parseSeedPhrase = (seedPhrase) => (seedPhrase || '').trim().toLowerCase().match(/\w+/gu)?.join(' ') || ''
|
||||
|
||||
UNSAFE_componentWillMount () {
|
||||
this._onBeforeUnload = () => this.context.metricsEvent({
|
||||
@ -73,7 +57,7 @@ export default class ImportWithSeedPhrase extends PureComponent {
|
||||
|
||||
if (seedPhrase) {
|
||||
const parsedSeedPhrase = this.parseSeedPhrase(seedPhrase)
|
||||
const wordCount = parsedSeedPhrase.split(new RegExp('\\s')).length
|
||||
const wordCount = parsedSeedPhrase.split(/\s/u).length
|
||||
if (wordCount % 3 !== 0 || wordCount > 24 || wordCount < 12) {
|
||||
seedPhraseError = this.context.t('seedPhraseReq')
|
||||
} else if (!validateMnemonic(parsedSeedPhrase)) {
|
||||
@ -284,16 +268,19 @@ export default class ImportWithSeedPhrase extends PureComponent {
|
||||
{termsChecked ? <i className="fa fa-check fa-2x" /> : null}
|
||||
</div>
|
||||
<span id="ftf-chk1-label" className="first-time-flow__checkbox-label">
|
||||
I have read and agree to the
|
||||
<a
|
||||
href="https://metamask.io/terms.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span className="first-time-flow__link-text">
|
||||
{ t('terms') }
|
||||
</span>
|
||||
</a>
|
||||
{t('acceptTermsOfUse', [(
|
||||
<a
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
key="first-time-flow__link-text"
|
||||
href="https://metamask.io/terms.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span className="first-time-flow__link-text">
|
||||
{ t('terms') }
|
||||
</span>
|
||||
</a>
|
||||
)])}
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
|
@ -204,16 +204,19 @@ export default class NewAccount extends PureComponent {
|
||||
{termsChecked ? <i className="fa fa-check fa-2x" /> : null}
|
||||
</div>
|
||||
<span id="ftf-chk1-label" className="first-time-flow__checkbox-label">
|
||||
I have read and agree to the
|
||||
<a
|
||||
href="https://metamask.io/terms.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span className="first-time-flow__link-text">
|
||||
{ t('terms') }
|
||||
</span>
|
||||
</a>
|
||||
{t('acceptTermsOfUse', [(
|
||||
<a
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
key="first-time-flow__link-text"
|
||||
href="https://metamask.io/terms.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span className="first-time-flow__link-text">
|
||||
{ t('terms') }
|
||||
</span>
|
||||
</a>
|
||||
)])}
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
|
@ -232,7 +232,7 @@ export default class Home extends PureComponent {
|
||||
<div className="home__balance-wrapper">
|
||||
<EthOverview />
|
||||
</div>
|
||||
<Tabs defaultActiveTabName={defaultHomeActiveTabName} onTabClick={onTabClick}>
|
||||
<Tabs defaultActiveTabName={defaultHomeActiveTabName} onTabClick={onTabClick} tabsClassName="home__tabs">
|
||||
<Tab
|
||||
activeClassName="home__tab--active"
|
||||
className="home__tab"
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user