diff --git a/CHANGELOG.md b/CHANGELOG.md index 009cd5f7c..faffb8a2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Current Master +## 3.13.0 2017-12-7 + +- Allow resubmitting transactions that are taking long to complete. + ## 3.12.1 2017-11-29 - Fix bug where a user could be shown two different seed phrases. diff --git a/app/manifest.json b/app/manifest.json index 4219f3298..aa23f85ff 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.12.1", + "version": "3.13.0", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 6bdbd2cfc..bb408d445 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -185,6 +185,13 @@ module.exports = class TransactionController extends EventEmitter { return await this.txGasUtil.analyzeGasUsage(txMeta) } + async retryTransaction (txId) { + this.txStateManager.setTxStatusUnapproved(txId) + const txMeta = this.txStateManager.getTx(txId) + txMeta.lastGasPrice = txMeta.txParams.gasPrice + this.txStateManager.updateTx(txMeta, 'retryTransaction: manual retry') + } + async updateAndApproveTransaction (txMeta) { this.txStateManager.updateTx(txMeta, 'confTx: user approved transaction') await this.approveTransaction(txMeta.id) diff --git a/app/scripts/lib/tx-state-manager.js b/app/scripts/lib/tx-state-manager.js index 0fd6bed4b..cc441c584 100644 --- a/app/scripts/lib/tx-state-manager.js +++ b/app/scripts/lib/tx-state-manager.js @@ -187,6 +187,10 @@ module.exports = class TransactionStateManger extends EventEmitter { this._setTxStatus(txId, 'rejected') } + // should update the status of the tx to 'unapproved'. + setTxStatusUnapproved (txId) { + this._setTxStatus(txId, 'unapproved') + } // should update the status of the tx to 'approved'. setTxStatusApproved (txId) { this._setTxStatus(txId, 'approved') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 130ad1471..9d126b416 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -363,6 +363,7 @@ module.exports = class MetamaskController extends EventEmitter { // txController cancelTransaction: nodeify(txController.cancelTransaction, txController), updateAndApproveTransaction: nodeify(txController.updateAndApproveTransaction, txController), + retryTransaction: nodeify(this.retryTransaction, this), // messageManager signMessage: nodeify(this.signMessage, this), @@ -573,6 +574,14 @@ module.exports = class MetamaskController extends EventEmitter { // // Identity Management // + // + + async retryTransaction (txId, cb) { + await this.txController.retryTransaction(txId) + const state = await this.getState() + return state + } + newUnsignedMessage (msgParams, cb) { const msgId = this.messageManager.addUnapprovedMessage(msgParams) diff --git a/development/states/pending-tx.json b/development/states/pending-tx.json new file mode 100644 index 000000000..bfa93f7ae --- /dev/null +++ b/development/states/pending-tx.json @@ -0,0 +1,739 @@ +{ + "metamask": { + "isInitialized": true, + "isUnlocked": true, + "isMascara": false, + "rpcTarget": "https://rawtestrpc.metamask.io/", + "identities": { + "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": { + "address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "name": "Account 1" + } + }, + "unapprovedTxs": {}, + "noActiveNotices": true, + "frequentRpcList": [ + "http://192.168.1.34:7545/" + ], + "addressBook": [], + "tokenExchangeRates": {}, + "coinOptions": {}, + "provider": { + "type": "mainnet", + "rpcTarget": "https://mainnet.infura.io/metamask" + }, + "network": "1", + "accounts": { + "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": { + "code": "0x", + "balance": "0x1b3f641ed0c2f62", + "nonce": "0x35", + "address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825" + } + }, + "currentBlockGasLimit": "0x66df83", + "selectedAddressTxList": [ + { + "id": 3516145537630216, + "time": 1512615655535, + "status": "submitted", + "metamaskNetworkId": "1", + "txParams": { + "from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "value": "0x16345785d8a0000", + "gasPrice": "0xc1b710800", + "gas": "0x7b0c", + "nonce": "0x35", + "chainId": "0x1" + }, + "gasPriceSpecified": false, + "gasLimitSpecified": false, + "estimatedGas": "5208", + "history": [ + { + "id": 3516145537630216, + "time": 1512615655535, + "status": "unapproved", + "metamaskNetworkId": "1", + "txParams": { + "from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "value": "0x16345785d8a0000", + "gasPrice": "0xe6f7cec00", + "gas": "0x7b0c" + }, + "gasPriceSpecified": false, + "gasLimitSpecified": false, + "estimatedGas": "5208" + }, + [ + { + "op": "replace", + "path": "/txParams/gasPrice", + "value": "0xc1b710800", + "note": "confTx: user approved transaction" + } + ], + [ + { + "op": "replace", + "path": "/status", + "value": "approved", + "note": "txStateManager: setting status to approved" + } + ], + [ + { + "op": "add", + "path": "/txParams/nonce", + "value": "0x35", + "note": "transactions#approveTransaction" + }, + { + "op": "add", + "path": "/nonceDetails", + "value": { + "params": { + "highestLocalNonce": 53, + "highestSuggested": 53, + "nextNetworkNonce": 53 + }, + "local": { + "name": "local", + "nonce": 53, + "details": { + "startPoint": 53, + "highest": 53 + } + }, + "network": { + "name": "network", + "nonce": 53, + "details": { + "baseCount": 53 + } + } + } + } + ], + [ + { + "op": "add", + "path": "/txParams/chainId", + "value": "0x1", + "note": "txStateManager: setting status to signed" + }, + { + "op": "replace", + "path": "/status", + "value": "signed" + } + ], + [ + { + "op": "add", + "path": "/rawTx", + "value": "0xf86c35850c1b710800827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a0f5142ba79a13ca7ec65548953017edafb217803244bbf9821d9ad077d89921e9a03afcb614169c90be9905d5b469d06984825c76675d3a535937cdb8f2ad1c0a95", + "note": "transactions#publishTransaction" + } + ], + [ + { + "op": "add", + "path": "/hash", + "value": "0x7ce19c0d128ca11293b44a4e6d3cc9063665c00ea8c8eb400f548e132c147353", + "note": "transactions#setTxHash" + } + ], + [ + { + "op": "replace", + "path": "/status", + "value": "submitted", + "note": "txStateManager: setting status to submitted" + } + ], + [ + { + "op": "add", + "path": "/firstRetryBlockNumber", + "value": "0x478ab3", + "note": "transactions/pending-tx-tracker#event: tx:block-update" + } + ] + ], + "nonceDetails": { + "params": { + "highestLocalNonce": 53, + "highestSuggested": 53, + "nextNetworkNonce": 53 + }, + "local": { + "name": "local", + "nonce": 53, + "details": { + "startPoint": 53, + "highest": 53 + } + }, + "network": { + "name": "network", + "nonce": 53, + "details": { + "baseCount": 53 + } + } + }, + "rawTx": "0xf86c35850c1b710800827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a0f5142ba79a13ca7ec65548953017edafb217803244bbf9821d9ad077d89921e9a03afcb614169c90be9905d5b469d06984825c76675d3a535937cdb8f2ad1c0a95", + "hash": "0x7ce19c0d128ca11293b44a4e6d3cc9063665c00ea8c8eb400f548e132c147353", + "firstRetryBlockNumber": "0x478ab3" + }, + { + "id": 3516145537630211, + "time": 1512613432658, + "status": "confirmed", + "metamaskNetworkId": "1", + "txParams": { + "from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "value": "0x16345785d8a0000", + "gasPrice": "0xba43b7400", + "gas": "0x7b0c", + "nonce": "0x34", + "chainId": "0x1" + }, + "gasPriceSpecified": false, + "gasLimitSpecified": false, + "estimatedGas": "5208", + "history": [ + { + "id": 3516145537630211, + "time": 1512613432658, + "status": "unapproved", + "metamaskNetworkId": "1", + "txParams": { + "from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "value": "0x16345785d8a0000", + "gasPrice": "0xdf8475800", + "gas": "0x7b0c" + }, + "gasPriceSpecified": false, + "gasLimitSpecified": false, + "estimatedGas": "5208" + }, + [ + { + "op": "replace", + "path": "/txParams/gasPrice", + "value": "0xba43b7400", + "note": "confTx: user approved transaction" + } + ], + [ + { + "op": "replace", + "path": "/status", + "value": "approved", + "note": "txStateManager: setting status to approved" + } + ], + [ + { + "op": "add", + "path": "/txParams/nonce", + "value": "0x34", + "note": "transactions#approveTransaction" + }, + { + "op": "add", + "path": "/nonceDetails", + "value": { + "params": { + "highestLocalNonce": 52, + "highestSuggested": 52, + "nextNetworkNonce": 52 + }, + "local": { + "name": "local", + "nonce": 52, + "details": { + "startPoint": 52, + "highest": 52 + } + }, + "network": { + "name": "network", + "nonce": 52, + "details": { + "baseCount": 52 + } + } + } + } + ], + [ + { + "op": "add", + "path": "/txParams/chainId", + "value": "0x1", + "note": "txStateManager: setting status to signed" + }, + { + "op": "replace", + "path": "/status", + "value": "signed" + } + ], + [ + { + "op": "add", + "path": "/rawTx", + "value": "0xf86c34850ba43b7400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a073a4afdb8e8ad32b0cf9039af56c66baffd60d30e75cee5c1b783208824eafb8a0021ca6c1714a2c71281333ab77f776d3514348ab77967280fca8a5b4be44285e", + "note": "transactions#publishTransaction" + } + ], + [ + { + "op": "add", + "path": "/hash", + "value": "0x5c98409883fdfd3cd24058a83b91470da6c40ffae41a40eb90d7dee0b837d26d", + "note": "transactions#setTxHash" + } + ], + [ + { + "op": "replace", + "path": "/status", + "value": "submitted", + "note": "txStateManager: setting status to submitted" + } + ], + [ + { + "op": "add", + "path": "/firstRetryBlockNumber", + "value": "0x478a2c", + "note": "transactions/pending-tx-tracker#event: tx:block-update" + } + ], + [ + { + "op": "replace", + "path": "/status", + "value": "confirmed", + "note": "txStateManager: setting status to confirmed" + } + ] + ], + "nonceDetails": { + "params": { + "highestLocalNonce": 52, + "highestSuggested": 52, + "nextNetworkNonce": 52 + }, + "local": { + "name": "local", + "nonce": 52, + "details": { + "startPoint": 52, + "highest": 52 + } + }, + "network": { + "name": "network", + "nonce": 52, + "details": { + "baseCount": 52 + } + } + }, + "rawTx": "0xf86c34850ba43b7400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a073a4afdb8e8ad32b0cf9039af56c66baffd60d30e75cee5c1b783208824eafb8a0021ca6c1714a2c71281333ab77f776d3514348ab77967280fca8a5b4be44285e", + "hash": "0x5c98409883fdfd3cd24058a83b91470da6c40ffae41a40eb90d7dee0b837d26d", + "firstRetryBlockNumber": "0x478a2c" + }, + { + "id": 3516145537630210, + "time": 1512612826136, + "status": "confirmed", + "metamaskNetworkId": "1", + "txParams": { + "from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "value": "0x16345785d8a0000", + "gasPrice": "0xa7a358200", + "gas": "0x7b0c", + "nonce": "0x33", + "chainId": "0x1" + }, + "gasPriceSpecified": false, + "gasLimitSpecified": false, + "estimatedGas": "5208", + "history": [ + { + "id": 3516145537630210, + "time": 1512612826136, + "status": "unapproved", + "metamaskNetworkId": "1", + "txParams": { + "from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "value": "0x16345785d8a0000", + "gasPrice": "0xba43b7400", + "gas": "0x7b0c" + }, + "gasPriceSpecified": false, + "gasLimitSpecified": false, + "estimatedGas": "5208" + }, + [ + { + "op": "replace", + "path": "/txParams/gasPrice", + "value": "0xa7a358200", + "note": "confTx: user approved transaction" + } + ], + [ + { + "op": "replace", + "path": "/status", + "value": "approved", + "note": "txStateManager: setting status to approved" + } + ], + [ + { + "op": "add", + "path": "/txParams/nonce", + "value": "0x33", + "note": "transactions#approveTransaction" + }, + { + "op": "add", + "path": "/nonceDetails", + "value": { + "params": { + "highestLocalNonce": 0, + "highestSuggested": 51, + "nextNetworkNonce": 51 + }, + "local": { + "name": "local", + "nonce": 51, + "details": { + "startPoint": 51, + "highest": 51 + } + }, + "network": { + "name": "network", + "nonce": 51, + "details": { + "baseCount": 51 + } + } + } + } + ], + [ + { + "op": "add", + "path": "/txParams/chainId", + "value": "0x1", + "note": "txStateManager: setting status to signed" + }, + { + "op": "replace", + "path": "/status", + "value": "signed" + } + ], + [ + { + "op": "add", + "path": "/rawTx", + "value": "0xf86c33850a7a358200827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a0021a8cd6c10208cc593e22af53637e5d127cee5cc6f9443a3e758a02afff1d7ca025f7420e974d3f2c668c165040987c72543a8e709bfea3528a62836a6ced9ce8", + "note": "transactions#publishTransaction" + } + ], + [ + { + "op": "add", + "path": "/hash", + "value": "0x289772800898bc9cd414530d8581c0da257a9055e4aaaa6d10d92d700bfbd044", + "note": "transactions#setTxHash" + } + ], + [ + { + "op": "replace", + "path": "/status", + "value": "submitted", + "note": "txStateManager: setting status to submitted" + } + ], + [ + { + "op": "add", + "path": "/firstRetryBlockNumber", + "value": "0x478a04", + "note": "transactions/pending-tx-tracker#event: tx:block-update" + } + ], + [ + { + "op": "replace", + "path": "/status", + "value": "confirmed", + "note": "txStateManager: setting status to confirmed" + } + ] + ], + "nonceDetails": { + "params": { + "highestLocalNonce": 0, + "highestSuggested": 51, + "nextNetworkNonce": 51 + }, + "local": { + "name": "local", + "nonce": 51, + "details": { + "startPoint": 51, + "highest": 51 + } + }, + "network": { + "name": "network", + "nonce": 51, + "details": { + "baseCount": 51 + } + } + }, + "rawTx": "0xf86c33850a7a358200827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a0021a8cd6c10208cc593e22af53637e5d127cee5cc6f9443a3e758a02afff1d7ca025f7420e974d3f2c668c165040987c72543a8e709bfea3528a62836a6ced9ce8", + "hash": "0x289772800898bc9cd414530d8581c0da257a9055e4aaaa6d10d92d700bfbd044", + "firstRetryBlockNumber": "0x478a04" + }, + { + "id": 3516145537630209, + "time": 1512612809252, + "status": "failed", + "metamaskNetworkId": "1", + "txParams": { + "from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "value": "0x16345785d8a0000", + "gasPrice": "0x77359400", + "gas": "0x7b0c", + "nonce": "0x33", + "chainId": "0x1" + }, + "gasPriceSpecified": false, + "gasLimitSpecified": false, + "estimatedGas": "5208", + "history": [ + { + "id": 3516145537630209, + "time": 1512612809252, + "status": "unapproved", + "metamaskNetworkId": "1", + "txParams": { + "from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "value": "0x16345785d8a0000", + "gasPrice": "0xba43b7400", + "gas": "0x7b0c" + }, + "gasPriceSpecified": false, + "gasLimitSpecified": false, + "estimatedGas": "5208" + }, + [ + { + "op": "replace", + "path": "/txParams/gasPrice", + "value": "0x77359400", + "note": "confTx: user approved transaction" + } + ], + [ + { + "op": "replace", + "path": "/status", + "value": "approved", + "note": "txStateManager: setting status to approved" + } + ], + [ + { + "op": "add", + "path": "/txParams/nonce", + "value": "0x33", + "note": "transactions#approveTransaction" + }, + { + "op": "add", + "path": "/nonceDetails", + "value": { + "params": { + "highestLocalNonce": 0, + "highestSuggested": 51, + "nextNetworkNonce": 51 + }, + "local": { + "name": "local", + "nonce": 51, + "details": { + "startPoint": 51, + "highest": 51 + } + }, + "network": { + "name": "network", + "nonce": 51, + "details": { + "baseCount": 51 + } + } + } + } + ], + [ + { + "op": "add", + "path": "/txParams/chainId", + "value": "0x1", + "note": "txStateManager: setting status to signed" + }, + { + "op": "replace", + "path": "/status", + "value": "signed" + } + ], + [ + { + "op": "add", + "path": "/rawTx", + "value": "0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7", + "note": "transactions#publishTransaction" + } + ], + [ + { + "op": "add", + "path": "/err", + "value": { + "message": "Error: [ethjs-rpc] rpc error with payload {\"id\":7801900228852,\"jsonrpc\":\"2.0\",\"params\":[\"0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7\"],\"method\":\"eth_sendRawTransaction\"} Error: transaction underpriced", + "stack": "Error: [ethjs-rpc] rpc error with payload {\"id\":7801900228852,\"jsonrpc\":\"2.0\",\"params\":[\"0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7\"],\"method\":\"eth_sendRawTransaction\"} Error: transaction underpriced\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:60327:26\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:88030:9\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16678:16\n at replenish (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16522:25)\n at iterateeCallback (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16512:17)\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16694:16\n at resultObj.id (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:88012:9)\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16813:16\n at replenish (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16527:17)\n at iterateeCallback (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16512:17)" + } + } + ], + [ + { + "op": "replace", + "path": "/status", + "value": "failed", + "note": "txStateManager: setting status to failed" + } + ] + ], + "nonceDetails": { + "params": { + "highestLocalNonce": 0, + "highestSuggested": 51, + "nextNetworkNonce": 51 + }, + "local": { + "name": "local", + "nonce": 51, + "details": { + "startPoint": 51, + "highest": 51 + } + }, + "network": { + "name": "network", + "nonce": 51, + "details": { + "baseCount": 51 + } + } + }, + "rawTx": "0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7", + "err": { + "message": "Error: [ethjs-rpc] rpc error with payload {\"id\":7801900228852,\"jsonrpc\":\"2.0\",\"params\":[\"0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7\"],\"method\":\"eth_sendRawTransaction\"} Error: transaction underpriced", + "stack": "Error: [ethjs-rpc] rpc error with payload {\"id\":7801900228852,\"jsonrpc\":\"2.0\",\"params\":[\"0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7\"],\"method\":\"eth_sendRawTransaction\"} Error: transaction underpriced\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:60327:26\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:88030:9\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16678:16\n at replenish (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16522:25)\n at iterateeCallback (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16512:17)\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16694:16\n at resultObj.id (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:88012:9)\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16813:16\n at replenish (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16527:17)\n at iterateeCallback (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16512:17)" + } + } + ], + "unapprovedMsgs": {}, + "unapprovedMsgCount": 0, + "unapprovedPersonalMsgs": {}, + "unapprovedPersonalMsgCount": 0, + "unapprovedTypedMessages": {}, + "unapprovedTypedMessagesCount": 0, + "keyringTypes": [ + "Simple Key Pair", + "HD Key Tree" + ], + "keyrings": [ + { + "type": "HD Key Tree", + "accounts": [ + "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825" + ] + } + ], + "computedBalances": {}, + "currentAccountTab": "history", + "tokens": [ + { + "address": "0x0d8775f648430679a709e98d2b0cb6250d2887ef", + "symbol": "BAT", + "decimals": "18" + } + ], + "selectedAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "currentCurrency": "usd", + "conversionRate": 418.62, + "conversionDate": 1512615622, + "infuraNetworkStatus": { + "mainnet": "ok", + "ropsten": "ok", + "kovan": "ok", + "rinkeby": "ok" + }, + "shapeShiftTxList": [], + "lostAccounts": [] + }, + "appState": { + "shouldClose": true, + "menuOpen": false, + "currentView": { + "name": "accountDetail", + "context": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825" + }, + "accountDetail": { + "subview": "transactions", + "accountExport": "none", + "privateKey": "" + }, + "transForward": false, + "isLoading": false, + "warning": null, + "forgottenPassword": false, + "scrollToBottom": false + }, + "identities": {}, + "version": "3.12.1", + "platform": { + "arch": "x86-64", + "nacl_arch": "x86-64", + "os": "mac" + }, + "browser": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36" +} \ No newline at end of file diff --git a/ui/app/actions.js b/ui/app/actions.js index 04fd35b20..52ea899aa 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -168,6 +168,7 @@ var actions = { callBackgroundThenUpdate, forceUpdateMetamaskState, + retryTransaction, } module.exports = actions @@ -759,6 +760,19 @@ function markAccountsFound () { return callBackgroundThenUpdate(background.markAccountsFound) } +function retryTransaction (txId) { + log.debug(`background.retryTransaction`) + return (dispatch) => { + background.retryTransaction(txId, (err, newState) => { + if (err) { + return dispatch(actions.displayWarning(err.message)) + } + dispatch(actions.updateMetamaskState(newState)) + dispatch(actions.viewPendingTx(txId)) + }) + } +} + // // config // diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 5b1b367c6..32d54902e 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -38,6 +38,16 @@ PendingTx.prototype.render = function () { const txMeta = this.gatherTxMeta() const txParams = txMeta.txParams || {} + // Allow retry txs + const { lastGasPrice } = txMeta + let forceGasMin + if (lastGasPrice) { + const stripped = ethUtil.stripHexPrefix(lastGasPrice) + const lastGas = new BN(stripped, 16) + const priceBump = lastGas.divn('10') + forceGasMin = lastGas.add(priceBump) + } + // Account Details const address = txParams.from || props.selectedAddress const identity = props.identities[address] || { address: address } @@ -199,7 +209,7 @@ PendingTx.prototype.render = function () { precision: 9, scale: 9, suffix: 'GWEI', - min: MIN_GAS_PRICE_BN, + min: forceGasMin || MIN_GAS_PRICE_BN, style: { position: 'relative', top: '5px', diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js index 891d5e227..42ef665b1 100644 --- a/ui/app/components/transaction-list-item.js +++ b/ui/app/components/transaction-list-item.js @@ -1,6 +1,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits +const connect = require('react-redux').connect const EthBalance = require('./eth-balance') const addressSummary = require('../util').addressSummary @@ -9,18 +10,33 @@ const CopyButton = require('./copyButton') const vreme = new (require('vreme'))() const Tooltip = require('./tooltip') const numberToBN = require('number-to-bn') +const actions = require('../actions') const TransactionIcon = require('./transaction-list-item-icon') const ShiftListItem = require('./shift-list-item') -module.exports = TransactionListItem + +const mapDispatchToProps = dispatch => { + return { + retryTransaction: transactionId => dispatch(actions.retryTransaction(transactionId)), + } +} + +module.exports = connect(null, mapDispatchToProps)(TransactionListItem) inherits(TransactionListItem, Component) function TransactionListItem () { Component.call(this) } +TransactionListItem.prototype.showRetryButton = function () { + const { transaction = {} } = this.props + const { status, time } = transaction + return status === 'submitted' && Date.now() - time > 30000 +} + TransactionListItem.prototype.render = function () { const { transaction, network, conversionRate, currentCurrency } = this.props + const { status } = transaction if (transaction.key === 'shapeshift') { if (network === '1') return h(ShiftListItem, transaction) } @@ -32,7 +48,7 @@ TransactionListItem.prototype.render = function () { var isMsg = ('msgParams' in transaction) var isTx = ('txParams' in transaction) - var isPending = transaction.status === 'unapproved' + var isPending = status === 'unapproved' let txParams if (isTx) { txParams = transaction.txParams @@ -44,7 +60,7 @@ TransactionListItem.prototype.render = function () { const isClickable = ('hash' in transaction && isLinkable) || isPending return ( - h(`.transaction-list-item.flex-row.flex-space-between${isClickable ? '.pointer' : ''}`, { + h('.transaction-list-item.flex-column', { onClick: (event) => { if (isPending) { this.props.showTx(transaction.id) @@ -56,51 +72,92 @@ TransactionListItem.prototype.render = function () { }, style: { padding: '20px 0', + alignItems: 'center', }, }, [ - - h('.identicon-wrapper.flex-column.flex-center.select-none', [ - h(TransactionIcon, { txParams, transaction, isTx, isMsg }), - ]), - - h(Tooltip, { - title: 'Transaction Number', - position: 'right', + h(`.flex-row.flex-space-between${isClickable ? '.pointer' : ''}`, { + style: { + width: '100%', + }, }, [ - h('span', { + h('.identicon-wrapper.flex-column.flex-center.select-none', [ + h(TransactionIcon, { txParams, transaction, isTx, isMsg }), + ]), + + h(Tooltip, { + title: 'Transaction Number', + position: 'right', + }, [ + h('span', { + style: { + display: 'flex', + cursor: 'normal', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + padding: '10px', + }, + }, nonce), + ]), + + h('.flex-column', {style: {width: '200px', overflow: 'hidden'}}, [ + domainField(txParams), + h('div', date), + recipientField(txParams, transaction, isTx, isMsg), + ]), + + // Places a copy button if tx is successful, else places a placeholder empty div. + transaction.hash ? h(CopyButton, { value: transaction.hash }) : h('div', {style: { display: 'flex', alignItems: 'center', width: '26px' }}), + + isTx ? h(EthBalance, { + value: txParams.value, + conversionRate, + currentCurrency, + width: '55px', + shorten: true, + showFiat: false, + style: {fontSize: '15px'}, + }) : h('.flex-column'), + ]), + + this.showRetryButton() && h('.transition-list-item__retry.grow-on-hover', { + onClick: event => { + event.stopPropagation() + this.resubmit() + }, + style: { + height: '22px', + borderRadius: '22px', + color: '#F9881B', + padding: '0 20px', + backgroundColor: '#FFE3C9', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + fontSize: '8px', + cursor: 'pointer', + }, + }, [ + h('div', { style: { - display: 'flex', - cursor: 'normal', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - padding: '10px', + paddingRight: '2px', }, - }, nonce), + }, 'Taking too long?'), + h('div', { + style: { + textDecoration: 'underline', + }, + }, 'Retry with a higher gas price here'), ]), - - h('.flex-column', {style: {width: '200px', overflow: 'hidden'}}, [ - domainField(txParams), - h('div', date), - recipientField(txParams, transaction, isTx, isMsg), - ]), - - // Places a copy button if tx is successful, else places a placeholder empty div. - transaction.hash ? h(CopyButton, { value: transaction.hash }) : h('div', {style: { display: 'flex', alignItems: 'center', width: '26px' }}), - - isTx ? h(EthBalance, { - value: txParams.value, - conversionRate, - currentCurrency, - width: '55px', - shorten: true, - showFiat: false, - style: {fontSize: '15px'}, - }) : h('.flex-column'), ]) ) } +TransactionListItem.prototype.resubmit = function () { + const { transaction } = this.props + this.props.retryTransaction(transaction.id) +} + function domainField (txParams) { return h('div', { style: { diff --git a/ui/app/css/index.css b/ui/app/css/index.css index 0630c4c12..c0bf18c23 100644 --- a/ui/app/css/index.css +++ b/ui/app/css/index.css @@ -108,6 +108,10 @@ button:not([disabled]):active, input[type="submit"]:not([disabled]):active { transform: scale(0.95); } +.grow-on-hover:hover { + transform: scale(1.05); +} + a { text-decoration: none; color: inherit;