diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 000000000..041803eae --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,34 @@ +--- +name: Bug Report +about: Using MetaMask, but it's not working as you expect? + +--- + + + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Browser details (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - MetaMask Version [e.g. 4.9.0] + - Old UI or New / Beta UI? + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 000000000..edba657a4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,14 @@ +--- +name: Feature Request +about: Looking for a feature that doesn't exist? Let us know! + +--- + +**What problem are you trying to solve?** +A short description of what you're trying to do. E.g., "My users need to wrap ETH, but they're intimidated by the confirm screen..." or "I'm trying to debug my application, and XYZ..." + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. Try to also include any alternative solutions you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/support-request-or-question.md b/.github/ISSUE_TEMPLATE/support-request-or-question.md new file mode 100644 index 000000000..aeb31af26 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/support-request-or-question.md @@ -0,0 +1,9 @@ +--- +name: Support Request or Question +about: Have a question about how to use MetaMask? + +--- + +FOR USER QUESTIONS, PLEASE DO NOT OPEN A GITHUB ISSUE - IT WILL NOT BE HANDLED HERE. + +INSTEAD, PLEASE EMAIL SUPPORT@METAMASK.IO WITH A DESCRIPTION OF YOUR PROBLEM. diff --git a/CHANGELOG.md b/CHANGELOG.md index ea5d90500..d372c2849 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,28 @@ # Changelog -## Current Master +## Current Develop Branch -- [#4884](https://github.com/MetaMask/metamask-extension/pull/4884): Allow to have tokens per account and network. - -## 4.9.0 Tue Aug 07 2018 +## 4.9.1 Mon Aug 09 2018 -- Add new tokens auto detection -- Remove rejected transactions from transaction history -- Add Trezor Support -- Allow to remove accounts (Imported and Hardware Wallets) +- [#4884](https://github.com/MetaMask/metamask-extension/pull/4884): Allow to have tokens per account and network. +- [#4989](https://github.com/MetaMask/metamask-extension/pull/4989): Continue to use original signedTypedData. +- [#5010](https://github.com/MetaMask/metamask-extension/pull/5010): Fix ENS resolution issues. +- [#5000](https://github.com/MetaMask/metamask-extension/pull/5000): Show error while allowing confirmation of tx where simulation fails. +- [#4995](https://github.com/MetaMask/metamask-extension/pull/4995): Shows retry button on dApp initialized transactions. + +## 4.9.0 Mon Aug 07 2018 + +- [#4926](https://github.com/MetaMask/metamask-extension/pull/4926): Show retry button on the latest tx of the earliest nonce. +- [#4888](https://github.com/MetaMask/metamask-extension/pull/4888): Suggest using the new user interface. +- [#4947](https://github.com/MetaMask/metamask-extension/pull/4947): Prevent sending multiple transasctions on multiple confirm clicks. +- [#4844](https://github.com/MetaMask/metamask-extension/pull/4844): Add new tokens auto detection. +- [#4667](https://github.com/MetaMask/metamask-extension/pull/4667): Remove rejected transactions from transaction history. +- [#4625](https://github.com/MetaMask/metamask-extension/pull/4625): Add Trezor Support. +- [#4625](https://github.com/MetaMask/metamask-extension/pull/4625/commits/523cf9ad33d88719520ae5e7293329d133b64d4d): Allow to remove accounts (Imported and Hardware Wallets) +- [#4814](https://github.com/MetaMask/metamask-extension/pull/4814): Add hex data input to send screen. +- [#4691](https://github.com/MetaMask/metamask-extension/pull/4691): Redesign of the Confirm Transaction Screen. - [#4840](https://github.com/MetaMask/metamask-extension/pull/4840): Now shows notifications when transactions are completed. -- [#4855](https://github.com/MetaMask/metamask-extension/pull/4855): network.js: convert rpc protocol to lower case. +- [#4855](https://github.com/MetaMask/metamask-extension/pull/4855): Allow the use of HTTP prefix for custom rpc urls. ## 4.8.0 Thur Jun 14 2018 diff --git a/app/manifest.json b/app/manifest.json index 3e217943e..53776bd82 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "4.8.0", + "version": "4.9.1", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", diff --git a/app/scripts/background.js b/app/scripts/background.js index 7eb7b1255..3d3afdd4e 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -44,8 +44,8 @@ const notificationManager = new NotificationManager() global.METAMASK_NOTIFIER = notificationManager // setup sentry error reporting -const releaseVersion = platform.getVersion() -const raven = setupRaven({ releaseVersion }) +const release = platform.getVersion() +const raven = setupRaven({ release }) // browser check if it is Edge - https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser // Internet Explorer 6-11 diff --git a/app/scripts/lib/ipfsContent.js b/app/scripts/lib/ipfsContent.js index 5222151ea..5db63f47d 100644 --- a/app/scripts/lib/ipfsContent.js +++ b/app/scripts/lib/ipfsContent.js @@ -5,7 +5,7 @@ module.exports = function (provider) { function ipfsContent (details) { const name = details.url.substring(7, details.url.length - 1) let clearTime = null - extension.tabs.getSelected(null, tab => { + extension.tabs.query({active: true}, tab => { extension.tabs.update(tab.id, { url: 'loading.html' }) clearTime = setTimeout(() => { @@ -34,11 +34,11 @@ module.exports = function (provider) { return { cancel: true } } - extension.webRequest.onBeforeRequest.addListener(ipfsContent, {urls: ['*://*.eth/', '*://*.test/']}) + extension.webRequest.onErrorOccurred.addListener(ipfsContent, {urls: ['*://*.eth/', '*://*.test/']}) return { remove () { - extension.webRequest.onBeforeRequest.removeListener(ipfsContent) + extension.webRequest.onErrorOccurred.removeListener(ipfsContent) }, } } diff --git a/app/scripts/lib/setupRaven.js b/app/scripts/lib/setupRaven.js index e657e278f..3651524f1 100644 --- a/app/scripts/lib/setupRaven.js +++ b/app/scripts/lib/setupRaven.js @@ -8,7 +8,7 @@ module.exports = setupRaven // Setup raven / sentry remote error reporting function setupRaven (opts) { - const { releaseVersion } = opts + const { release } = opts let ravenTarget // detect brave const isBrave = Boolean(window.chrome.ipcRenderer) @@ -22,7 +22,7 @@ function setupRaven (opts) { } const client = Raven.config(ravenTarget, { - releaseVersion, + release, transport: function (opts) { opts.data.extra.isBrave = isBrave const report = opts.data diff --git a/app/scripts/migrations/028.js b/app/scripts/migrations/028.js index 6553a1052..9e995ee1a 100644 --- a/app/scripts/migrations/028.js +++ b/app/scripts/migrations/028.js @@ -25,8 +25,8 @@ function transformState (state) { const newState = state if (newState.PreferencesController) { - if (newState.PreferencesController.tokens) { - const identities = newState.TransactionController.identities + if (newState.PreferencesController.tokens && newState.PreferencesController.identities) { + const identities = newState.PreferencesController.identities const tokens = newState.PreferencesController.tokens newState.PreferencesController.accountTokens = {} for (const identity in identities) { diff --git a/test/integration/lib/tx-list-items.js b/test/integration/lib/tx-list-items.js index b7aca44d5..9075efe03 100644 --- a/test/integration/lib/tx-list-items.js +++ b/test/integration/lib/tx-list-items.js @@ -14,6 +14,11 @@ QUnit.test('renders list items successfully', (assert) => { }) }) +global.ethQuery = global.ethQuery || {} +global.ethQuery.getTransactionCount = (_, cb) => { + cb(null, '0x3') +} + async function runTxListItemsTest (assert, done) { console.log('*** start runTxListItemsTest') const selectState = await queryAsync($, 'select') diff --git a/test/unit/migrations/028-test.js b/test/unit/migrations/028-test.js new file mode 100644 index 000000000..a9c7dcdf1 --- /dev/null +++ b/test/unit/migrations/028-test.js @@ -0,0 +1,46 @@ +const assert = require('assert') +const migration28 = require('../../../app/scripts/migrations/028') + +const oldStorage = { + 'meta': {}, + 'data': { + 'PreferencesController': { + 'tokens': [{address: '0xa', symbol: 'A', decimals: 4}, {address: '0xb', symbol: 'B', decimals: 4}], + 'identities': { + '0x6d14': {}, + '0x3695': {}, + }, + }, + }, +} + +describe('migration #28', () => { + it('should add corresponding tokens to accountTokens', (done) => { + migration28.migrate(oldStorage) + .then((newStorage) => { + const newTokens = newStorage.data.PreferencesController.tokens + const newAccountTokens = newStorage.data.PreferencesController.accountTokens + + const testTokens = [{address: '0xa', symbol: 'A', decimals: 4}, {address: '0xb', symbol: 'B', decimals: 4}] + assert.equal(newTokens.length, 0, 'tokens is expected to have the length of 0') + assert.equal(newAccountTokens['0x6d14']['mainnet'].length, 2, 'tokens for address is expected to have the length of 2') + assert.equal(newAccountTokens['0x3695']['mainnet'].length, 2, 'tokens for address is expected to have the length of 2') + assert.equal(Object.keys(newAccountTokens).length, 2, 'account tokens should be created for all identities') + assert.deepEqual(newAccountTokens['0x6d14']['mainnet'], testTokens, 'tokens for address should be the same than before') + assert.deepEqual(newAccountTokens['0x3695']['mainnet'], testTokens, 'tokens for address should be the same than before') + done() + }) + .catch(done) + }) + + it('should successfully migrate first time state', (done) => { + migration28.migrate({ + meta: {}, + data: require('../../../app/scripts/first-time-state'), + }) + .then((migratedData) => { + assert.equal(migratedData.meta.version, migration28.version) + done() + }).catch(done) + }) +}) diff --git a/ui/app/actions.js b/ui/app/actions.js index 4f71d911b..9edb3511a 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -143,6 +143,8 @@ var actions = { exportAccountComplete, SET_ACCOUNT_LABEL: 'SET_ACCOUNT_LABEL', setAccountLabel, + updateNetworkNonce, + SET_NETWORK_NONCE: 'SET_NETWORK_NONCE', // tx conf screen COMPLETED_TX: 'COMPLETED_TX', TRANSACTION_ERROR: 'TRANSACTION_ERROR', @@ -2116,6 +2118,24 @@ function updateFeatureFlags (updatedFeatureFlags) { } } +function setNetworkNonce (networkNonce) { + return { + type: actions.SET_NETWORK_NONCE, + value: networkNonce, + } +} + +function updateNetworkNonce (address) { + return (dispatch) => { + return new Promise((resolve, reject) => { + global.ethQuery.getTransactionCount(address, (err, data) => { + dispatch(setNetworkNonce(data)) + resolve(data) + }) + }) + } +} + function setMouseUserState (isMouseUser) { return { type: actions.SET_MOUSE_USER_STATE, diff --git a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js index b170880b4..961aa304e 100644 --- a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js +++ b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js @@ -124,7 +124,7 @@ export default class ConfirmTransactionBase extends Component { if (simulationFails) { return { - valid: false, + valid: true, errorKey: TRANSACTION_ERROR_KEY, } } diff --git a/ui/app/components/tx-list-item.js b/ui/app/components/tx-list-item.js index 7513ba267..474d62638 100644 --- a/ui/app/components/tx-list-item.js +++ b/ui/app/components/tx-list-item.js @@ -35,6 +35,7 @@ function mapStateToProps (state) { currentCurrency: getCurrentCurrency(state), contractExchangeRates: state.metamask.contractExchangeRates, selectedAddressTxList: state.metamask.selectedAddressTxList, + networkNonce: state.appState.networkNonce, } } @@ -209,6 +210,7 @@ TxListItem.prototype.showRetryButton = function () { selectedAddressTxList, transactionId, txParams, + networkNonce, } = this.props if (!txParams) { return false @@ -222,11 +224,7 @@ TxListItem.prototype.showRetryButton = function () { const currentTxIsLatestWithNonce = lastSubmittedTxWithCurrentNonce && lastSubmittedTxWithCurrentNonce.id === transactionId if (currentSubmittedTxs.length > 0) { - const earliestSubmitted = currentSubmittedTxs.reduce((tx1, tx2) => { - if (tx1.submittedTime < tx2.submittedTime) return tx1 - return tx2 - }) - currentTxSharesEarliestNonce = currentNonce === earliestSubmitted.txParams.nonce + currentTxSharesEarliestNonce = currentNonce === networkNonce } return currentTxSharesEarliestNonce && currentTxIsLatestWithNonce && Date.now() - transactionSubmittedTime > 30000 diff --git a/ui/app/components/tx-list.js b/ui/app/components/tx-list.js index 554febcff..d8c4a9d19 100644 --- a/ui/app/components/tx-list.js +++ b/ui/app/components/tx-list.js @@ -8,7 +8,7 @@ const selectors = require('../selectors') const TxListItem = require('./tx-list-item') const ShiftListItem = require('./shift-list-item') const { formatDate } = require('../util') -const { showConfTxPage } = require('../actions') +const { showConfTxPage, updateNetworkNonce } = require('../actions') const classnames = require('classnames') const { tokenInfoGetter } = require('../token-util') const { withRouter } = require('react-router-dom') @@ -28,12 +28,14 @@ function mapStateToProps (state) { return { txsToRender: selectors.transactionsSelector(state), conversionRate: selectors.conversionRateSelector(state), + selectedAddress: selectors.getSelectedAddress(state), } } function mapDispatchToProps (dispatch) { return { showConfTxPage: ({ id }) => dispatch(showConfTxPage({ id })), + updateNetworkNonce: (address) => dispatch(updateNetworkNonce(address)), } } @@ -44,6 +46,20 @@ function TxList () { TxList.prototype.componentWillMount = function () { this.tokenInfoGetter = tokenInfoGetter() + this.props.updateNetworkNonce(this.props.selectedAddress) +} + +TxList.prototype.componentDidUpdate = function (prevProps) { + const oldTxsToRender = prevProps.txsToRender + const { + txsToRender: newTxsToRender, + selectedAddress, + updateNetworkNonce, + } = this.props + + if (newTxsToRender.length > oldTxsToRender.length) { + updateNetworkNonce(selectedAddress) + } } TxList.prototype.render = function () { diff --git a/ui/app/constants/error-keys.js b/ui/app/constants/error-keys.js index 1b89be62e..f70ed3b19 100644 --- a/ui/app/constants/error-keys.js +++ b/ui/app/constants/error-keys.js @@ -1,3 +1,3 @@ export const INSUFFICIENT_FUNDS_ERROR_KEY = 'insufficientFunds' export const GAS_LIMIT_TOO_LOW_ERROR_KEY = 'gasLimitTooLow' -export const TRANSACTION_ERROR = 'transactionError' +export const TRANSACTION_ERROR_KEY = 'transactionError' diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 50d8bcba7..e4e4c8581 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -65,6 +65,7 @@ function reduceApp (state, action) { buyView: {}, isMouseUser: false, gasIsLoading: false, + networkNonce: null, }, state.appState) switch (action.type) { @@ -701,6 +702,11 @@ function reduceApp (state, action) { gasIsLoading: false, }) + case actions.SET_NETWORK_NONCE: + return extend(appState, { + networkNonce: action.value, + }) + default: return appState } diff --git a/ui/app/selectors/confirm-transaction.js b/ui/app/selectors/confirm-transaction.js index 9548cf75e..aa1fc5404 100644 --- a/ui/app/selectors/confirm-transaction.js +++ b/ui/app/selectors/confirm-transaction.js @@ -147,14 +147,20 @@ export const tokenAmountAndToAddressSelector = createSelector( export const approveTokenAmountAndToAddressSelector = createSelector( tokenDataParamsSelector, - params => { + tokenDecimalsSelector, + (params, tokenDecimals) => { let toAddress = '' let tokenAmount = 0 if (params && params.length) { toAddress = params.find(param => param.name === TOKEN_PARAM_SPENDER).value const value = Number(params.find(param => param.name === TOKEN_PARAM_VALUE).value) - tokenAmount = roundExponential(value) + + if (tokenDecimals) { + tokenAmount = calcTokenAmount(value, tokenDecimals) + } + + tokenAmount = roundExponential(tokenAmount) } return {