Merge remote-tracking branch 'origin/develop' into Version-v8.0.0

* origin/develop:
  Fix signing method bugs (#8833)
  replace icons with Checkbox component (#8830)
  Use gulp-cli@2.3.0 (#8845)
  Use node-sass@4.14.1 (#8844)
  Call getMethodDataAsync when knownMethodData[fourBytePrefix] object is empty (#8836)
  Update connected status popover content (#8834)
  Use @metamask/controllers@2.0.1 (#8832)
  ParseInt nextworkNextNonce correction (#8827)
  Fix first time onboarding popup position (#8829)
  fix overflowing contract names and origins (#8823)
  Hide 'Expand view' button in fullscreen (#8826)
feature/default_network_editable
Mark Stacey 4 years ago
commit cee773e1b5
  1. 7
      app/_locales/en/messages.json
  2. 1
      app/scripts/controllers/permissions/enums.js
  3. 11
      app/scripts/controllers/transactions/lib/util.js
  4. 2
      app/scripts/controllers/transactions/pending-tx-tracker.js
  5. 1
      app/scripts/lib/encryption-public-key-manager.js
  6. 38
      app/scripts/lib/typed-message-manager.js
  7. 6
      package.json
  8. 2
      test/e2e/metamask-responsive-ui.spec.js
  9. 68
      test/unit/app/controllers/metamask-controller-test.js
  10. 13
      test/unit/app/controllers/transactions/pending-tx-tracker-test.js
  11. 2
      ui/app/components/app/asset-list-item/asset-list-item.js
  12. 4
      ui/app/components/app/connected-accounts-permissions/connected-accounts-permissions.component.js
  13. 10
      ui/app/components/app/connected-accounts-permissions/index.scss
  14. 28
      ui/app/components/app/menu-bar/account-options-menu.js
  15. 10
      ui/app/components/app/permission-page-container/index.scss
  16. 16
      ui/app/components/app/permission-page-container/permission-page-container-content/permission-page-container-content.component.js
  17. 2
      ui/app/components/app/token-cell/token-cell.test.js
  18. 23
      ui/app/components/app/transaction-list-item/index.scss
  19. 23
      ui/app/components/app/transaction-list-item/transaction-list-item.component.js
  20. 10
      ui/app/components/app/transaction-status/tests/transaction-status.component.test.js
  21. 17
      ui/app/components/app/transaction-status/transaction-status.component.js
  22. 4
      ui/app/components/ui/check-box/check-box.component.js
  23. 1
      ui/app/components/ui/check-box/index.scss
  24. 12
      ui/app/components/ui/list-item/index.scss
  25. 19
      ui/app/components/ui/list-item/list-item.component.js
  26. 6
      ui/app/hooks/tests/useTransactionDisplayData.test.js
  27. 35
      ui/app/hooks/useTransactionDisplayData.js
  28. 24
      ui/app/pages/home/home.component.js
  29. 31
      ui/app/pages/home/index.scss
  30. 2
      ui/app/store/actions.js
  31. 87
      yarn.lock

@ -909,10 +909,13 @@
"message": "Message"
},
"metaMaskConnectStatusParagraphOne": {
"message": "This is the new MetaMask Connect status indicator. From here you can easily see and manage sites you’ve connected to with your MetaMask wallet."
"message": "You now have more control over your account connections in MetaMask."
},
"metaMaskConnectStatusParagraphTwo": {
"message": "Click the Connect status to see your connected sites and their permissions."
"message": "The connection status button shows if the website you’re visiting is connected to your currently selected account."
},
"metaMaskConnectStatusParagraphThree": {
"message": "Click it to manage your connected accounts."
},
"metamaskDescription": {
"message": "Connecting you to Ethereum and the Decentralized Web."

@ -75,6 +75,7 @@ export const SAFE_METHODS = [
'eth_sendTransaction',
'eth_sign',
'personal_sign',
'personal_ecRecover',
'eth_signTypedData',
'eth_signTypedData_v1',
'eth_signTypedData_v3',

@ -1,8 +1,8 @@
import { addHexPrefix, isValidAddress } from 'ethereumjs-util'
const normalizers = {
from: (from, lowerCase = true) => (lowerCase ? addHexPrefix(from).toLowerCase() : addHexPrefix(from)),
to: (to, lowerCase = true) => (lowerCase ? addHexPrefix(to).toLowerCase() : addHexPrefix(to)),
from: (from) => addHexPrefix(from),
to: (to, lowerCase) => (lowerCase ? addHexPrefix(to).toLowerCase() : addHexPrefix(to)),
nonce: (nonce) => addHexPrefix(nonce),
value: (value) => addHexPrefix(value),
data: (data) => addHexPrefix(data),
@ -12,11 +12,12 @@ const normalizers = {
/**
* Normalizes the given txParams
* @param {Object} txParams - the tx params
* @param {boolean} lowerCase - whether to return the addresses lower cased
* @param {Object} txParams - The transaction params
* @param {boolean} [lowerCase] - Whether to lowercase the 'to' address.
* Default: true
* @returns {Object} the normalized tx params
*/
export function normalizeTxParams (txParams, lowerCase) {
export function normalizeTxParams (txParams, lowerCase = true) {
// apply only keys in the normalizers
const normalizedTxParams = {}
for (const key in normalizers) {

@ -211,7 +211,7 @@ export default class PendingTransactionTracker extends EventEmitter {
const { hash: txHash, txParams: { nonce, from } } = txMeta
const networkNextNonce = await this.query.getTransactionCount(from)
if (parseInt(nonce, 16) >= parseInt(networkNextNonce, 16)) {
if (parseInt(nonce, 16) >= networkNextNonce.toNumber()) {
return false
}

@ -283,5 +283,4 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
this.memStore.updateState({ unapprovedEncryptionPublicKeyMsgs, unapprovedEncryptionPublicKeyMsgCount })
this.emit('updateBadge')
}
}

@ -4,9 +4,11 @@ import createId from './random-id'
import assert from 'assert'
import { ethErrors } from 'eth-json-rpc-errors'
import sigUtil from 'eth-sig-util'
import { isValidAddress } from 'ethereumjs-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.
@ -102,14 +104,15 @@ export default class TypedMessageManager extends EventEmitter {
*
*/
addUnapprovedMessage (msgParams, req, version) {
msgParams.version = version
this.validateParams(msgParams)
// add origin from request
if (req) {
msgParams.origin = req.origin
}
this.validateParams(msgParams)
log.debug(`TypedMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`)
// create txData obj with parameters and meta data
const time = (new Date()).getTime()
const msgId = createId()
@ -134,37 +137,38 @@ export default class TypedMessageManager extends EventEmitter {
*
*/
validateParams (params) {
assert.ok(params && typeof params === 'object', 'Params must be an object.')
assert.ok('data' in params, 'Params must include a "data" field.')
assert.ok('from' in params, 'Params must include a "from" field.')
assert.ok(
typeof params.from === 'string' && isValidAddress(params.from),
'"from" field must be a valid, lowercase, hexadecimal Ethereum address string.'
)
switch (params.version) {
case 'V1':
assert.equal(typeof params, 'object', 'Params should ben an object.')
assert.ok('data' in params, 'Params must include a data field.')
assert.ok('from' in params, 'Params must include a from field.')
assert.ok(Array.isArray(params.data), 'Data should be an array.')
assert.equal(typeof params.from, 'string', 'From field must be a string.')
assert.ok(Array.isArray(params.data), '"params.data" must be an array.')
assert.doesNotThrow(() => {
sigUtil.typedSignatureHash(params.data)
}, 'Expected EIP712 typed data')
}, 'Signing data must be valid EIP-712 typed data.')
break
case 'V3':
case 'V4':
assert.equal(typeof params.data, 'string', '"params.data" must be a string.')
let data
assert.equal(typeof params, 'object', 'Params should be an object.')
assert.ok('data' in params, 'Params must include a data field.')
assert.ok('from' in params, 'Params must include a from field.')
assert.equal(typeof params.from, 'string', 'From field must be a string.')
assert.equal(typeof params.data, 'string', 'Data must be passed as a valid JSON string.')
assert.doesNotThrow(() => {
data = JSON.parse(params.data)
}, 'Data must be passed as a valid JSON string.')
}, '"data" must be a valid JSON string.')
const validation = jsonschema.validate(data, sigUtil.TYPED_MESSAGE_SCHEMA)
assert.ok(data.primaryType in data.types, `Primary type of "${data.primaryType}" has no type definition.`)
assert.equal(validation.errors.length, 0, 'Data must conform to EIP-712 schema. See https://git.io/fNtcx.')
assert.equal(validation.errors.length, 0, 'Signing data must conform to EIP-712 schema. See https://git.io/fNtcx.')
const chainId = data.domain.chainId
const activeChainId = parseInt(this.networkController.getNetworkState())
chainId && assert.equal(chainId, activeChainId, `Provided chainId (${chainId}) must match the active chainId (${activeChainId})`)
chainId && assert.equal(chainId, activeChainId, `Provided chainId "${chainId}" must match the active chainId "${activeChainId}"`)
break
default:
assert.fail(`Unknown params.version ${params.version}`)
assert.fail(`Unknown typed data version "${params.version}"`)
}
}

@ -74,7 +74,7 @@
"@formatjs/intl-relativetimeformat": "^5.2.6",
"@fortawesome/fontawesome-free": "^5.13.0",
"@material-ui/core": "1.0.0",
"@metamask/controllers": "^2.0.0",
"@metamask/controllers": "^2.0.1",
"@metamask/eth-ledger-bridge-keyring": "^0.2.6",
"@metamask/eth-token-tracker": "^2.0.0",
"@metamask/etherscan-link": "^1.1.0",
@ -106,7 +106,7 @@
"eth-json-rpc-errors": "^2.0.2",
"eth-json-rpc-filters": "^4.1.1",
"eth-json-rpc-infura": "^4.0.2",
"eth-json-rpc-middleware": "^4.4.1",
"eth-json-rpc-middleware": "^5.0.0",
"eth-keyring-controller": "^6.0.0",
"eth-method-registry": "^1.2.0",
"eth-phishing-detect": "^1.1.4",
@ -260,7 +260,7 @@
"mocha": "^7.2.0",
"nock": "^9.0.14",
"node-fetch": "^2.6.0",
"node-sass": "^4.12.0",
"node-sass": "^4.14.1",
"nyc": "^15.0.0",
"polyfill-crypto.getrandomvalues": "^1.0.0",
"proxyquire": "^2.1.3",

@ -119,7 +119,7 @@ describe('MetaMask', function () {
it('show account details dropdown menu', async function () {
await driver.clickElement(By.css('[data-testid="account-options-menu-button"]'))
const options = await driver.findElements(By.css('.account-options-menu .menu-item'))
assert.equal(options.length, 4) // HD Wallet type does not have to show the Remove Account option
assert.equal(options.length, 3) // HD Wallet type does not have to show the Remove Account option
// click outside of menu to dismiss
// account menu button chosen because the menu never covers it.
await driver.clickPoint(By.css('.account-menu__icon'), 0, 0)

@ -35,9 +35,33 @@ const ExtensionizerMock = {
},
}
let loggerMiddlewareMock
const initializeMockMiddlewareLog = () => {
loggerMiddlewareMock = {
requests: [],
responses: [],
}
}
const tearDownMockMiddlewareLog = () => {
loggerMiddlewareMock = undefined
}
const createLoggerMiddlewareMock = () => (req, res, next) => {
if (loggerMiddlewareMock) {
loggerMiddlewareMock.requests.push(req)
next((cb) => {
loggerMiddlewareMock.responses.push(res)
cb()
})
} else {
next()
}
}
const MetaMaskController = proxyquire('../../../../app/scripts/metamask-controller', {
'./controllers/threebox': { default: ThreeBoxControllerMock },
'extensionizer': ExtensionizerMock,
'./lib/createLoggerMiddleware': { default: createLoggerMiddlewareMock },
}).default
const currentNetworkId = 42
@ -96,7 +120,6 @@ describe('MetaMaskController', function () {
// add sinon method spies
sandbox.spy(metamaskController.keyringController, 'createNewVaultAndKeychain')
sandbox.spy(metamaskController.keyringController, 'createNewVaultAndRestore')
sandbox.spy(metamaskController.txController, 'newUnapprovedTransaction')
})
afterEach(function () {
@ -776,6 +799,17 @@ describe('MetaMaskController', function () {
})
describe('#setupUntrustedCommunication', function () {
const mockTxParams = { from: TEST_ADDRESS }
beforeEach(function () {
initializeMockMiddlewareLog()
})
after(function () {
tearDownMockMiddlewareLog()
})
it('sets up phishing stream for untrusted communication', async function () {
const phishingMessageSender = {
url: 'http://myethereumwalletntw.com',
@ -815,7 +849,7 @@ describe('MetaMaskController', function () {
const message = {
id: 1999133338649204,
jsonrpc: '2.0',
params: ['mock tx params'],
params: [{ ...mockTxParams }],
method: 'eth_sendTransaction',
}
streamTest.write({
@ -824,15 +858,12 @@ describe('MetaMaskController', function () {
}, null, () => {
setTimeout(() => {
assert.deepStrictEqual(
metamaskController.txController.newUnapprovedTransaction.getCall(0).args,
[
'mock tx params',
{
...message,
origin: 'http://mycrypto.com',
tabId: 456,
},
]
loggerMiddlewareMock.requests[0],
{
...message,
origin: 'http://mycrypto.com',
tabId: 456,
},
)
done()
})
@ -856,7 +887,7 @@ describe('MetaMaskController', function () {
const message = {
id: 1999133338649204,
jsonrpc: '2.0',
params: ['mock tx params'],
params: [{ ...mockTxParams }],
method: 'eth_sendTransaction',
}
streamTest.write({
@ -865,14 +896,11 @@ describe('MetaMaskController', function () {
}, null, () => {
setTimeout(() => {
assert.deepStrictEqual(
metamaskController.txController.newUnapprovedTransaction.getCall(0).args,
[
'mock tx params',
{
...message,
origin: 'http://mycrypto.com',
},
]
loggerMiddlewareMock.requests[0],
{
...message,
origin: 'http://mycrypto.com',
},
)
done()
})

@ -1,5 +1,6 @@
import sinon from 'sinon'
import { strict as assert } from 'assert'
import BN from 'bn.js'
import PendingTransactionTracker from '../../../../../app/scripts/controllers/transactions/pending-tx-tracker'
describe('PendingTransactionTracker', function () {
@ -311,10 +312,11 @@ describe('PendingTransactionTracker', function () {
describe('#_checkIfTxWasDropped', function () {
it('should return true when the given nonce is lower than the network nonce', async function () {
const nonceBN = new BN(2)
const pendingTxTracker = new PendingTransactionTracker({
query: {
getTransactionReceipt: sinon.stub(),
getTransactionCount: sinon.stub().resolves('0x02'),
getTransactionCount: sinon.stub().resolves(nonceBN),
},
nonceTracker: {
getGlobalLock: sinon.stub().resolves({
@ -343,10 +345,11 @@ describe('PendingTransactionTracker', function () {
})
it('should return false when the given nonce is the network nonce', async function () {
const nonceBN = new BN(1)
const pendingTxTracker = new PendingTransactionTracker({
query: {
getTransactionReceipt: sinon.stub(),
getTransactionCount: sinon.stub().resolves('0x01'),
getTransactionCount: sinon.stub().resolves(nonceBN),
},
nonceTracker: {
getGlobalLock: sinon.stub().resolves({
@ -487,10 +490,11 @@ describe('PendingTransactionTracker', function () {
history: [{}],
rawTx: '0xf86c808504a817c80082471d',
}
const nonceBN = new BN(2)
const pendingTxTracker = new PendingTransactionTracker({
query: {
getTransactionReceipt: sinon.stub().rejects(),
getTransactionCount: sinon.stub().resolves('0x02'),
getTransactionCount: sinon.stub().resolves(nonceBN),
},
nonceTracker: {
getGlobalLock: sinon.stub().resolves({
@ -647,6 +651,7 @@ describe('PendingTransactionTracker', function () {
})
it("should emit 'tx:dropped' with the txMetas id only after the fourth call", async function () {
const nonceBN = new BN(2)
const txMeta = {
id: 1,
hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
@ -662,7 +667,7 @@ describe('PendingTransactionTracker', function () {
const pendingTxTracker = new PendingTransactionTracker({
query: {
getTransactionReceipt: sinon.stub().resolves(null),
getTransactionCount: sinon.stub().resolves('0x02'),
getTransactionCount: sinon.stub().resolves(nonceBN),
},
nonceTracker: {
getGlobalLock: sinon.stub().resolves({

@ -97,7 +97,7 @@ const AssetListItem = ({
data-testid={dataTestId}
title={primary}
titleIcon={titleIcon}
subtitle={secondary}
subtitle={<h3>{secondary}</h3>}
onClick={onClick}
icon={(
<Identicon

@ -1,6 +1,7 @@
import classnames from 'classnames'
import PropTypes from 'prop-types'
import React, { PureComponent } from 'react'
import CheckBox from '../../ui/check-box'
export default class ConnectedAccountsPermissions extends PureComponent {
static contextTypes = {
@ -57,7 +58,8 @@ export default class ConnectedAccountsPermissions extends PureComponent {
<ul className="connected-accounts-permissions__list">
{permissions.map(({ key: permissionName }) => (
<li key={permissionName} className="connected-accounts-permissions__list-item">
<i className="fas fa-check-square" />{t(permissionName)}
<CheckBox checked disabled id={permissionName} className="connected-accounts-permissions__checkbox" />
<label htmlFor={permissionName}>{t(permissionName)}</label>
</li>
))}
</ul>

@ -41,13 +41,11 @@
&__list-item {
display: flex;
}
i {
display: block;
padding-right: 8px;
font-size: 18px;
color: $Grey-800;
}
& &__checkbox {
margin: 0 8px 0 0;
font-size: 18px;
}
&__list-container {

@ -10,6 +10,8 @@ import genAccountLink from '../../../../lib/account-link'
import { getCurrentKeyring, getCurrentNetwork, getRpcPrefsForCurrentProvider, getSelectedIdentity } from '../../../selectors'
import { useI18nContext } from '../../../hooks/useI18nContext'
import { useMetricEvent } from '../../../hooks/useMetricEvent'
import { getEnvironmentType } from '../../../../../app/scripts/lib/util'
import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../../app/scripts/lib/enums'
export default function AccountOptionsMenu ({ anchorElement, onClose }) {
const t = useI18nContext()
@ -58,16 +60,22 @@ export default function AccountOptionsMenu ({ anchorElement, onClose }) {
className="account-options-menu"
onHide={onClose}
>
<MenuItem
onClick={() => {
openFullscreenEvent()
global.platform.openExtensionInBrowser()
onClose()
}}
iconClassName="fas fa-expand-alt"
>
{ t('expandView') }
</MenuItem>
{
getEnvironmentType() === ENVIRONMENT_TYPE_FULLSCREEN
? null
: (
<MenuItem
onClick={() => {
openFullscreenEvent()
global.platform.openExtensionInBrowser()
onClose()
}}
iconClassName="fas fa-expand-alt"
>
{ t('expandView') }
</MenuItem>
)
}
<MenuItem
data-testid="account-options-menu__account-details"
onClick={() => {

@ -62,11 +62,6 @@
display: flex;
align-items: center;
i {
font-size: 1.4rem;
color: $Grey-200;
}
label {
font-size: 14px;
margin-left: 16px;
@ -75,6 +70,11 @@
}
}
& &__checkbox {
font-size: 1.4rem;
margin: 0;
}
&__content-container {
display: flex;
flex-direction: column;

@ -2,6 +2,7 @@ import PropTypes from 'prop-types'
import React, { PureComponent } from 'react'
import PermissionsConnectHeader from '../../permissions-connect-header'
import Tooltip from '../../../ui/tooltip-v2'
import CheckBox from '../../../ui/check-box'
export default class PermissionPageContainerContent extends PureComponent {
@ -33,6 +34,8 @@ export default class PermissionPageContainerContent extends PureComponent {
const description = t(permissionName)
// don't allow deselecting eth_accounts
const isDisabled = permissionName === 'eth_accounts'
const isChecked = Boolean(selectedPermissions[permissionName])
const title = isChecked ? t('permissionCheckedIconDescription') : t('permissionUncheckedIconDescription')
return (
<div
@ -44,11 +47,14 @@ export default class PermissionPageContainerContent extends PureComponent {
}
}}
>
{ selectedPermissions[permissionName]
? <i title={t('permissionCheckedIconDescription')} className="fa fa-check-square" />
: <i title={t('permissionUncheckedIconDescription')} className="fa fa-square" />
}
<label>{description}</label>
<CheckBox
disabled={isDisabled}
id={permissionName}
className="permission-approval-container__checkbox"
checked={isChecked}
title={title}
/>
<label htmlFor={permissionName}>{description}</label>
</div>
)
})

@ -69,7 +69,7 @@ describe('Token Cell', function () {
})
it('renders token balance and symbol', function () {
assert.equal(wrapper.find('.list-item__heading').text(), '5.000 TEST ')
assert.equal(wrapper.find('.list-item__heading').text(), '5.000 TEST')
})
it('renders converted fiat amount', function () {

@ -31,4 +31,27 @@
padding-top: 0;
}
}
.list-item__subheading > h3 {
overflow: visible;
display: flex;
white-space: nowrap;
text-overflow: initial;
}
.transaction-status:after {
content: "·";
margin: 0 4px;
}
&__origin, &__address {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&__origin {
/*rtl:ignore*/
direction: rtl;
}
}

@ -40,6 +40,7 @@ export default function TransactionListItem ({ transactionGroup, isEarliestNonce
const {
title,
subtitle,
subtitleContainsOrigin,
date,
category,
primaryCurrency,
@ -122,15 +123,19 @@ export default function TransactionListItem ({ transactionGroup, isEarliestNonce
/>
)}
icon={<TransactionIcon category={category} status={status} />}
subtitle={subtitle}
subtitleStatus={(
<TransactionStatus
isPending={isPending}
isEarliestNonce={isEarliestNonce}
error={err}
date={date}
status={status}
/>
subtitle={(
<h3>
<TransactionStatus
isPending={isPending}
isEarliestNonce={isEarliestNonce}
error={err}
date={date}
status={status}
/>
<span className={subtitleContainsOrigin ? 'transaction-list-item__origin' : 'transaction-list-item__address'}>
{subtitle}
</span>
</h3>
)}
rightContent={!isSignatureReq && (
<>

@ -20,7 +20,7 @@ describe('TransactionStatus Component', function () {
)
assert.ok(wrapper)
assert.equal(wrapper.text(), 'June 1 · ')
assert.equal(wrapper.text(), 'June 1')
})
it('should render PENDING properly when status is APPROVED', function () {
@ -33,7 +33,7 @@ describe('TransactionStatus Component', function () {
)
assert.ok(wrapper)
assert.equal(wrapper.text(), 'PENDING · ')
assert.equal(wrapper.text(), 'PENDING')
assert.equal(wrapper.find(Tooltip).props().title, 'test-title')
})
@ -47,7 +47,7 @@ describe('TransactionStatus Component', function () {
)
assert.ok(wrapper)
assert.equal(wrapper.text(), 'PENDING · ')
assert.equal(wrapper.text(), 'PENDING')
})
it('should render QUEUED properly', function () {
@ -59,7 +59,7 @@ describe('TransactionStatus Component', function () {
assert.ok(wrapper)
assert.ok(wrapper.find('.transaction-status--queued').length, 'queued className not found')
assert.equal(wrapper.text(), 'QUEUED · ')
assert.equal(wrapper.text(), 'QUEUED')
})
it('should render UNAPPROVED properly', function () {
@ -71,7 +71,7 @@ describe('TransactionStatus Component', function () {
assert.ok(wrapper)
assert.ok(wrapper.find('.transaction-status--unapproved').length, 'unapproved className not found')
assert.equal(wrapper.text(), 'UNAPPROVED · ')
assert.equal(wrapper.text(), 'UNAPPROVED')
})
after(function () {

@ -56,16 +56,13 @@ export default function TransactionStatus ({ status, date, error, isEarliestNonc
const statusText = statusKey === CONFIRMED_STATUS ? date : t(statusKey)
return (
<span>
<Tooltip
position="top"
title={tooltipText}
wrapperClassName={classnames('transaction-status', className, statusToClassNameHash[statusKey])}
>
{ statusText }
</Tooltip>
{' · '}
</span>
<Tooltip
position="top"
title={tooltipText}
wrapperClassName={classnames('transaction-status', className, statusToClassNameHash[statusKey])}
>
{ statusText }
</Tooltip>
)
}

@ -10,7 +10,7 @@ const CHECKBOX_STATE = {
export const { CHECKED, INDETERMINATE, UNCHECKED } = CHECKBOX_STATE
const CheckBox = ({ className, disabled, id, onClick, checked }) => {
const CheckBox = ({ className, disabled, id, onClick, checked, title }) => {
if (typeof checked === 'boolean') {
checked = checked
? CHECKBOX_STATE.CHECKED
@ -41,6 +41,7 @@ const CheckBox = ({ className, disabled, id, onClick, checked }) => {
}
readOnly
ref={ref}
title={title}
type="checkbox"
/>
)
@ -52,6 +53,7 @@ CheckBox.propTypes = {
id: PropTypes.string,
onClick: PropTypes.func,
checked: PropTypes.oneOf([...Object.keys(CHECKBOX_STATE), true, false]).isRequired,
title: PropTypes.string,
}
CheckBox.defaultProps = {

@ -18,6 +18,5 @@
&:disabled {
color: $Grey-100;
cursor: not-allowed;
opacity: 0.5;
}
}

@ -41,6 +41,11 @@
display: flex;
align-items: center;
> h2 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&-wrap {
display: inline-block;
margin-left: 8px;
@ -53,6 +58,13 @@
line-height: 14px;
color: $Grey-500;
margin-top: 4px;
// all direct descendants should be truncated with ellipses
// allows flexibility in consuming components to use h3/other tag
> * {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&:empty {
display: none;
}

@ -6,7 +6,6 @@ export default function ListItem ({
title,
subtitle,
onClick,
subtitleStatus,
children,
titleIcon,
icon,
@ -24,16 +23,19 @@ export default function ListItem ({
{icon}
</div>
)}
<h2 className="list-item__heading">
{ title } {titleIcon && (
<div className="list-item__heading">
<h2>{ title }</h2>
{titleIcon && (
<div className="list-item__heading-wrap">
{titleIcon}
</div>
)}
</h2>
<h3 className="list-item__subheading">
{subtitleStatus}{subtitle}
</h3>
</div>
{subtitle && (
<div className="list-item__subheading">
{subtitle}
</div>
)}
{children && (
<div className="list-item__actions">
{ children }
@ -56,8 +58,7 @@ export default function ListItem ({
ListItem.propTypes = {
title: PropTypes.string.isRequired,
titleIcon: PropTypes.node,
subtitle: PropTypes.string,
subtitleStatus: PropTypes.node,
subtitle: PropTypes.node,
children: PropTypes.node,
icon: PropTypes.node,
rightContent: PropTypes.node,

@ -16,6 +16,7 @@ const expectedResults = [
{ title: 'Send ETH',
category: 'send',
subtitle: 'To: 0xffe5...1a97',
subtitleContainsOrigin: false,
date: 'May 12',
primaryCurrency: '-1 ETH',
senderAddress: '0x9eca64466f257793eaa52fcfff5066894b76a149',
@ -26,6 +27,7 @@ const expectedResults = [
{ title: 'Send ETH',
category: 'send',
subtitle: 'To: 0x0ccc...8848',
subtitleContainsOrigin: false,
date: 'May 12',
primaryCurrency: '-2 ETH',
senderAddress: '0x9eca64466f257793eaa52fcfff5066894b76a149',
@ -36,6 +38,7 @@ const expectedResults = [
{ title: 'Send ETH',
category: 'send',
subtitle: 'To: 0xffe5...1a97',
subtitleContainsOrigin: false,
date: 'May 12',
primaryCurrency: '-2 ETH',
senderAddress: '0x9eca64466f257793eaa52fcfff5066894b76a149',
@ -46,6 +49,7 @@ const expectedResults = [
{ title: 'Receive',
category: 'receive',
subtitle: 'From: 0x31b9...4523',
subtitleContainsOrigin: false,
date: 'May 12',
primaryCurrency: '18.75 ETH',
senderAddress: '0x31b98d14007bdee637298086988a0bbd31184523',
@ -56,6 +60,7 @@ const expectedResults = [
{ title: 'Receive',
category: 'receive',
subtitle: 'From: 0x9eca...a149',
subtitleContainsOrigin: false,
date: 'May 8',
primaryCurrency: '0 ETH',
senderAddress: '0x9eca64466f257793eaa52fcfff5066894b76a149',
@ -66,6 +71,7 @@ const expectedResults = [
{ title: 'Receive',
category: 'receive',
subtitle: 'From: 0xee01...febb',
subtitleContainsOrigin: false,
date: 'May 24',
primaryCurrency: '1 ETH',
senderAddress: '0xee014609ef9e09776ac5fe00bdbfef57bcdefebb',

@ -7,7 +7,7 @@ import { useTokenFiatAmount } from './useTokenFiatAmount'
import { PRIMARY, SECONDARY } from '../helpers/constants/common'
import { getTokenToAddress } from '../helpers/utils/token-util'
import { useUserPreferencedCurrency } from './useUserPreferencedCurrency'
import { formatDateWithYearContext, shortenAddress } from '../helpers/utils/util'
import { formatDateWithYearContext, shortenAddress, stripHttpSchemes } from '../helpers/utils/util'
import {
CONTRACT_INTERACTION_KEY,
DEPLOY_CONTRACT_ACTION_KEY,
@ -31,14 +31,15 @@ import { getTokens } from '../ducks/metamask/metamask'
/**
* @typedef {Object} TransactionDisplayData
* @property {string} title - primary description of the transaction
* @property {string} subtitle - supporting text describing the transaction
* @property {string} category - the transaction category
* @property {string} primaryCurrency - the currency string to display in the primary position
* @property {string} [secondaryCurrency] - the currency string to display in the secondary position
* @property {string} status - the status of the transaction
* @property {string} senderAddress - the Ethereum address of the sender
* @property {string} recipientAddress - the Ethereum address of the recipient
* @property {string} title - primary description of the transaction
* @property {string} subtitle - supporting text describing the transaction
* @property {bool} subtitleContainsOrigin - true if the subtitle includes the origin of the tx
* @property {string} category - the transaction category
* @property {string} primaryCurrency - the currency string to display in the primary position
* @property {string} [secondaryCurrency] - the currency string to display in the secondary position
* @property {string} status - the status of the transaction
* @property {string} senderAddress - the Ethereum address of the sender
* @property {string} recipientAddress - the Ethereum address of the recipient
*/
/**
@ -70,6 +71,7 @@ export function useTransactionDisplayData (transactionGroup) {
let prefix = '-'
const date = formatDateWithYearContext(initialTransaction.time || 0)
let subtitle
let subtitleContainsOrigin = false
let recipientAddress = to
// This value is used to determine whether we should look inside txParams.data
@ -87,27 +89,31 @@ export function useTransactionDisplayData (transactionGroup) {
const tokenDisplayValue = useTokenDisplayValue(initialTransaction?.txParams?.data, token, isTokenCategory)
const tokenFiatAmount = useTokenFiatAmount(token?.address, tokenDisplayValue, token?.symbol)
const origin = stripHttpSchemes(initialTransaction.origin || initialTransaction.msgParams?.origin || '')
let category
let title
// There are four types of transaction entries that are currently differentiated in the design
// 1. (PENDING DESIGN) signature request
// 1. signature request
// 2. Send (sendEth sendTokens)
// 3. Deposit
// 4. Site interaction
// 5. Approval
if (transactionCategory == null) {
const origin = initialTransaction.msgParams?.origin || initialTransaction.origin
category = TRANSACTION_CATEGORY_SIGNATURE_REQUEST
title = t('signatureRequest')
subtitle = origin || ''
subtitle = origin
subtitleContainsOrigin = true
} else if (transactionCategory === TOKEN_METHOD_APPROVE) {
category = TRANSACTION_CATEGORY_APPROVAL
title = t('approve')
subtitle = initialTransaction.origin
subtitle = origin
subtitleContainsOrigin = true
} else if (transactionCategory === DEPLOY_CONTRACT_ACTION_KEY || transactionCategory === CONTRACT_INTERACTION_KEY) {
category = TRANSACTION_CATEGORY_INTERACTION
title = (methodData?.name && camelCaseToCapitalize(methodData.name)) || (actionKey && t(actionKey)) || ''
subtitle = initialTransaction.origin
subtitle = origin
subtitleContainsOrigin = true
} else if (transactionCategory === INCOMING_TRANSACTION) {
category = TRANSACTION_CATEGORY_RECEIVE
title = t('receive')
@ -146,6 +152,7 @@ export function useTransactionDisplayData (transactionGroup) {
category,
date,
subtitle,
subtitleContainsOrigin,
primaryCurrency,
senderAddress,
recipientAddress,

@ -25,6 +25,8 @@ import {
CONNECTED_ACCOUNTS_ROUTE,
} from '../../helpers/constants/routes'
const LEARN_MORE_URL = 'https://metamask.zendesk.com/hc/en-us/articles/360045129011-Intro-to-MetaMask-v8-extension'
export default class Home extends PureComponent {
static contextTypes = {
t: PropTypes.func,
@ -187,17 +189,27 @@ export default class Home extends PureComponent {
)
}}
footer={(
<Button
type="primary"
onClick={setConnectedStatusPopoverHasBeenShown}
>
{ t('dismiss') }
</Button>
<>
<a
href={LEARN_MORE_URL}
target="_blank"
rel="noopener noreferrer"
>
{ t('learnMore') }
</a>
<Button
type="primary"
onClick={setConnectedStatusPopoverHasBeenShown}
>
{ t('dismiss') }
</Button>
</>
)}
>
<main className="home__connect-status-text">
<div>{ t('metaMaskConnectStatusParagraphOne') }</div>
<div>{ t('metaMaskConnectStatusParagraphTwo') }</div>
<div>{ t('metaMaskConnectStatusParagraphThree') }</div>
</main>
</Popover>
)

@ -47,19 +47,24 @@
padding-right: 24px;
div {
margin-bottom: 20px;
&:last-child {
margin-top: 26px;
margin-bottom: 0px;
}
}
}
&__connected-status-popover {
width: 329px;
height: 295px;
margin-top: -12px;
margin-top: -15px;
.popover-header {
padding-bottom: 4px;
padding-bottom: 20px;
&__title {
padding-bottom: 0;
}
}
.popover-content {
@ -72,7 +77,11 @@
}
.popover-footer {
justify-content: flex-end;
border-top: 0;
justify-content: space-between;
align-items: center;
padding-top: 20px;
font-size: 14px;
& :only-child {
margin: 0;
@ -84,16 +93,20 @@
border-radius: 39px;
padding: 0;
}
a, a:hover {
color: $dodger-blue;
cursor: pointer;
}
}
}
&__connected-status-popover-bg {
height: 34px;
width: 110px;
height: 55px;
width: 120px;
border-radius: 34px;
position: absolute;
top: 82px;
left: 12px;
top: 73px;
opacity: 1;
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.2);
background: none;

@ -2160,7 +2160,7 @@ export function getContractMethodData (data = '') {
const prefixedData = ethUtil.addHexPrefix(data)
const fourBytePrefix = prefixedData.slice(0, 10)
const { knownMethodData } = getState().metamask
if (knownMethodData && knownMethodData[fourBytePrefix]) {
if (knownMethodData && knownMethodData[fourBytePrefix] && Object.keys(knownMethodData[fourBytePrefix]).length !== 0) {
return Promise.resolve(knownMethodData[fourBytePrefix])
}

@ -1629,10 +1629,10 @@
scroll "^2.0.3"
warning "^3.0.0"
"@metamask/controllers@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-2.0.0.tgz#e57762c213d2722fd178d4c5c288ad98a2ee8bdf"
integrity sha512-J2DSoyyPRAilTdohl43O5tZdqOSMvB94Kx1ApRpkPZxQwtDUIzWHRpTXptyCEwkhaOdkNz9BvWMoaRQxjktfKw==
"@metamask/controllers@^2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-2.0.1.tgz#33b0e2826fb6d4aa582acaff6b347e6abe0f54f5"
integrity sha512-xioh4h+4D2pUSJ9H2CaffxKGmlg0kUK2bbRJ8c9GXPVTo8KhRHryvNKfkVCyoSt35FLROzzwTEdVJ4dmUFuELQ==
dependencies:
await-semaphore "^0.1.3"
eth-contract-metadata "^1.11.0"
@ -10174,7 +10174,7 @@ eth-json-rpc-middleware@^1.5.0:
promise-to-callback "^1.0.0"
tape "^4.6.3"
eth-json-rpc-middleware@^4.1.4, eth-json-rpc-middleware@^4.1.5, eth-json-rpc-middleware@^4.4.1:
eth-json-rpc-middleware@^4.1.4, eth-json-rpc-middleware@^4.1.5:
version "4.4.1"
resolved "https://registry.yarnpkg.com/eth-json-rpc-middleware/-/eth-json-rpc-middleware-4.4.1.tgz#07d3dd0724c24a8d31e4a172ee96271da71b4228"
integrity sha512-yoSuRgEYYGFdVeZg3poWOwAlRI+MoBIltmOB86MtpoZjvLbou9EB/qWMOWSmH2ryCWLW97VYY6NWsmWm3OAA7A==
@ -10194,6 +10194,26 @@ eth-json-rpc-middleware@^4.1.4, eth-json-rpc-middleware@^4.1.5, eth-json-rpc-mid
pify "^3.0.0"
safe-event-emitter "^1.0.1"
eth-json-rpc-middleware@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/eth-json-rpc-middleware/-/eth-json-rpc-middleware-5.0.0.tgz#9a202a41a2b1678c094cbffd0db0da619ec86f7b"
integrity sha512-31zLHvElrQV7opFZs47UqjMGFM4QCgMms7HJSQAZ3wjPuE3rFLxFUTeGHXwM3fAp9zh7n0M+B2gK7Ds4OUKfog==
dependencies:
btoa "^1.2.1"
clone "^2.1.1"
eth-query "^2.1.2"
eth-rpc-errors "^2.1.1"
eth-sig-util "^1.4.2"
ethereumjs-block "^1.6.0"
ethereumjs-tx "^1.3.7"
ethereumjs-util "^5.1.2"
ethereumjs-vm "^2.6.0"
fetch-ponyfill "^4.0.0"
json-rpc-engine "^5.1.3"
json-stable-stringify "^1.0.1"
pify "^3.0.0"
safe-event-emitter "^1.0.1"
eth-keyring-controller@^5.3.0, eth-keyring-controller@^5.6.1:
version "5.6.1"
resolved "https://registry.yarnpkg.com/eth-keyring-controller/-/eth-keyring-controller-5.6.1.tgz#7b7268400704c8f5ce98a055910341177dd207ca"
@ -10275,6 +10295,13 @@ eth-query@^2.0.2, eth-query@^2.1.0, eth-query@^2.1.2:
json-rpc-random-id "^1.0.0"
xtend "^4.0.1"
eth-rpc-errors@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/eth-rpc-errors/-/eth-rpc-errors-2.1.1.tgz#00a7d6c8a9c864a8ab7d0356be20964e5bee4b13"
integrity sha512-MY3zAa5ZF8hvgQu1HOF9agaK5GgigBRGpTJ8H0oVlE0NqMu13CW6syyjLXdeIDCGQTbUeHliU1z9dVmvMKx1Tg==
dependencies:
fast-safe-stringify "^2.0.6"
eth-sig-util@2.3.0, eth-sig-util@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/eth-sig-util/-/eth-sig-util-2.3.0.tgz#c54a6ac8e8796f7e25f59cf436982a930e645231"
@ -13060,9 +13087,9 @@ gulp-babel@^8.0.0:
vinyl-sourcemaps-apply "^0.2.0"
gulp-cli@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/gulp-cli/-/gulp-cli-2.2.0.tgz#5533126eeb7fe415a7e3e84a297d334d5cf70ebc"
integrity sha512-rGs3bVYHdyJpLqR0TUBnlcZ1O5O++Zs4bA0ajm+zr3WFCfiSLjGwoCBqFs18wzN+ZxahT9DkOK5nDf26iDsWjA==
version "2.3.0"
resolved "https://registry.yarnpkg.com/gulp-cli/-/gulp-cli-2.3.0.tgz#ec0d380e29e52aa45e47977f0d32e18fd161122f"
integrity sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==
dependencies:
ansi-colors "^1.0.1"
archy "^1.0.0"
@ -13072,7 +13099,7 @@ gulp-cli@^2.2.0:
copy-props "^2.0.1"
fancy-log "^1.3.2"
gulplog "^1.0.0"
interpret "^1.1.0"
interpret "^1.4.0"
isobject "^3.0.1"
liftoff "^3.1.0"
matchdep "^2.0.0"
@ -13080,7 +13107,7 @@ gulp-cli@^2.2.0:
pretty-hrtime "^1.0.0"
replace-homedir "^1.0.0"
semver-greatest-satisfied-range "^1.1.0"
v8flags "^3.0.1"
v8flags "^3.2.0"
yargs "^7.1.0"
gulp-debug@^3.2.0:
@ -14269,10 +14296,10 @@ internal-slot@^1.0.2:
has "^1.0.3"
side-channel "^1.0.2"
interpret@^1.0.0, interpret@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
integrity sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=
interpret@^1.0.0, interpret@^1.1.0, interpret@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
interpret@^2.0.0:
version "2.0.0"
@ -19345,10 +19372,10 @@ node-releases@^1.1.46:
dependencies:
semver "^6.3.0"
node-sass@^4.12.0, node-sass@^4.8.3:
version "4.13.1"
resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.13.1.tgz#9db5689696bb2eec2c32b98bfea4c7a2e992d0a3"
integrity sha512-TTWFx+ZhyDx1Biiez2nB0L3YrCZ/8oHagaDalbuBSlqXgUPsdkUSzJsVxeDO9LtPB49+Fh3WQl3slABo6AotNw==
node-sass@^4.14.1, node-sass@^4.8.3:
version "4.14.1"
resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.14.1.tgz#99c87ec2efb7047ed638fb4c9db7f3a42e2217b5"
integrity sha512-sjCuOlvGyCJS40R8BscF5vhVlQjNN069NtQ1gSxyK1u9iqvn6tf7O1R4GNowVZfiZUCRt5MmMs1xd+4V/7Yr0g==
dependencies:
async-foreach "^0.1.3"
chalk "^1.1.1"
@ -19364,7 +19391,7 @@ node-sass@^4.12.0, node-sass@^4.8.3:
node-gyp "^3.8.0"
npmlog "^4.0.0"
request "^2.88.0"
sass-graph "^2.2.4"
sass-graph "2.2.5"
stdout-stream "^1.4.0"
"true-case-path" "^1.0.2"
@ -24044,15 +24071,15 @@ sanitize-filename@^1.6.1:
dependencies:
truncate-utf8-bytes "^1.0.0"
sass-graph@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49"
integrity sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=
sass-graph@2.2.5:
version "2.2.5"
resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.5.tgz#a981c87446b8319d96dce0671e487879bd24c2e8"
integrity sha512-VFWDAHOe6mRuT4mZRd4eKE+d8Uedrk6Xnh7Sh9b4NGufQLQjOrvf/MQoOdx+0s92L89FeyUUNfU597j/3uNpag==
dependencies:
glob "^7.0.0"
lodash "^4.0.0"
scss-tokenizer "^0.2.3"
yargs "^7.0.0"
yargs "^13.3.2"
sass-loader@^7.0.1:
version "7.0.1"
@ -27548,10 +27575,10 @@ uuid@^3.3.2, uuid@^3.3.3:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866"
integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==
v8flags@^3.0.1, v8flags@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.1.1.tgz#42259a1461c08397e37fe1d4f1cfb59cad85a053"
integrity sha512-iw/1ViSEaff8NJ3HLyEjawk/8hjJib3E7pvG4pddVXfUg1983s3VGsiClDjhK64MQVDGqc1Q8r18S4VKQZS9EQ==
v8flags@^3.1.1, v8flags@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.2.0.tgz#b243e3b4dfd731fa774e7492128109a0fe66d656"
integrity sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==
dependencies:
homedir-polyfill "^1.0.1"
@ -28795,7 +28822,7 @@ yargs@13.2.4:
y18n "^4.0.0"
yargs-parser "^13.1.0"
yargs@13.3.2, yargs@^13.2.2, yargs@^13.2.4, yargs@^13.3.0:
yargs@13.3.2, yargs@^13.2.2, yargs@^13.2.4, yargs@^13.3.0, yargs@^13.3.2:
version "13.3.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"
integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==
@ -28864,7 +28891,7 @@ yargs@^15.0.0, yargs@^15.0.2:
y18n "^4.0.0"
yargs-parser "^18.1.1"
yargs@^7.0.0, yargs@^7.1.0:
yargs@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8"
integrity sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=

Loading…
Cancel
Save