From 89af0ef408eb62b7af5e05167f210d6563ef0f43 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Fri, 17 Feb 2017 12:08:54 -0800 Subject: [PATCH 01/52] Change state to props, add modifiable fields. --- ui/app/components/pending-tx.js | 42 ++++++++++++++++++++++++++------- ui/app/conf-tx.js | 40 ++++++++++++++++--------------- 2 files changed, 54 insertions(+), 28 deletions(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 96f968929..9eefe1b22 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -2,6 +2,8 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits const PendingTxDetails = require('./pending-tx-details') +const BN = require('ethereumjs-util').BN +const ethUtil = require('ethereumjs-util') module.exports = PendingTx @@ -11,8 +13,12 @@ function PendingTx () { } PendingTx.prototype.render = function () { - var state = this.props - var txData = state.txData + var props = this.props + var state = this.state || {} + var txData = props.txData + var txParams = txData.txParams + var gasValue = state.gas || txParams.gas + var decimalGas = decimalize(gasValue) return ( @@ -21,7 +27,7 @@ PendingTx.prototype.render = function () { }, [ // tx info - h(PendingTxDetails, state), + h(PendingTxDetails, props), h('style', ` .conf-buttons button { @@ -39,7 +45,7 @@ PendingTx.prototype.render = function () { }, 'Transaction Error. Exception thrown in contract code.') : null, - state.insufficientBalance ? + props.insufficientBalance ? h('span.error', { style: { marginLeft: 50, @@ -57,21 +63,39 @@ PendingTx.prototype.render = function () { }, }, [ - state.insufficientBalance ? + props.insufficientBalance ? h('button.btn-green', { - onClick: state.buyEth, + onClick: props.buyEth, }, 'Buy Ether') : null, h('button.confirm', { - disabled: state.insufficientBalance, - onClick: state.sendTransaction, + disabled: props.insufficientBalance, + onClick: props.sendTransaction, }, 'Accept'), h('button.cancel.btn-red', { - onClick: state.cancelTransaction, + onClick: props.cancelTransaction, }, 'Reject'), ]), + h('input', { + value: decimalGas, + onChange: (event) => { + const hexString = hexify(event.target.value) + this.setState({ gas: hexString }) + } + }), ]) ) } + +function decimalize (input) { + const strippedInput = ethUtil.stripHexPrefix(input) + const inputBN = new BN(strippedInput, 'hex') + return inputBN.toString(10) +} + +function hexify (decimalString) { + const hexBN = new BN(decimalString, 10) + return '0x' + hexBN.toString('hex') +} diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index a27219576..eed1f59ad 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -35,15 +35,16 @@ function ConfirmTxScreen () { } ConfirmTxScreen.prototype.render = function () { - var state = this.props + var props = this.props + var state = this.state || {} - var network = state.network - var provider = state.provider - var unapprovedTxs = state.unapprovedTxs - var unapprovedMsgs = state.unapprovedMsgs + var network = props.network + var provider = props.provider + var unapprovedTxs = props.unapprovedTxs + var unapprovedMsgs = props.unapprovedMsgs var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, network) - var index = state.index !== undefined && unconfTxList[index] ? state.index : 0 + var index = props.index !== undefined && unconfTxList[index] ? props.index : 0 var txData = unconfTxList[index] || {} var txParams = txData.params || {} var isNotification = isPopupOrNotification() === 'notification' @@ -73,20 +74,20 @@ ConfirmTxScreen.prototype.render = function () { }, [ h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', { style: { - display: state.index === 0 ? 'none' : 'inline-block', + display: props.index === 0 ? 'none' : 'inline-block', }, - onClick: () => state.dispatch(actions.previousTx()), + onClick: () => props.dispatch(actions.previousTx()), }), - ` ${state.index + 1} of ${unconfTxList.length} `, + ` ${props.index + 1} of ${unconfTxList.length} `, h('i.fa.fa-arrow-right.fa-lg.cursor-pointer', { style: { - display: state.index + 1 === unconfTxList.length ? 'none' : 'inline-block', + display: props.index + 1 === unconfTxList.length ? 'none' : 'inline-block', }, - onClick: () => state.dispatch(actions.nextTx()), + onClick: () => props.dispatch(actions.nextTx()), }), ]), - warningIfExists(state.warning), + warningIfExists(props.warning), h(ReactCSSTransitionGroup, { className: 'css-transition-group', @@ -99,12 +100,12 @@ ConfirmTxScreen.prototype.render = function () { // Properties txData: txData, key: txData.id, - selectedAddress: state.selectedAddress, - accounts: state.accounts, - identities: state.identities, + selectedAddress: props.selectedAddress, + accounts: props.accounts, + identities: props.identities, insufficientBalance: this.checkBalanceAgainstTx(txData), // Actions - buyEth: this.buyEth.bind(this, txParams.from || state.selectedAddress), + buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress), sendTransaction: this.sendTransaction.bind(this, txData), cancelTransaction: this.cancelTransaction.bind(this, txData), signMessage: this.signMessage.bind(this, txData), @@ -128,11 +129,12 @@ function currentTxView (opts) { return h(PendingMsg, opts) } } + ConfirmTxScreen.prototype.checkBalanceAgainstTx = function (txData) { if (!txData.txParams) return false - var state = this.props - var address = txData.txParams.from || state.selectedAddress - var account = state.accounts[address] + var props = this.props + var address = txData.txParams.from || props.selectedAddress + var account = props.accounts[address] var balance = account ? account.balance : '0x0' var maxCost = new BN(txData.maxCost, 16) From 6b56d6ba9853ec978cd2d3d030882fa5ee3645cd Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 17 Feb 2017 12:44:09 -0800 Subject: [PATCH 02/52] Broke hex decimal input into its own component Also added a new state to try to make UI dev mode work again, but it has other issues, like #1128, that need to be addressed before UI dev mode can be used again. --- development/states/conf-tx.json | 210 ++++++++++++++++++++++ ui/app/components/hex-as-decimal-input.js | 49 +++++ ui/app/components/pending-tx.js | 46 +++-- ui/app/conf-tx.js | 1 - 4 files changed, 281 insertions(+), 25 deletions(-) create mode 100644 development/states/conf-tx.json create mode 100644 ui/app/components/hex-as-decimal-input.js diff --git a/development/states/conf-tx.json b/development/states/conf-tx.json new file mode 100644 index 000000000..11b0eec89 --- /dev/null +++ b/development/states/conf-tx.json @@ -0,0 +1,210 @@ +{ + "metamask": { + "isInitialized": true, + "isUnlocked": true, + "rpcTarget": "https://rawtestrpc.metamask.io/", + "identities": { + "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": { + "address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "name": "Account 1" + }, + "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb": { + "address": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb", + "name": "Account 2" + }, + "0x2f8d4a878cfa04a6e60d46362f5644deab66572d": { + "address": "0x2f8d4a878cfa04a6e60d46362f5644deab66572d", + "name": "Account 3" + }, + "0x18c643c9cf21027339c8648fbaa2f348ddcbe00a": { + "address": "0x18c643c9cf21027339c8648fbaa2f348ddcbe00a", + "name": "Account 4" + }, + "0x9858e7d8b79fc3e6d989636721584498926da38a": { + "address": "0x9858e7d8b79fc3e6d989636721584498926da38a", + "name": "Account 5" + } + }, + "unapprovedTxs": { + "4768706228115573": { + "id": 4768706228115573, + "time": 1487363153561, + "status": "unapproved", + "gasMultiplier": 1, + "metamaskNetworkId": "3", + "txParams": { + "from": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb", + "to": "0x18a3462427bcc9133bb46e88bcbe39cd7ef0e761", + "value": "0xde0b6b3a7640000", + "metamaskId": 4768706228115573, + "metamaskNetworkId": "3", + "gas": "0x5209" + }, + "gasLimitSpecified": false, + "estimatedGas": "0x5209", + "txFee": "17e0186e60800", + "txValue": "de0b6b3a7640000", + "maxCost": "de234b52e4a0800" + } + }, + "currentFiat": "USD", + "conversionRate": 12.7200827, + "conversionDate": 1487363041, + "noActiveNotices": false, + "lastUnreadNotice": { + "read": true, + "date": "Thu Feb 09 2017", + "title": "Terms of Use", + "body": "# Terms of Use #\n\n**THIS AGREEMENT IS SUBJECT TO BINDING ARBITRATION AND A WAIVER OF CLASS ACTION RIGHTS AS DETAILED IN SECTION 13. PLEASE READ THE AGREEMENT CAREFULLY.**\n\n_Our Terms of Use have been updated as of September 5, 2016_\n\n## 1. Acceptance of Terms ##\n\nMetaMask provides a platform for managing Ethereum (or \"ETH\") accounts, and allowing ordinary websites to interact with the Ethereum blockchain, while keeping the user in control over what transactions they approve, through our website located at[ ](http://metamask.io)[https://metamask.io/](https://metamask.io/) and browser plugin (the \"Site\") — which includes text, images, audio, code and other materials (collectively, the “Content”) and all of the features, and services provided. The Site, and any other features, tools, materials, or other services offered from time to time by MetaMask are referred to here as the “Service.” Please read these Terms of Use (the “Terms” or “Terms of Use”) carefully before using the Service. By using or otherwise accessing the Services, or clicking to accept or agree to these Terms where that option is made available, you (1) accept and agree to these Terms (2) consent to the collection, use, disclosure and other handling of information as described in our Privacy Policy and (3) any additional terms, rules and conditions of participation issued by MetaMask from time to time. If you do not agree to the Terms, then you may not access or use the Content or Services.\n\n## 2. Modification of Terms of Use ##\n\nExcept for Section 13, providing for binding arbitration and waiver of class action rights, MetaMask reserves the right, at its sole discretion, to modify or replace the Terms of Use at any time. The most current version of these Terms will be posted on our Site. You shall be responsible for reviewing and becoming familiar with any such modifications. Use of the Services by you after any modification to the Terms constitutes your acceptance of the Terms of Use as modified.\n\n\n\n## 3. Eligibility ##\n\nYou hereby represent and warrant that you are fully able and competent to enter into the terms, conditions, obligations, affirmations, representations and warranties set forth in these Terms and to abide by and comply with these Terms.\n\nMetaMask is a global platform and by accessing the Content or Services, you are representing and warranting that, you are of the legal age of majority in your jurisdiction as is required to access such Services and Content and enter into arrangements as provided by the Service. You further represent that you are otherwise legally permitted to use the service in your jurisdiction including owning cryptographic tokens of value, and interacting with the Services or Content in any way. You further represent you are responsible for ensuring compliance with the laws of your jurisdiction and acknowledge that MetaMask is not liable for your compliance with such laws.\n\n## 4 Account Password and Security ##\n\nWhen setting up an account within MetaMask, you will be responsible for keeping your own account secrets, which may be a twelve-word seed phrase, an account file, or other locally stored secret information. MetaMask encrypts this information locally with a password you provide, that we never send to our servers. You agree to (a) never use the same password for MetaMask that you have ever used outside of this service; (b) keep your secret information and password confidential and do not share them with anyone else; (c) immediately notify MetaMask of any unauthorized use of your account or breach of security. MetaMask cannot and will not be liable for any loss or damage arising from your failure to comply with this section.\n\n## 5. Representations, Warranties, and Risks ##\n\n### 5.1. Warranty Disclaimer ###\n\nYou expressly understand and agree that your use of the Service is at your sole risk. The Service (including the Service and the Content) are provided on an \"AS IS\" and \"as available\" basis, without warranties of any kind, either express or implied, including, without limitation, implied warranties of merchantability, fitness for a particular purpose or non-infringement. You acknowledge that MetaMask has no control over, and no duty to take any action regarding: which users gain access to or use the Service; what effects the Content may have on you; how you may interpret or use the Content; or what actions you may take as a result of having been exposed to the Content. You release MetaMask from all liability for you having acquired or not acquired Content through the Service. MetaMask makes no representations concerning any Content contained in or accessed through the Service, and MetaMask will not be responsible or liable for the accuracy, copyright compliance, legality or decency of material contained in or accessed through the Service.\n\n### 5.2 Sophistication and Risk of Cryptographic Systems ###\n\nBy utilizing the Service or interacting with the Content or platform in any way, you represent that you understand the inherent risks associated with cryptographic systems; and warrant that you have an understanding of the usage and intricacies of native cryptographic tokens, like Ether (ETH) and Bitcoin (BTC), smart contract based tokens such as those that follow the Ethereum Token Standard (https://github.com/ethereum/EIPs/issues/20), and blockchain-based software systems.\n\n### 5.3 Risk of Regulatory Actions in One or More Jurisdictions ###\n\nMetaMask and ETH could be impacted by one or more regulatory inquiries or regulatory action, which could impede or limit the ability of MetaMask to continue to develop, or which could impede or limit your ability to access or use the Service or Ethereum blockchain.\n\n### 5.4 Risk of Weaknesses or Exploits in the Field of Cryptography ###\n\nYou acknowledge and understand that Cryptography is a progressing field. Advances in code cracking or technical advances such as the development of quantum computers may present risks to cryptocurrencies and Services of Content, which could result in the theft or loss of your cryptographic tokens or property. To the extent possible, MetaMask intends to update the protocol underlying Services to account for any advances in cryptography and to incorporate additional security measures, but does not guarantee or otherwise represent full security of the system. By using the Service or accessing Content, you acknowledge these inherent risks.\n\n### 5.5 Volatility of Crypto Currencies ###\n\nYou understand that Ethereum and other blockchain technologies and associated currencies or tokens are highly volatile due to many factors including but not limited to adoption, speculation, technology and security risks. You also acknowledge that the cost of transacting on such technologies is variable and may increase at any time causing impact to any activities taking place on the Ethereum blockchain. You acknowledge these risks and represent that MetaMask cannot be held liable for such fluctuations or increased costs.\n\n### 5.6 Application Security ###\n\nYou acknowledge that Ethereum applications are code subject to flaws and acknowledge that you are solely responsible for evaluating any code provided by the Services or Content and the trustworthiness of any third-party websites, products, smart-contracts, or Content you access or use through the Service. You further expressly acknowledge and represent that Ethereum applications can be written maliciously or negligently, that MetaMask cannot be held liable for your interaction with such applications and that such applications may cause the loss of property or even identity. This warning and others later provided by MetaMask in no way evidence or represent an on-going duty to alert you to all of the potential risks of utilizing the Service or Content.\n\n## 6. Indemnity ##\n\nYou agree to release and to indemnify, defend and hold harmless MetaMask and its parents, subsidiaries, affiliates and agencies, as well as the officers, directors, employees, shareholders and representatives of any of the foregoing entities, from and against any and all losses, liabilities, expenses, damages, costs (including attorneys’ fees and court costs) claims or actions of any kind whatsoever arising or resulting from your use of the Service, your violation of these Terms of Use, and any of your acts or omissions that implicate publicity rights, defamation or invasion of privacy. MetaMask reserves the right, at its own expense, to assume exclusive defense and control of any matter otherwise subject to indemnification by you and, in such case, you agree to cooperate with MetaMask in the defense of such matter.\n\n## 7. Limitation on liability ##\n\nYOU ACKNOWLEDGE AND AGREE THAT YOU ASSUME FULL RESPONSIBILITY FOR YOUR USE OF THE SITE AND SERVICE. YOU ACKNOWLEDGE AND AGREE THAT ANY INFORMATION YOU SEND OR RECEIVE DURING YOUR USE OF THE SITE AND SERVICE MAY NOT BE SECURE AND MAY BE INTERCEPTED OR LATER ACQUIRED BY UNAUTHORIZED PARTIES. YOU ACKNOWLEDGE AND AGREE THAT YOUR USE OF THE SITE AND SERVICE IS AT YOUR OWN RISK. RECOGNIZING SUCH, YOU UNDERSTAND AND AGREE THAT, TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, NEITHER METAMASK NOR ITS SUPPLIERS OR LICENSORS WILL BE LIABLE TO YOU FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY OR OTHER DAMAGES OF ANY KIND, INCLUDING WITHOUT LIMITATION DAMAGES FOR LOSS OF PROFITS, GOODWILL, USE, DATA OR OTHER TANGIBLE OR INTANGIBLE LOSSES OR ANY OTHER DAMAGES BASED ON CONTRACT, TORT, STRICT LIABILITY OR ANY OTHER THEORY (EVEN IF METAMASK HAD BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES), RESULTING FROM THE SITE OR SERVICE; THE USE OR THE INABILITY TO USE THE SITE OR SERVICE; UNAUTHORIZED ACCESS TO OR ALTERATION OF YOUR TRANSMISSIONS OR DATA; STATEMENTS OR CONDUCT OF ANY THIRD PARTY ON THE SITE OR SERVICE; ANY ACTIONS WE TAKE OR FAIL TO TAKE AS A RESULT OF COMMUNICATIONS YOU SEND TO US; HUMAN ERRORS; TECHNICAL MALFUNCTIONS; FAILURES, INCLUDING PUBLIC UTILITY OR TELEPHONE OUTAGES; OMISSIONS, INTERRUPTIONS, LATENCY, DELETIONS OR DEFECTS OF ANY DEVICE OR NETWORK, PROVIDERS, OR SOFTWARE (INCLUDING, BUT NOT LIMITED TO, THOSE THAT DO NOT PERMIT PARTICIPATION IN THE SERVICE); ANY INJURY OR DAMAGE TO COMPUTER EQUIPMENT; INABILITY TO FULLY ACCESS THE SITE OR SERVICE OR ANY OTHER WEBSITE; THEFT, TAMPERING, DESTRUCTION, OR UNAUTHORIZED ACCESS TO, IMAGES OR OTHER CONTENT OF ANY KIND; DATA THAT IS PROCESSED LATE OR INCORRECTLY OR IS INCOMPLETE OR LOST; TYPOGRAPHICAL, PRINTING OR OTHER ERRORS, OR ANY COMBINATION THEREOF; OR ANY OTHER MATTER RELATING TO THE SITE OR SERVICE.\n\nSOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF CERTAIN WARRANTIES OR THE LIMITATION OR EXCLUSION OF LIABILITY FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES. ACCORDINGLY, SOME OF THE ABOVE LIMITATIONS MAY NOT APPLY TO YOU.\n\n## 8. Our Proprietary Rights ##\n\nAll title, ownership and intellectual property rights in and to the Service are owned by MetaMask or its licensors. You acknowledge and agree that the Service contains proprietary and confidential information that is protected by applicable intellectual property and other laws. Except as expressly authorized by MetaMask, you agree not to copy, modify, rent, lease, loan, sell, distribute, perform, display or create derivative works based on the Service, in whole or in part. MetaMask issues a license for MetaMask, found [here](https://github.com/MetaMask/metamask-plugin/blob/master/LICENSE). For information on other licenses utilized in the development of MetaMask, please see our attribution page at: [https://metamask.io/attributions.html](https://metamask.io/attributions.html)\n\n## 9. Links ##\n\nThe Service provides, or third parties may provide, links to other World Wide Web or accessible sites, applications or resources. Because MetaMask has no control over such sites, applications and resources, you acknowledge and agree that MetaMask is not responsible for the availability of such external sites, applications or resources, and does not endorse and is not responsible or liable for any content, advertising, products or other materials on or available from such sites or resources. You further acknowledge and agree that MetaMask shall not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such content, goods or services available on or through any such site or resource.\n\n## 10. Termination and Suspension ##\n\nMetaMask may terminate or suspend all or part of the Service and your MetaMask access immediately, without prior notice or liability, if you breach any of the terms or conditions of the Terms. Upon termination of your access, your right to use the Service will immediately cease.\n\nThe following provisions of the Terms survive any termination of these Terms: INDEMNITY; WARRANTY DISCLAIMERS; LIMITATION ON LIABILITY; OUR PROPRIETARY RIGHTS; LINKS; TERMINATION; NO THIRD PARTY BENEFICIARIES; BINDING ARBITRATION AND CLASS ACTION WAIVER; GENERAL INFORMATION.\n\n## 11. No Third Party Beneficiaries ##\n\nYou agree that, except as otherwise expressly provided in these Terms, there shall be no third party beneficiaries to the Terms.\n\n## 12. Notice and Procedure For Making Claims of Copyright Infringement ##\n\nIf you believe that your copyright or the copyright of a person on whose behalf you are authorized to act has been infringed, please provide MetaMask’s Copyright Agent a written Notice containing the following information:\n\n· an electronic or physical signature of the person authorized to act on behalf of the owner of the copyright or other intellectual property interest;\n\n· a description of the copyrighted work or other intellectual property that you claim has been infringed;\n\n· a description of where the material that you claim is infringing is located on the Service;\n\n· your address, telephone number, and email address;\n\n· a statement by you that you have a good faith belief that the disputed use is not authorized by the copyright owner, its agent, or the law;\n\n· a statement by you, made under penalty of perjury, that the above information in your Notice is accurate and that you are the copyright or intellectual property owner or authorized to act on the copyright or intellectual property owner's behalf.\n\nMetaMask’s Copyright Agent can be reached at:\n\nEmail: copyright [at] metamask [dot] io\n\nMail:\n\nAttention:\n\nMetaMask Copyright ℅ ConsenSys\n\n49 Bogart Street\n\nBrooklyn, NY 11206\n\n## 13. Binding Arbitration and Class Action Waiver ##\n\nPLEASE READ THIS SECTION CAREFULLY – IT MAY SIGNIFICANTLY AFFECT YOUR LEGAL RIGHTS, INCLUDING YOUR RIGHT TO FILE A LAWSUIT IN COURT\n\n### 13.1 Initial Dispute Resolution ###\n\nThe parties shall use their best efforts to engage directly to settle any dispute, claim, question, or disagreement and engage in good faith negotiations which shall be a condition to either party initiating a lawsuit or arbitration.\n\n### 13.2 Binding Arbitration ###\n\nIf the parties do not reach an agreed upon solution within a period of 30 days from the time informal dispute resolution under the Initial Dispute Resolution provision begins, then either party may initiate binding arbitration as the sole means to resolve claims, subject to the terms set forth below. Specifically, all claims arising out of or relating to these Terms (including their formation, performance and breach), the parties’ relationship with each other and/or your use of the Service shall be finally settled by binding arbitration administered by the American Arbitration Association in accordance with the provisions of its Commercial Arbitration Rules and the supplementary procedures for consumer related disputes of the American Arbitration Association (the \"AAA\"), excluding any rules or procedures governing or permitting class actions.\n\nThe arbitrator, and not any federal, state or local court or agency, shall have exclusive authority to resolve all disputes arising out of or relating to the interpretation, applicability, enforceability or formation of these Terms, including, but not limited to any claim that all or any part of these Terms are void or voidable, or whether a claim is subject to arbitration. The arbitrator shall be empowered to grant whatever relief would be available in a court under law or in equity. The arbitrator’s award shall be written, and binding on the parties and may be entered as a judgment in any court of competent jurisdiction.\n\nThe parties understand that, absent this mandatory provision, they would have the right to sue in court and have a jury trial. They further understand that, in some instances, the costs of arbitration could exceed the costs of litigation and the right to discovery may be more limited in arbitration than in court.\n\n### 13.3 Location ###\n\nBinding arbitration shall take place in New York. You agree to submit to the personal jurisdiction of any federal or state court in New York County, New York, in order to compel arbitration, to stay proceedings pending arbitration, or to confirm, modify, vacate or enter judgment on the award entered by the arbitrator.\n\n### 13.4 Class Action Waiver ###\n\nThe parties further agree that any arbitration shall be conducted in their individual capacities only and not as a class action or other representative action, and the parties expressly waive their right to file a class action or seek relief on a class basis. YOU AND METAMASK AGREE THAT EACH MAY BRING CLAIMS AGAINST THE OTHER ONLY IN YOUR OR ITS INDIVIDUAL CAPACITY, AND NOT AS A PLAINTIFF OR CLASS MEMBER IN ANY PURPORTED CLASS OR REPRESENTATIVE PROCEEDING. If any court or arbitrator determines that the class action waiver set forth in this paragraph is void or unenforceable for any reason or that an arbitration can proceed on a class basis, then the arbitration provision set forth above shall be deemed null and void in its entirety and the parties shall be deemed to have not agreed to arbitrate disputes.\n\n### 13.5 Exception - Litigation of Intellectual Property and Small Claims Court Claims ###\n\nNotwithstanding the parties' decision to resolve all disputes through arbitration, either party may bring an action in state or federal court to protect its intellectual property rights (\"intellectual property rights\" means patents, copyrights, moral rights, trademarks, and trade secrets, but not privacy or publicity rights). Either party may also seek relief in a small claims court for disputes or claims within the scope of that court’s jurisdiction.\n\n### 13.6 30-Day Right to Opt Out ###\n\nYou have the right to opt-out and not be bound by the arbitration and class action waiver provisions set forth above by sending written notice of your decision to opt-out to the following address: MetaMask ℅ ConsenSys, 49 Bogart Street, Brooklyn NY 11206 and via email at legal-opt@metamask.io. The notice must be sent within 30 days of September 6, 2016 or your first use of the Service, whichever is later, otherwise you shall be bound to arbitrate disputes in accordance with the terms of those paragraphs. If you opt-out of these arbitration provisions, MetaMask also will not be bound by them.\n\n### 13.7 Changes to This Section ###\n\nMetaMask will provide 60-days’ notice of any changes to this section. Changes will become effective on the 60th day, and will apply prospectively only to any claims arising after the 60th day.\n\nFor any dispute not subject to arbitration you and MetaMask agree to submit to the personal and exclusive jurisdiction of and venue in the federal and state courts located in New York, New York. You further agree to accept service of process by mail, and hereby waive any and all jurisdictional and venue defenses otherwise available.\n\nThe Terms and the relationship between you and MetaMask shall be governed by the laws of the State of New York without regard to conflict of law provisions.\n\n## 14. General Information ##\n\n### 14.1 Entire Agreement ###\n\nThese Terms (and any additional terms, rules and conditions of participation that MetaMask may post on the Service) constitute the entire agreement between you and MetaMask with respect to the Service and supersedes any prior agreements, oral or written, between you and MetaMask. In the event of a conflict between these Terms and the additional terms, rules and conditions of participation, the latter will prevail over the Terms to the extent of the conflict.\n\n### 14.2 Waiver and Severability of Terms ###\n\nThe failure of MetaMask to exercise or enforce any right or provision of the Terms shall not constitute a waiver of such right or provision. If any provision of the Terms is found by an arbitrator or court of competent jurisdiction to be invalid, the parties nevertheless agree that the arbitrator or court should endeavor to give effect to the parties' intentions as reflected in the provision, and the other provisions of the Terms remain in full force and effect.\n\n### 14.3 Statute of Limitations ###\n\nYou agree that regardless of any statute or law to the contrary, any claim or cause of action arising out of or related to the use of the Service or the Terms must be filed within one (1) year after such claim or cause of action arose or be forever barred.\n\n### 14.4 Section Titles ###\n\nThe section titles in the Terms are for convenience only and have no legal or contractual effect.\n\n### 14.5 Communications ###\n\nUsers with questions, complaints or claims with respect to the Service may contact us using the relevant contact information set forth above and at communications@metamask.io.\n\n## 15 Related Links ##\n\n**[Terms of Use](https://metamask.io/terms.html)**\n\n**[Privacy](https://metamask.io/privacy.html)**\n\n**[Attributions](https://metamask.io/attributions.html)**\n\n", + "id": 0 + }, + "network": "3", + "accounts": { + "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": { + "code": "0x", + "nonce": "0x12", + "balance": "0x6ae7c45a61c0e8d2", + "address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825" + }, + "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb": { + "code": "0x", + "balance": "0x136b2c3ecfdb480", + "nonce": "0x2", + "address": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb" + }, + "0x2f8d4a878cfa04a6e60d46362f5644deab66572d": { + "code": "0x", + "balance": "0x0", + "nonce": "0x0", + "address": "0x2f8d4a878cfa04a6e60d46362f5644deab66572d" + }, + "0x18c643c9cf21027339c8648fbaa2f348ddcbe00a": { + "code": "0x", + "balance": "0x0", + "nonce": "0x0", + "address": "0x18c643c9cf21027339c8648fbaa2f348ddcbe00a" + }, + "0x9858e7d8b79fc3e6d989636721584498926da38a": { + "code": "0x", + "balance": "0x0", + "nonce": "0x0", + "address": "0x9858e7d8b79fc3e6d989636721584498926da38a" + } + }, + "transactions": {}, + "selectedAddressTxList": [ + { + "id": 3870222542191014, + "time": 1487271497135, + "status": "confirmed", + "gasMultiplier": 1, + "metamaskNetworkId": "3", + "txParams": { + "from": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb", + "to": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb", + "value": "0x38d7ea4c68000", + "metamaskId": 3870222542191014, + "metamaskNetworkId": "3", + "gas": "0x5209", + "gasPrice": "0x458d0be6b8", + "nonce": "0x0", + "gasLimit": "0x5209" + }, + "gasLimitSpecified": false, + "estimatedGas": "0x5209", + "txFee": "17e0186e60800", + "txValue": "38d7ea4c68000", + "maxCost": "50b802bac8800", + "hash": "0x45daeb705b5b4dfeaddbd81efc95edfc8adbd229f8adbcb38f33cfc973dcc2ef" + }, + { + "id": 3870222542191015, + "time": 1487271512355, + "status": "confirmed", + "gasMultiplier": 1, + "metamaskNetworkId": "3", + "txParams": { + "from": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb", + "to": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb", + "value": "0x0", + "metamaskId": 3870222542191015, + "metamaskNetworkId": "3", + "gas": "0x5209", + "nonce": "0x01", + "gasPrice": "0x458d0be6b8", + "gasLimit": "0x5209" + }, + "gasLimitSpecified": false, + "estimatedGas": "0x5209", + "txFee": "17e0186e60800", + "txValue": "0", + "maxCost": "17e0186e60800", + "hash": "0x61581e70651fa917c0af0e8b3b2d2bdf7d4cee0f1925518a985ade0fd49b4fa8" + }, + { + "id": 4768706228115573, + "time": 1487363153561, + "status": "unapproved", + "gasMultiplier": 1, + "metamaskNetworkId": "3", + "txParams": { + "from": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb", + "to": "0x18a3462427bcc9133bb46e88bcbe39cd7ef0e761", + "value": "0xde0b6b3a7640000", + "metamaskId": 4768706228115573, + "metamaskNetworkId": "3", + "gas": "0x5209" + }, + "gasLimitSpecified": false, + "estimatedGas": "0x5209", + "txFee": "17e0186e60800", + "txValue": "de0b6b3a7640000", + "maxCost": "de234b52e4a0800" + } + ], + "unapprovedMsgs": {}, + "unapprovedMsgCount": 0, + "keyringTypes": [ + "Simple Key Pair", + "HD Key Tree" + ], + "keyrings": [ + { + "type": "HD Key Tree", + "accounts": [ + "fdea65c8e26263f6d9a1b5de9555d2931a33b825", + "c5b8dbac4c1d3f152cdeb400e2313f309c410acb", + "2f8d4a878cfa04a6e60d46362f5644deab66572d", + "18c643c9cf21027339c8648fbaa2f348ddcbe00a" + ] + }, + { + "type": "Simple Key Pair", + "accounts": [ + "0x9858e7d8b79fc3e6d989636721584498926da38a" + ] + } + ], + "selectedAddress": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb", + "currentCurrency": "USD", + "provider": { + "type": "testnet" + }, + "shapeShiftTxList": [], + "lostAccounts": [] + }, + "appState": { + "menuOpen": false, + "currentView": { + "name": "confTx", + "context": 0 + }, + "accountDetail": { + "subview": "transactions" + }, + "transForward": true, + "isLoading": false, + "warning": null + }, + "identities": {} +} diff --git a/ui/app/components/hex-as-decimal-input.js b/ui/app/components/hex-as-decimal-input.js new file mode 100644 index 000000000..4d3105b8d --- /dev/null +++ b/ui/app/components/hex-as-decimal-input.js @@ -0,0 +1,49 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits +const ethUtil = require('ethereumjs-util') +const BN = ethUtil.BN + +module.exports = HexAsDecimalInput + +inherits(HexAsDecimalInput, Component) +function HexAsDecimalInput () { + Component.call(this) +} + +/* Hex as Decimal Input + * + * A component for allowing easy, decimal editing + * of a passed in hex string value. + * + * On change, calls back its `onChange` function parameter + * and passes it an updated hex string. + */ + +HexAsDecimalInput.prototype.render = function () { + const props = this.props + const { value, onChange } = props + const decimalValue = decimalize(value) + + return ( + h('input', { + value: decimalValue, + onChange: (event) => { + const hexString = hexify(event.target.value) + onChange(hexString) + }, + }) + ) +} + +function hexify (decimalString) { + const hexBN = new BN(decimalString, 10) + return '0x' + hexBN.toString('hex') +} + +function decimalize (input) { + const strippedInput = ethUtil.stripHexPrefix(input) + const inputBN = new BN(strippedInput, 'hex') + return inputBN.toString(10) +} + diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 9eefe1b22..761c75be3 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -2,8 +2,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits const PendingTxDetails = require('./pending-tx-details') -const BN = require('ethereumjs-util').BN -const ethUtil = require('ethereumjs-util') +const HexInput = require('./hex-as-decimal-input') module.exports = PendingTx @@ -13,12 +12,13 @@ function PendingTx () { } PendingTx.prototype.render = function () { - var props = this.props - var state = this.state || {} - var txData = props.txData - var txParams = txData.txParams - var gasValue = state.gas || txParams.gas - var decimalGas = decimalize(gasValue) + const props = this.props + const state = this.state || {} + const txData = props.txData + const txParams = txData.txParams + + const gas = state.gas || txParams.gas + const gasPrice = state.gasPrice || txParams.gasPrice return ( @@ -78,24 +78,22 @@ PendingTx.prototype.render = function () { onClick: props.cancelTransaction, }, 'Reject'), ]), - h('input', { - value: decimalGas, - onChange: (event) => { - const hexString = hexify(event.target.value) - this.setState({ gas: hexString }) - } + + h(HexInput, { + value: gas, + onChange: (newHex) => { + this.setState({ gas: newHex }) + }, }), + + h(HexInput, { + value: gasPrice, + onChange: (newHex) => { + this.setState({ gasPrice: newHex }) + }, + }), + ]) ) } -function decimalize (input) { - const strippedInput = ethUtil.stripHexPrefix(input) - const inputBN = new BN(strippedInput, 'hex') - return inputBN.toString(10) -} - -function hexify (decimalString) { - const hexBN = new BN(decimalString, 10) - return '0x' + hexBN.toString('hex') -} diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index eed1f59ad..831bed0ec 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -36,7 +36,6 @@ function ConfirmTxScreen () { ConfirmTxScreen.prototype.render = function () { var props = this.props - var state = this.state || {} var network = props.network var provider = props.provider From 29f07238f443007a03c968349af4d3fca7e10522 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 21 Feb 2017 09:03:16 -0800 Subject: [PATCH 03/52] Move changelog entry --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d347d2333..cf726a336 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,10 @@ ## Current Master +- Add personal_sign and personal_ecRecover support. + ## 3.3.0 2017-2-20 -- Add personal_sign and personal_ecRecover support. - net_version has been made synchronous. - Test suite for migrations expanded. - Network now changeable from lock screen. From 0584988688a471698e9b3ad05cb0597f0270ea9e Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 21 Feb 2017 14:25:47 -0800 Subject: [PATCH 04/52] Move sigUtil and keyrings to external modules These external modules now have their own test coverage and build enforcement. This allowed me to somewhat more easily add good tests around our personalSign strategy (held now in [eth-sig-util](https://github.com/flyswatter/eth-sig-util), and allow each of the keyrings to import that, etc. --- app/scripts/keyring-controller.js | 35 ++++- app/scripts/keyrings/hd.js | 125 ---------------- app/scripts/keyrings/simple.js | 100 ------------- app/scripts/lib/config-manager.js | 2 +- app/scripts/lib/controllers/preferences.js | 2 +- app/scripts/lib/idStore-migrator.js | 2 +- app/scripts/lib/personal-message-manager.js | 118 ++++++++++++++++ app/scripts/lib/sig-util.js | 28 ---- app/scripts/lib/tx-utils.js | 2 +- app/scripts/metamask-controller.js | 49 ++++++- package.json | 3 + test/unit/keyrings/hd-test.js | 127 ----------------- test/unit/keyrings/simple-test.js | 149 -------------------- test/unit/personal-message-manager-test.js | 89 ++++++++++++ 14 files changed, 294 insertions(+), 537 deletions(-) delete mode 100644 app/scripts/keyrings/hd.js delete mode 100644 app/scripts/keyrings/simple.js create mode 100644 app/scripts/lib/personal-message-manager.js delete mode 100644 app/scripts/lib/sig-util.js delete mode 100644 test/unit/keyrings/hd-test.js delete mode 100644 test/unit/keyrings/simple-test.js create mode 100644 test/unit/personal-message-manager-test.js diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js index b30161003..8c379b5b9 100644 --- a/app/scripts/keyring-controller.js +++ b/app/scripts/keyring-controller.js @@ -5,10 +5,10 @@ const EventEmitter = require('events').EventEmitter const ObservableStore = require('obs-store') const filter = require('promise-filter') const encryptor = require('browser-passworder') -const normalizeAddress = require('./lib/sig-util').normalize +const normalizeAddress = require('eth-sig-util').normalize // Keyrings: -const SimpleKeyring = require('./keyrings/simple') -const HdKeyring = require('./keyrings/hd') +const SimpleKeyring = require('eth-simple-keyring') +const HdKeyring = require('eth-hd-keyring') const keyringTypes = [ SimpleKeyring, HdKeyring, @@ -262,6 +262,35 @@ class KeyringController extends EventEmitter { }) } + // Sign Personal Message + // @object msgParams + // + // returns Promise(@buffer rawSig) + // + // Attempts to sign the provided @object msgParams. + // Prefixes the hash before signing as per the new geth behavior. + signPersonalMessage (msgParams) { + const address = normalizeAddress(msgParams.from) + return this.getKeyringForAccount(address) + .then((keyring) => { + return keyring.signPersonalMessage(address, msgParams.data) + }) + } + + // Recover Personal Message + // @object msgParams + // + // returns Promise(@buffer signer) + // + // recovers a signature of the prefixed-style personalMessage signature. + recoverPersonalMessage (msgParams) { + const address = normalizeAddress(msgParams.from) + return this.getKeyringForAccount(address) + .then((keyring) => { + return keyring.recoverPersonalMessage(address, msgParams.data) + }) + } + // PRIVATE METHODS // // THESE METHODS ARE ONLY USED INTERNALLY TO THE KEYRING-CONTROLLER diff --git a/app/scripts/keyrings/hd.js b/app/scripts/keyrings/hd.js deleted file mode 100644 index 3a66f7868..000000000 --- a/app/scripts/keyrings/hd.js +++ /dev/null @@ -1,125 +0,0 @@ -const EventEmitter = require('events').EventEmitter -const hdkey = require('ethereumjs-wallet/hdkey') -const bip39 = require('bip39') -const ethUtil = require('ethereumjs-util') - -// *Internal Deps -const sigUtil = require('../lib/sig-util') - -// Options: -const hdPathString = `m/44'/60'/0'/0` -const type = 'HD Key Tree' - -class HdKeyring extends EventEmitter { - - /* PUBLIC METHODS */ - - constructor (opts = {}) { - super() - this.type = type - this.deserialize(opts) - } - - serialize () { - return Promise.resolve({ - mnemonic: this.mnemonic, - numberOfAccounts: this.wallets.length, - }) - } - - deserialize (opts = {}) { - this.opts = opts || {} - this.wallets = [] - this.mnemonic = null - this.root = null - - if (opts.mnemonic) { - this._initFromMnemonic(opts.mnemonic) - } - - if (opts.numberOfAccounts) { - return this.addAccounts(opts.numberOfAccounts) - } - - return Promise.resolve([]) - } - - addAccounts (numberOfAccounts = 1) { - if (!this.root) { - this._initFromMnemonic(bip39.generateMnemonic()) - } - - const oldLen = this.wallets.length - const newWallets = [] - for (let i = oldLen; i < numberOfAccounts + oldLen; i++) { - const child = this.root.deriveChild(i) - const wallet = child.getWallet() - newWallets.push(wallet) - this.wallets.push(wallet) - } - const hexWallets = newWallets.map(w => w.getAddress().toString('hex')) - return Promise.resolve(hexWallets) - } - - getAccounts () { - return Promise.resolve(this.wallets.map(w => w.getAddress().toString('hex'))) - } - - // tx is an instance of the ethereumjs-transaction class. - signTransaction (address, tx) { - const wallet = this._getWalletForAccount(address) - var privKey = wallet.getPrivateKey() - tx.sign(privKey) - return Promise.resolve(tx) - } - - // For eth_sign, we need to sign transactions: - // hd - signMessage (withAccount, data) { - const wallet = this._getWalletForAccount(withAccount) - const message = ethUtil.stripHexPrefix(data) - var privKey = wallet.getPrivateKey() - var msgSig = ethUtil.ecsign(new Buffer(message, 'hex'), privKey) - var rawMsgSig = ethUtil.bufferToHex(sigUtil.concatSig(msgSig.v, msgSig.r, msgSig.s)) - return Promise.resolve(rawMsgSig) - } - - // For eth_sign, we need to sign transactions: - newGethSignMessage (withAccount, msgHex) { - const wallet = this._getWalletForAccount(withAccount) - const privKey = wallet.getPrivateKey() - const msgBuffer = ethUtil.toBuffer(msgHex) - const msgHash = ethUtil.hashPersonalMessage(msgBuffer) - const msgSig = ethUtil.ecsign(msgHash, privKey) - const rawMsgSig = ethUtil.bufferToHex(sigUtil.concatSig(msgSig.v, msgSig.r, msgSig.s)) - return Promise.resolve(rawMsgSig) - } - - exportAccount (address) { - const wallet = this._getWalletForAccount(address) - return Promise.resolve(wallet.getPrivateKey().toString('hex')) - } - - - /* PRIVATE METHODS */ - - _initFromMnemonic (mnemonic) { - this.mnemonic = mnemonic - const seed = bip39.mnemonicToSeed(mnemonic) - this.hdWallet = hdkey.fromMasterSeed(seed) - this.root = this.hdWallet.derivePath(hdPathString) - } - - - _getWalletForAccount (account) { - const targetAddress = sigUtil.normalize(account) - return this.wallets.find((w) => { - const address = w.getAddress().toString('hex') - return ((address === targetAddress) || - (sigUtil.normalize(address) === targetAddress)) - }) - } -} - -HdKeyring.type = type -module.exports = HdKeyring diff --git a/app/scripts/keyrings/simple.js b/app/scripts/keyrings/simple.js deleted file mode 100644 index 82881aa2d..000000000 --- a/app/scripts/keyrings/simple.js +++ /dev/null @@ -1,100 +0,0 @@ -const EventEmitter = require('events').EventEmitter -const Wallet = require('ethereumjs-wallet') -const ethUtil = require('ethereumjs-util') -const type = 'Simple Key Pair' -const sigUtil = require('../lib/sig-util') - -class SimpleKeyring extends EventEmitter { - - /* PUBLIC METHODS */ - - constructor (opts) { - super() - this.type = type - this.opts = opts || {} - this.wallets = [] - } - - serialize () { - return Promise.resolve(this.wallets.map(w => w.getPrivateKey().toString('hex'))) - } - - deserialize (privateKeys = []) { - return new Promise((resolve, reject) => { - try { - this.wallets = privateKeys.map((privateKey) => { - const stripped = ethUtil.stripHexPrefix(privateKey) - const buffer = new Buffer(stripped, 'hex') - const wallet = Wallet.fromPrivateKey(buffer) - return wallet - }) - } catch (e) { - reject(e) - } - resolve() - }) - } - - addAccounts (n = 1) { - var newWallets = [] - for (var i = 0; i < n; i++) { - newWallets.push(Wallet.generate()) - } - this.wallets = this.wallets.concat(newWallets) - const hexWallets = newWallets.map(w => ethUtil.bufferToHex(w.getAddress())) - return Promise.resolve(hexWallets) - } - - getAccounts () { - return Promise.resolve(this.wallets.map(w => ethUtil.bufferToHex(w.getAddress()))) - } - - // tx is an instance of the ethereumjs-transaction class. - signTransaction (address, tx) { - const wallet = this._getWalletForAccount(address) - var privKey = wallet.getPrivateKey() - tx.sign(privKey) - return Promise.resolve(tx) - } - - // For eth_sign, we need to sign transactions: - signMessage (withAccount, data) { - const wallet = this._getWalletForAccount(withAccount) - const message = ethUtil.stripHexPrefix(data) - var privKey = wallet.getPrivateKey() - var msgSig = ethUtil.ecsign(new Buffer(message, 'hex'), privKey) - var rawMsgSig = ethUtil.bufferToHex(sigUtil.concatSig(msgSig.v, msgSig.r, msgSig.s)) - return Promise.resolve(rawMsgSig) - } - - // For eth_sign, we need to sign transactions: - - newGethSignMessage (withAccount, msgHex) { - const wallet = this._getWalletForAccount(withAccount) - const privKey = wallet.getPrivateKey() - const msgBuffer = ethUtil.toBuffer(msgHex) - const msgHash = ethUtil.hashPersonalMessage(msgBuffer) - const msgSig = ethUtil.ecsign(msgHash, privKey) - const rawMsgSig = ethUtil.bufferToHex(sigUtil.concatSig(msgSig.v, msgSig.r, msgSig.s)) - return Promise.resolve(rawMsgSig) - } - - exportAccount (address) { - const wallet = this._getWalletForAccount(address) - return Promise.resolve(wallet.getPrivateKey().toString('hex')) - } - - - /* PRIVATE METHODS */ - - _getWalletForAccount (account) { - const address = sigUtil.normalize(account) - let wallet = this.wallets.find(w => ethUtil.bufferToHex(w.getAddress()) === address) - if (!wallet) throw new Error('Simple Keyring - Unable to find matching address.') - return wallet - } - -} - -SimpleKeyring.type = type -module.exports = SimpleKeyring diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index 6267eab68..ea5e49b19 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -1,6 +1,6 @@ const MetamaskConfig = require('../config.js') const ethUtil = require('ethereumjs-util') -const normalize = require('./sig-util').normalize +const normalize = require('eth-sig-util').normalize const TESTNET_RPC = MetamaskConfig.network.testnet const MAINNET_RPC = MetamaskConfig.network.mainnet diff --git a/app/scripts/lib/controllers/preferences.js b/app/scripts/lib/controllers/preferences.js index dc9464c4e..c5e93a5b9 100644 --- a/app/scripts/lib/controllers/preferences.js +++ b/app/scripts/lib/controllers/preferences.js @@ -1,5 +1,5 @@ const ObservableStore = require('obs-store') -const normalizeAddress = require('../sig-util').normalize +const normalizeAddress = require('eth-sig-util').normalize class PreferencesController { diff --git a/app/scripts/lib/idStore-migrator.js b/app/scripts/lib/idStore-migrator.js index 655aed0af..1485beb48 100644 --- a/app/scripts/lib/idStore-migrator.js +++ b/app/scripts/lib/idStore-migrator.js @@ -1,6 +1,6 @@ const IdentityStore = require('./idStore') const HdKeyring = require('../keyrings/hd') -const sigUtil = require('./sig-util') +const sigUtil = require('eth-sig-util') const normalize = sigUtil.normalize const denodeify = require('denodeify') diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js new file mode 100644 index 000000000..72dd1da96 --- /dev/null +++ b/app/scripts/lib/personal-message-manager.js @@ -0,0 +1,118 @@ +const EventEmitter = require('events') +const ObservableStore = require('obs-store') +const ethUtil = require('ethereumjs-util') +const createId = require('./random-id') + + +module.exports = class MessageManager extends EventEmitter{ + constructor (opts) { + super() + this.memStore = new ObservableStore({ + unapprovedPersonalMsgs: {}, + unapprovedPersonalMsgCount: 0, + }) + this.messages = [] + } + + get unapprovedPersonalMsgCount () { + return Object.keys(this.getUnapprovedMsgs()).length + } + + getUnapprovedMsgs () { + return this.messages.filter(msg => msg.status === 'unapproved') + .reduce((result, msg) => { result[msg.id] = msg; return result }, {}) + } + + addUnapprovedMessage (msgParams) { + msgParams.data = normalizeMsgData(msgParams.data) + // create txData obj with parameters and meta data + var time = (new Date()).getTime() + var msgId = createId() + var msgData = { + id: msgId, + msgParams: msgParams, + time: time, + status: 'unapproved', + } + this.addMsg(msgData) + + // signal update + this.emit('update') + return msgId + } + + addMsg (msg) { + this.messages.push(msg) + this._saveMsgList() + } + + getMsg (msgId) { + return this.messages.find(msg => msg.id === msgId) + } + + approveMessage (msgParams) { + this.setMsgStatusApproved(msgParams.metamaskId) + return this.prepMsgForSigning(msgParams) + } + + setMsgStatusApproved (msgId) { + this._setMsgStatus(msgId, 'approved') + } + + setMsgStatusSigned (msgId, rawSig) { + const msg = this.getMsg(msgId) + msg.rawSig = rawSig + this._updateMsg(msg) + this._setMsgStatus(msgId, 'signed') + } + + prepMsgForSigning (msgParams) { + delete msgParams.metamaskId + return Promise.resolve(msgParams) + } + + rejectMsg (msgId) { + this._setMsgStatus(msgId, 'rejected') + } + + // + // PRIVATE METHODS + // + + _setMsgStatus (msgId, status) { + const msg = this.getMsg(msgId) + if (!msg) throw new Error('MessageManager - Message not found for id: "${msgId}".') + msg.status = status + this._updateMsg(msg) + this.emit(`${msgId}:${status}`, msg) + if (status === 'rejected' || status === 'signed') { + this.emit(`${msgId}:finished`, msg) + } + } + + _updateMsg (msg) { + const index = this.messages.findIndex((message) => message.id === msg.id) + if (index !== -1) { + this.messages[index] = msg + } + this._saveMsgList() + } + + _saveMsgList () { + const unapprovedPersonalMsgs = this.getUnapprovedMsgs() + const unapprovedPersonalMsgCount = Object.keys(unapprovedPersonalMsgs).length + this.memStore.updateState({ unapprovedPersonalMsgs, unapprovedPersonalMsgCount }) + this.emit('updateBadge') + } + +} + +function normalizeMsgData(data) { + if (data.slice(0, 2) === '0x') { + // data is already hex + return data + } else { + // data is unicode, convert to hex + return ethUtil.bufferToHex(new Buffer(data, 'utf8')) + } +} diff --git a/app/scripts/lib/sig-util.js b/app/scripts/lib/sig-util.js deleted file mode 100644 index 193dda381..000000000 --- a/app/scripts/lib/sig-util.js +++ /dev/null @@ -1,28 +0,0 @@ -const ethUtil = require('ethereumjs-util') - -module.exports = { - - concatSig: function (v, r, s) { - const rSig = ethUtil.fromSigned(r) - const sSig = ethUtil.fromSigned(s) - const vSig = ethUtil.bufferToInt(v) - const rStr = padWithZeroes(ethUtil.toUnsigned(rSig).toString('hex'), 64) - const sStr = padWithZeroes(ethUtil.toUnsigned(sSig).toString('hex'), 64) - const vStr = ethUtil.stripHexPrefix(ethUtil.intToHex(vSig)) - return ethUtil.addHexPrefix(rStr.concat(sStr, vStr)).toString('hex') - }, - - normalize: function (address) { - if (!address) return - return ethUtil.addHexPrefix(address.toLowerCase()) - }, - -} - -function padWithZeroes (number, length) { - var myString = '' + number - while (myString.length < length) { - myString = '0' + myString - } - return myString -} diff --git a/app/scripts/lib/tx-utils.js b/app/scripts/lib/tx-utils.js index 5116cb93b..240a6ab47 100644 --- a/app/scripts/lib/tx-utils.js +++ b/app/scripts/lib/tx-utils.js @@ -2,7 +2,7 @@ const async = require('async') const EthQuery = require('eth-query') const ethUtil = require('ethereumjs-util') const Transaction = require('ethereumjs-tx') -const normalize = require('./sig-util').normalize +const normalize = require('eth-sig-util').normalize const BN = ethUtil.BN /* diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 29b13dc62..62242bd83 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -16,6 +16,7 @@ const CurrencyController = require('./lib/controllers/currency') const NoticeController = require('./notice-controller') const ShapeShiftController = require('./lib/controllers/shapeshift') const MessageManager = require('./lib/message-manager') +const PersonalMessageManager = require('./lib/personal-message-manager') const TxManager = require('./transaction-manager') const ConfigManager = require('./lib/config-manager') const extension = require('./lib/extension') @@ -23,6 +24,7 @@ const autoFaucet = require('./lib/auto-faucet') const nodeify = require('./lib/nodeify') const IdStoreMigrator = require('./lib/idStore-migrator') const accountImporter = require('./account-import-strategies') +const sigUtil = require('eth-sig-util') const version = require('../manifest.json').version @@ -105,6 +107,7 @@ module.exports = class MetamaskController extends EventEmitter { this.lookupNetwork() this.messageManager = new MessageManager() + this.personalMessageManager = new PersonalMessageManager() this.publicConfigStore = this.initPublicConfigStore() // TEMPORARY UNTIL FULL DEPRECATION: @@ -163,8 +166,13 @@ module.exports = class MetamaskController extends EventEmitter { }, // tx signing processTransaction: (txParams, cb) => this.newUnapprovedTransaction(txParams, cb), - // msg signing + // old style msg signing processMessage: this.newUnsignedMessage.bind(this), + + // new style msg signing + approvePersonalMessage: this.approvePersonalMessage.bind(this), + signPersonalMessage: this.signPersonalMessage.bind(this), + personalRecoverSigner: this.personalRecoverSigner.bind(this), }) return provider } @@ -209,6 +217,7 @@ module.exports = class MetamaskController extends EventEmitter { this.ethStore.getState(), this.txManager.memStore.getState(), this.messageManager.memStore.getState(), + this.personalMessageManager.memStore.getState(), this.keyringController.memStore.getState(), this.preferencesController.store.getState(), this.currencyController.store.getState(), @@ -449,6 +458,44 @@ module.exports = class MetamaskController extends EventEmitter { )(cb) } + // Prefixed Style Message Signing Methods: + approvePersonalMessage (cb) { + let msgId = this.personalMessageManager.addUnapprovedMessage(msgParams) + this.sendUpdate() + this.opts.showUnconfirmedMessage() + this.personalMessageManager.once(`${msgId}:finished`, (data) => { + switch (data.status) { + case 'signed': + return cb(null, data.rawSig) + case 'rejected': + return cb(new Error('MetaMask Message Signature: User denied transaction signature.')) + default: + return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) + } + }) + } + + signPersonalMessage (msgParams) { + const msgId = msgParams.metamaskId + // sets the status op the message to 'approved' + // and removes the metamaskId for signing + return this.personalMessageManager.approveMessage(msgParams) + .then((cleanMsgParams) => { + // signs the message + return this.keyringController.signPersonalMessage(cleanMsgParams) + }) + .then((rawSig) => { + // tells the listener that the message has been signed + // and can be returned to the dapp + this.personalMessageManager.setMsgStatusSigned(msgId, rawSig) + return rawSig + }) + } + + personalRecoverSigner (msgParams) { + const recovered = sigUtil.recoverPersonalSignature(msgParams) + return Promise.resolve(recovered) + } markAccountsFound (cb) { this.configManager.setLostAccounts([]) diff --git a/package.json b/package.json index 9f56d8b12..1542853ad 100644 --- a/package.json +++ b/package.json @@ -52,8 +52,11 @@ "end-of-stream": "^1.1.0", "ensnare": "^1.0.0", "eth-bin-to-ops": "^1.0.1", + "eth-hd-keyring": "^1.1.1", "eth-lightwallet": "^2.3.3", "eth-query": "^1.0.3", + "eth-sig-util": "^1.1.1", + "eth-simple-keyring": "^1.1.0", "ethereumjs-tx": "^1.0.0", "ethereumjs-util": "ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", "ethereumjs-wallet": "^0.6.0", diff --git a/test/unit/keyrings/hd-test.js b/test/unit/keyrings/hd-test.js deleted file mode 100644 index dfc0ec908..000000000 --- a/test/unit/keyrings/hd-test.js +++ /dev/null @@ -1,127 +0,0 @@ -const assert = require('assert') -const extend = require('xtend') -const HdKeyring = require('../../../app/scripts/keyrings/hd') - -// Sample account: -const privKeyHex = 'b8a9c05beeedb25df85f8d641538cbffedf67216048de9c678ee26260eb91952' - -const sampleMnemonic = 'finish oppose decorate face calm tragic certain desk hour urge dinosaur mango' -const firstAcct = '1c96099350f13d558464ec79b9be4445aa0ef579' -const secondAcct = '1b00aed43a693f3a957f9feb5cc08afa031e37a0' - -describe('hd-keyring', function() { - - let keyring - beforeEach(function() { - keyring = new HdKeyring() - }) - - describe('constructor', function(done) { - keyring = new HdKeyring({ - mnemonic: sampleMnemonic, - numberOfAccounts: 2, - }) - - const accounts = keyring.getAccounts() - .then((accounts) => { - assert.equal(accounts[0], firstAcct) - assert.equal(accounts[1], secondAcct) - done() - }) - }) - - describe('Keyring.type', function() { - it('is a class property that returns the type string.', function() { - const type = HdKeyring.type - assert.equal(typeof type, 'string') - }) - }) - - describe('#type', function() { - it('returns the correct value', function() { - const type = keyring.type - const correct = HdKeyring.type - assert.equal(type, correct) - }) - }) - - describe('#serialize empty wallets.', function() { - it('serializes a new mnemonic', function() { - keyring.serialize() - .then((output) => { - assert.equal(output.numberOfAccounts, 0) - assert.equal(output.mnemonic, null) - }) - }) - }) - - describe('#deserialize a private key', function() { - it('serializes what it deserializes', function(done) { - keyring.deserialize({ - mnemonic: sampleMnemonic, - numberOfAccounts: 1 - }) - .then(() => { - assert.equal(keyring.wallets.length, 1, 'restores two accounts') - return keyring.addAccounts(1) - }).then(() => { - return keyring.getAccounts() - }).then((accounts) => { - assert.equal(accounts[0], firstAcct) - assert.equal(accounts[1], secondAcct) - assert.equal(accounts.length, 2) - - return keyring.serialize() - }).then((serialized) => { - assert.equal(serialized.mnemonic, sampleMnemonic) - done() - }) - }) - }) - - describe('#addAccounts', function() { - describe('with no arguments', function() { - it('creates a single wallet', function(done) { - keyring.addAccounts() - .then(() => { - assert.equal(keyring.wallets.length, 1) - done() - }) - }) - }) - - describe('with a numeric argument', function() { - it('creates that number of wallets', function(done) { - keyring.addAccounts(3) - .then(() => { - assert.equal(keyring.wallets.length, 3) - done() - }) - }) - }) - }) - - describe('#getAccounts', function() { - it('calls getAddress on each wallet', function(done) { - - // Push a mock wallet - const desiredOutput = 'foo' - keyring.wallets.push({ - getAddress() { - return { - toString() { - return desiredOutput - } - } - } - }) - - const output = keyring.getAccounts() - .then((output) => { - assert.equal(output[0], desiredOutput) - assert.equal(output.length, 1) - done() - }) - }) - }) -}) diff --git a/test/unit/keyrings/simple-test.js b/test/unit/keyrings/simple-test.js deleted file mode 100644 index ba7dd448a..000000000 --- a/test/unit/keyrings/simple-test.js +++ /dev/null @@ -1,149 +0,0 @@ -const assert = require('assert') -const extend = require('xtend') -const Web3 = require('web3') -const web3 = new Web3() -const ethUtil = require('ethereumjs-util') -const SimpleKeyring = require('../../../app/scripts/keyrings/simple') -const TYPE_STR = 'Simple Key Pair' - -// Sample account: -const privKeyHex = 'b8a9c05beeedb25df85f8d641538cbffedf67216048de9c678ee26260eb91952' - -describe('simple-keyring', function() { - - let keyring - beforeEach(function() { - keyring = new SimpleKeyring() - }) - - describe('Keyring.type', function() { - it('is a class property that returns the type string.', function() { - const type = SimpleKeyring.type - assert.equal(type, TYPE_STR) - }) - }) - - describe('#type', function() { - it('returns the correct value', function() { - const type = keyring.type - assert.equal(type, TYPE_STR) - }) - }) - - describe('#serialize empty wallets.', function() { - it('serializes an empty array', function(done) { - keyring.serialize() - .then((output) => { - assert.deepEqual(output, []) - done() - }) - }) - }) - - describe('#deserialize a private key', function() { - it('serializes what it deserializes', function() { - keyring.deserialize([privKeyHex]) - .then(() => { - assert.equal(keyring.wallets.length, 1, 'has one wallet') - const serialized = keyring.serialize() - assert.equal(serialized[0], privKeyHex) - }) - }) - }) - - describe('#signMessage', function() { - const address = '0x9858e7d8b79fc3e6d989636721584498926da38a' - const message = '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0' - const privateKey = '0x7dd98753d7b4394095de7d176c58128e2ed6ee600abe97c9f6d9fd65015d9b18' - const expectedResult = '0x28fcb6768e5110144a55b2e6ce9d1ea5a58103033632d272d2b5cf506906f7941a00b539383fd872109633d8c71c404e13dba87bc84166ee31b0e36061a69e161c' - - it('passes the dennis test', function(done) { - keyring.deserialize([ privateKey ]) - .then(() => { - return keyring.signMessage(address, message) - }) - .then((result) => { - assert.equal(result, expectedResult) - done() - }) - }) - - it('reliably can decode messages it signs', function (done) { - - const message = 'hello there!' - const msgHashHex = web3.sha3(message) - let address - let addresses = [] - - keyring.deserialize([ privateKey ]) - .then(() => { - keyring.addAccounts(9) - }) - .then(() => { - return keyring.getAccounts() - }) - .then((addrs) => { - addresses = addrs - return Promise.all(addresses.map((address) => { - return keyring.signMessage(address, msgHashHex) - })) - }) - .then((signatures) => { - - signatures.forEach((sgn, index) => { - const address = addresses[index] - - var r = ethUtil.toBuffer(sgn.slice(0,66)) - var s = ethUtil.toBuffer('0x' + sgn.slice(66,130)) - var v = ethUtil.bufferToInt(ethUtil.toBuffer('0x' + sgn.slice(130,132))) - var m = ethUtil.toBuffer(msgHashHex) - var pub = ethUtil.ecrecover(m, v, r, s) - var adr = '0x' + ethUtil.pubToAddress(pub).toString('hex') - - assert.equal(adr, address, 'recovers address from signature correctly') - }) - done() - }) - }) - }) - - describe('#addAccounts', function() { - describe('with no arguments', function() { - it('creates a single wallet', function() { - keyring.addAccounts() - .then(() => { - assert.equal(keyring.wallets.length, 1) - }) - }) - }) - - describe('with a numeric argument', function() { - it('creates that number of wallets', function() { - keyring.addAccounts(3) - .then(() => { - assert.equal(keyring.wallets.length, 3) - }) - }) - }) - }) - - describe('#getAccounts', function() { - it('calls getAddress on each wallet', function(done) { - - // Push a mock wallet - const desiredOutput = '0x18a3462427bcc9133bb46e88bcbe39cd7ef0e761' - keyring.wallets.push({ - getAddress() { - return ethUtil.toBuffer(desiredOutput) - } - }) - - keyring.getAccounts() - .then((output) => { - assert.equal(output[0], desiredOutput) - assert.equal(output.length, 1) - done() - }) - }) - }) -}) diff --git a/test/unit/personal-message-manager-test.js b/test/unit/personal-message-manager-test.js new file mode 100644 index 000000000..657d5e675 --- /dev/null +++ b/test/unit/personal-message-manager-test.js @@ -0,0 +1,89 @@ +const assert = require('assert') +const extend = require('xtend') +const EventEmitter = require('events') + +const PersonalMessageManager = require('../../app/scripts/lib/personal-message-manager') + +describe('Transaction Manager', function() { + let messageManager + + beforeEach(function() { + messageManager = new PersonalMessageManager() + }) + + describe('#getMsgList', function() { + it('when new should return empty array', function() { + var result = messageManager.messages + assert.ok(Array.isArray(result)) + assert.equal(result.length, 0) + }) + it('should also return transactions from local storage if any', function() { + + }) + }) + + describe('#addMsg', function() { + it('adds a Msg returned in getMsgList', function() { + var Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' } + messageManager.addMsg(Msg) + var result = messageManager.messages + assert.ok(Array.isArray(result)) + assert.equal(result.length, 1) + assert.equal(result[0].id, 1) + }) + }) + + describe('#setMsgStatusApproved', function() { + it('sets the Msg status to approved', function() { + var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } + messageManager.addMsg(Msg) + messageManager.setMsgStatusApproved(1) + var result = messageManager.messages + assert.ok(Array.isArray(result)) + assert.equal(result.length, 1) + assert.equal(result[0].status, 'approved') + }) + }) + + describe('#rejectMsg', function() { + it('sets the Msg status to rejected', function() { + var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } + messageManager.addMsg(Msg) + messageManager.rejectMsg(1) + var result = messageManager.messages + assert.ok(Array.isArray(result)) + assert.equal(result.length, 1) + assert.equal(result[0].status, 'rejected') + }) + }) + + describe('#_updateMsg', function() { + it('replaces the Msg with the same id', function() { + messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) + messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) + messageManager._updateMsg({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test' }) + var result = messageManager.getMsg('1') + assert.equal(result.hash, 'foo') + }) + }) + + describe('#getUnapprovedMsgs', function() { + it('returns unapproved Msgs in a hash', function() { + messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) + messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) + let result = messageManager.getUnapprovedMsgs() + assert.equal(typeof result, 'object') + assert.equal(result['1'].status, 'unapproved') + assert.equal(result['2'], undefined) + }) + }) + + describe('#getMsg', function() { + it('returns a Msg with the requested id', function() { + messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) + messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) + assert.equal(messageManager.getMsg('1').status, 'unapproved') + assert.equal(messageManager.getMsg('2').status, 'approved') + }) + }) +}) From 92fb07999a011fa6939c0068f15dd55a6bcd7506 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 21 Feb 2017 14:30:07 -0800 Subject: [PATCH 05/52] Point metamask-controller personalSignRecover method to keyring-controller --- app/scripts/metamask-controller.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 62242bd83..06c133bb2 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -24,7 +24,6 @@ const autoFaucet = require('./lib/auto-faucet') const nodeify = require('./lib/nodeify') const IdStoreMigrator = require('./lib/idStore-migrator') const accountImporter = require('./account-import-strategies') -const sigUtil = require('eth-sig-util') const version = require('../manifest.json').version @@ -152,6 +151,8 @@ module.exports = class MetamaskController extends EventEmitter { // initializeProvider () { + const keyringController = this.keyringController + let provider = MetaMaskProvider({ static: { eth_syncing: false, @@ -171,8 +172,8 @@ module.exports = class MetamaskController extends EventEmitter { // new style msg signing approvePersonalMessage: this.approvePersonalMessage.bind(this), - signPersonalMessage: this.signPersonalMessage.bind(this), - personalRecoverSigner: this.personalRecoverSigner.bind(this), + signPersonalMessage: nodeify(this.signPersonalMessage).bind(this), + personalRecoverSigner: nodeify(keyringController.recoverPersonalMessage).bind(keyringController), }) return provider } @@ -459,7 +460,7 @@ module.exports = class MetamaskController extends EventEmitter { } // Prefixed Style Message Signing Methods: - approvePersonalMessage (cb) { + approvePersonalMessage (msgParams, cb) { let msgId = this.personalMessageManager.addUnapprovedMessage(msgParams) this.sendUpdate() this.opts.showUnconfirmedMessage() @@ -492,11 +493,6 @@ module.exports = class MetamaskController extends EventEmitter { }) } - personalRecoverSigner (msgParams) { - const recovered = sigUtil.recoverPersonalSignature(msgParams) - return Promise.resolve(recovered) - } - markAccountsFound (cb) { this.configManager.setLostAccounts([]) this.sendUpdate() From 6c0916c28dc3d4f8eb449f92393a70545481ce30 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 21 Feb 2017 14:37:01 -0800 Subject: [PATCH 06/52] Fix reference --- app/scripts/lib/idStore-migrator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/lib/idStore-migrator.js b/app/scripts/lib/idStore-migrator.js index 1485beb48..62d21eee7 100644 --- a/app/scripts/lib/idStore-migrator.js +++ b/app/scripts/lib/idStore-migrator.js @@ -1,5 +1,5 @@ const IdentityStore = require('./idStore') -const HdKeyring = require('../keyrings/hd') +const HdKeyring = require('eth-hd-keyring') const sigUtil = require('eth-sig-util') const normalize = sigUtil.normalize const denodeify = require('denodeify') From 8684fc40c78cb5293d85f751cf3927c14067d343 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 21 Feb 2017 14:41:55 -0800 Subject: [PATCH 07/52] Allow provider to init before keyringController --- app/scripts/metamask-controller.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 06c133bb2..d58d1c22d 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -151,7 +151,6 @@ module.exports = class MetamaskController extends EventEmitter { // initializeProvider () { - const keyringController = this.keyringController let provider = MetaMaskProvider({ static: { @@ -173,7 +172,7 @@ module.exports = class MetamaskController extends EventEmitter { // new style msg signing approvePersonalMessage: this.approvePersonalMessage.bind(this), signPersonalMessage: nodeify(this.signPersonalMessage).bind(this), - personalRecoverSigner: nodeify(keyringController.recoverPersonalMessage).bind(keyringController), + personalRecoverSigner: nodeify(this.recoverPersonalMessage).bind(this), }) return provider } @@ -493,6 +492,11 @@ module.exports = class MetamaskController extends EventEmitter { }) } + recoverPersonalMessage (msgParams) { + const keyringController = this.keyringController + return keyringController.recoverPersonalMessage(msgParams) + } + markAccountsFound (cb) { this.configManager.setLostAccounts([]) this.sendUpdate() From 564f920ae0a1be1aa08905f1b4cf6d081e9a5a0b Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 22 Feb 2017 16:23:13 -0800 Subject: [PATCH 08/52] Add personal sign actions and template --- app/scripts/metamask-controller.js | 5 ++ ui/app/actions.js | 22 +++++++++ ui/app/components/pending-personal-msg.js | 56 +++++++++++++++++++++++ ui/app/conf-tx.js | 41 ++++++++--------- ui/lib/tx-helper.js | 13 ++++-- 5 files changed, 111 insertions(+), 26 deletions(-) create mode 100644 ui/app/components/pending-personal-msg.js diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index d58d1c22d..b109918cf 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -241,6 +241,7 @@ module.exports = class MetamaskController extends EventEmitter { const preferencesController = this.preferencesController const txManager = this.txManager const messageManager = this.messageManager + const personalMessageManager = this.personalMessageManager const noticeController = this.noticeController return { @@ -285,6 +286,10 @@ module.exports = class MetamaskController extends EventEmitter { signMessage: this.signMessage.bind(this), cancelMessage: messageManager.rejectMsg.bind(messageManager), + // personalMessageManager + signPersonalMessage: this.signPersonalMessage.bind(this), + cancelPersonalMessage: personalMessageManager.rejectMsg.bind(personalMessageManager), + // notices checkNotices: noticeController.updateNoticesList.bind(noticeController), markNoticeRead: noticeController.markNoticeRead.bind(noticeController), diff --git a/ui/app/actions.js b/ui/app/actions.js index 6552e7f5c..6060d4299 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -90,6 +90,8 @@ var actions = { PREVIOUS_TX: 'PREV_TX', signMsg: signMsg, cancelMsg: cancelMsg, + signPersonalMsg, + cancelPersonalMsg, sendTx: sendTx, signTx: signTx, cancelTx: cancelTx, @@ -359,6 +361,20 @@ function signMsg (msgData) { } } +function signPersonalMsg (msgData) { + return (dispatch) => { + dispatch(actions.showLoadingIndication()) + + if (global.METAMASK_DEBUG) console.log(`background.signMessage`) + background.signPersonalMessage(msgData, (err) => { + dispatch(actions.hideLoadingIndication()) + + if (err) return dispatch(actions.displayWarning(err.message)) + dispatch(actions.completedTx(msgData.metamaskId)) + }) + } +} + function signTx (txData) { return (dispatch) => { if (global.METAMASK_DEBUG) console.log(`background.setGasMultiplier`) @@ -408,6 +424,12 @@ function cancelMsg (msgData) { return actions.completedTx(msgData.id) } +function cancelPersonalMsg (msgData) { + if (global.METAMASK_DEBUG) console.log(`background.cancelMessage`) + background.cancelPersonalMessage(msgData.id) + return actions.completedTx(msgData.id) +} + function cancelTx (txData) { if (global.METAMASK_DEBUG) console.log(`background.cancelTransaction`) background.cancelTransaction(txData.id) diff --git a/ui/app/components/pending-personal-msg.js b/ui/app/components/pending-personal-msg.js new file mode 100644 index 000000000..b2cac164a --- /dev/null +++ b/ui/app/components/pending-personal-msg.js @@ -0,0 +1,56 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits +const PendingTxDetails = require('./pending-msg-details') + +module.exports = PendingMsg + +inherits(PendingMsg, Component) +function PendingMsg () { + Component.call(this) +} + +PendingMsg.prototype.render = function () { + var state = this.props + var msgData = state.txData + + return ( + + h('div', { + key: msgData.id, + }, [ + + // header + h('h3', { + style: { + fontWeight: 'bold', + textAlign: 'center', + }, + }, 'Sign Message'), + + h('.error', { + style: { + margin: '10px', + }, + }, `Signing this message can have + dangerous side effects. Only sign messages from + sites you fully trust with your entire account. + This will be fixed in a future version.`), + + // message details + h(PendingTxDetails, state), + + // sign + cancel + h('.flex-row.flex-space-around', [ + h('button', { + onClick: state.cancelMessage, + }, 'Cancel'), + h('button', { + onClick: state.signMessage, + }, 'Sign'), + ]), + ]) + + ) +} + diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 646dbb602..672ea54ae 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -12,6 +12,7 @@ const BN = ethUtil.BN const PendingTx = require('./components/pending-tx') const PendingMsg = require('./components/pending-msg') +const PendingPersonalMsg = require('./components/pending-personal-msg') module.exports = connect(mapStateToProps)(ConfirmTxScreen) @@ -22,6 +23,7 @@ function mapStateToProps (state) { selectedAddress: state.metamask.selectedAddress, unapprovedTxs: state.metamask.unapprovedTxs, unapprovedMsgs: state.metamask.unapprovedMsgs, + unapprovedPersonalMsgs: state.metamask.unapprovedPersonalMsgs, index: state.appState.currentView.context, warning: state.appState.warning, network: state.metamask.network, @@ -35,15 +37,12 @@ function ConfirmTxScreen () { } ConfirmTxScreen.prototype.render = function () { - var state = this.props + const props = this.props + const { network, provider, unapprovedTxs, + unapprovedMsgs, unapprovedPersonalMsgs } = props - var network = state.network - var provider = state.provider - var unapprovedTxs = state.unapprovedTxs - var unapprovedMsgs = state.unapprovedMsgs - - var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, network) - var index = state.index !== undefined && unconfTxList[index] ? state.index : 0 + var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network) + var index = props.index !== undefined && unconfTxList[index] ? props.index : 0 var txData = unconfTxList[index] || {} var txParams = txData.params || {} var isNotification = isPopupOrNotification() === 'notification' @@ -75,20 +74,20 @@ ConfirmTxScreen.prototype.render = function () { }, [ h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', { style: { - display: state.index === 0 ? 'none' : 'inline-block', + display: props.index === 0 ? 'none' : 'inline-block', }, - onClick: () => state.dispatch(actions.previousTx()), + onClick: () => props.dispatch(actions.previousTx()), }), - ` ${state.index + 1} of ${unconfTxList.length} `, + ` ${props.index + 1} of ${unconfTxList.length} `, h('i.fa.fa-arrow-right.fa-lg.cursor-pointer', { style: { - display: state.index + 1 === unconfTxList.length ? 'none' : 'inline-block', + display: props.index + 1 === unconfTxList.length ? 'none' : 'inline-block', }, - onClick: () => state.dispatch(actions.nextTx()), + onClick: () => props.dispatch(actions.nextTx()), }), ]), - warningIfExists(state.warning), + warningIfExists(props.warning), h(ReactCSSTransitionGroup, { className: 'css-transition-group', @@ -101,12 +100,12 @@ ConfirmTxScreen.prototype.render = function () { // Properties txData: txData, key: txData.id, - selectedAddress: state.selectedAddress, - accounts: state.accounts, - identities: state.identities, + selectedAddress: props.selectedAddress, + accounts: props.accounts, + identities: props.identities, insufficientBalance: this.checkBalanceAgainstTx(txData), // Actions - buyEth: this.buyEth.bind(this, txParams.from || state.selectedAddress), + buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress), sendTransaction: this.sendTransaction.bind(this, txData), cancelTransaction: this.cancelTransaction.bind(this, txData), signMessage: this.signMessage.bind(this, txData), @@ -135,9 +134,9 @@ function currentTxView (opts) { } ConfirmTxScreen.prototype.checkBalanceAgainstTx = function (txData) { if (!txData.txParams) return false - var state = this.props - var address = txData.txParams.from || state.selectedAddress - var account = state.accounts[address] + var props = this.props + var address = txData.txParams.from || props.selectedAddress + var account = props.accounts[address] var balance = account ? account.balance : '0x0' var maxCost = new BN(txData.maxCost, 16) diff --git a/ui/lib/tx-helper.js b/ui/lib/tx-helper.js index 7f64f9fbe..c8dc46c9d 100644 --- a/ui/lib/tx-helper.js +++ b/ui/lib/tx-helper.js @@ -1,13 +1,16 @@ const valuesFor = require('../app/util').valuesFor -module.exports = function (unapprovedTxs, unapprovedMsgs, network) { +module.exports = function (unapprovedTxs, unapprovedMsgs, personalMsgs, network) { log.debug('tx-helper called with params:') - log.debug({ unapprovedTxs, unapprovedMsgs, network }) + log.debug({ unapprovedTxs, unapprovedMsgs, personalMsgs, network }) - var txValues = network ? valuesFor(unapprovedTxs).filter(tx => tx.txParams.metamaskNetworkId === network) : valuesFor(unapprovedTxs) + const txValues = network ? valuesFor(unapprovedTxs).filter(tx => tx.txParams.metamaskNetworkId === network) : valuesFor(unapprovedTxs) log.debug(`tx helper found ${txValues.length} unapproved txs`) - var msgValues = valuesFor(unapprovedMsgs) + const msgValues = valuesFor(unapprovedMsgs) log.debug(`tx helper found ${msgValues.length} unsigned messages`) - var allValues = txValues.concat(msgValues) + let allValues = txValues.concat(msgValues) + const personalValues = valuesFor(personalMsgs) + allValues = allValues.concat(personalValues) + return allValues.sort(tx => tx.time) } From 7ec25526b70473247a69ab4a3a1302e50b06f12b Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 23 Feb 2017 11:18:49 -0800 Subject: [PATCH 09/52] Add alternate UI for pending personal_sign messages --- app/scripts/lib/message-manager.js | 3 ++- app/scripts/lib/personal-message-manager.js | 1 + ui/app/components/pending-personal-msg.js | 9 --------- ui/app/conf-tx.js | 19 +++++++++++++------ 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/app/scripts/lib/message-manager.js b/app/scripts/lib/message-manager.js index ceaf8ee2f..711d5f159 100644 --- a/app/scripts/lib/message-manager.js +++ b/app/scripts/lib/message-manager.js @@ -33,6 +33,7 @@ module.exports = class MessageManager extends EventEmitter{ msgParams: msgParams, time: time, status: 'unapproved', + type: 'eth_sign', } this.addMsg(msgData) @@ -115,4 +116,4 @@ function normalizeMsgData(data) { // data is unicode, convert to hex return ethUtil.bufferToHex(new Buffer(data, 'utf8')) } -} \ No newline at end of file +} diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js index 72dd1da96..65ad9200a 100644 --- a/app/scripts/lib/personal-message-manager.js +++ b/app/scripts/lib/personal-message-manager.js @@ -33,6 +33,7 @@ module.exports = class MessageManager extends EventEmitter{ msgParams: msgParams, time: time, status: 'unapproved', + type: 'personal_sign', } this.addMsg(msgData) diff --git a/ui/app/components/pending-personal-msg.js b/ui/app/components/pending-personal-msg.js index b2cac164a..f4bde91dc 100644 --- a/ui/app/components/pending-personal-msg.js +++ b/ui/app/components/pending-personal-msg.js @@ -28,15 +28,6 @@ PendingMsg.prototype.render = function () { }, }, 'Sign Message'), - h('.error', { - style: { - margin: '10px', - }, - }, `Signing this message can have - dangerous side effects. Only sign messages from - sites you fully trust with your entire account. - This will be fixed in a future version.`), - // message details h(PendingTxDetails, state), diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 672ea54ae..571ae85b6 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -118,18 +118,25 @@ ConfirmTxScreen.prototype.render = function () { } function currentTxView (opts) { - const { txData } = opts - const { txParams, msgParams } = txData - log.info('rendering current tx view') + const { txData } = opts + const { txParams, msgParams, type } = txData + if (txParams) { - // This is a pending transaction log.debug('txParams detected, rendering pending tx') return h(PendingTx, opts) + } else if (msgParams) { - // This is a pending message to sign log.debug('msgParams detected, rendering pending msg') - return h(PendingMsg, opts) + + if (type === 'eth_sign') { + log.debug('rendering eth_sign message') + return h(PendingMsg, opts) + + } else if (type === 'personal_sign') { + log.debug('rendering personal_sign message') + return h(PendingPersonalMsg, opts) + } } } ConfirmTxScreen.prototype.checkBalanceAgainstTx = function (txData) { From 4697aca02c669b1787e72f0648b3043270867799 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 23 Feb 2017 14:23:45 -0800 Subject: [PATCH 10/52] Got personal_sign working Also fixed bug where signing would not close popup. --- app/scripts/background.js | 4 + app/scripts/lib/personal-message-manager.js | 4 +- app/scripts/metamask-controller.js | 55 ++++++++----- ui/app/actions.js | 77 +++++++++++-------- ui/app/app.js | 2 + .../pending-personal-msg-details.js | 50 ++++++++++++ ui/app/components/pending-personal-msg.js | 4 +- ui/app/conf-tx.js | 14 +++- ui/app/reducers/app.js | 30 +++++--- ui/index.js | 3 +- ui/lib/tx-helper.js | 1 + 11 files changed, 175 insertions(+), 69 deletions(-) create mode 100644 ui/app/components/pending-personal-msg-details.js diff --git a/app/scripts/background.js b/app/scripts/background.js index 2e5a992b9..254737dec 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -15,6 +15,10 @@ const firstTimeState = require('./first-time-state') const STORAGE_KEY = 'metamask-config' const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' +const log = require('loglevel') +window.log = log +log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn') + let popupIsOpen = false // state persistence diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js index 65ad9200a..3b8510767 100644 --- a/app/scripts/lib/personal-message-manager.js +++ b/app/scripts/lib/personal-message-manager.js @@ -4,7 +4,7 @@ const ethUtil = require('ethereumjs-util') const createId = require('./random-id') -module.exports = class MessageManager extends EventEmitter{ +module.exports = class PersonalMessageManager extends EventEmitter{ constructor (opts) { super() this.memStore = new ObservableStore({ @@ -82,7 +82,7 @@ module.exports = class MessageManager extends EventEmitter{ _setMsgStatus (msgId, status) { const msg = this.getMsg(msgId) - if (!msg) throw new Error('MessageManager - Message not found for id: "${msgId}".') + if (!msg) throw new Error('PersonalMessageManager - Message not found for id: "${msgId}".') msg.status = status this._updateMsg(msg) this.emit(`${msgId}:${status}`, msg) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index b109918cf..c301e2035 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -170,8 +170,7 @@ module.exports = class MetamaskController extends EventEmitter { processMessage: this.newUnsignedMessage.bind(this), // new style msg signing - approvePersonalMessage: this.approvePersonalMessage.bind(this), - signPersonalMessage: nodeify(this.signPersonalMessage).bind(this), + processPersonalMessage: this.newUnsignedPersonalMessage.bind(this), personalRecoverSigner: nodeify(this.recoverPersonalMessage).bind(this), }) return provider @@ -283,11 +282,11 @@ module.exports = class MetamaskController extends EventEmitter { cancelTransaction: txManager.cancelTransaction.bind(txManager), // messageManager - signMessage: this.signMessage.bind(this), + signMessage: nodeify(this.signMessage).bind(this), cancelMessage: messageManager.rejectMsg.bind(messageManager), // personalMessageManager - signPersonalMessage: this.signPersonalMessage.bind(this), + signPersonalMessage: nodeify(this.signPersonalMessage).bind(this), cancelPersonalMessage: personalMessageManager.rejectMsg.bind(personalMessageManager), // notices @@ -445,22 +444,39 @@ module.exports = class MetamaskController extends EventEmitter { }) } + newUnsignedPersonalMessage (msgParams, cb) { + let msgId = this.personalMessageManager.addUnapprovedMessage(msgParams) + this.sendUpdate() + this.opts.showUnconfirmedMessage() + this.personalMessageManager.once(`${msgId}:finished`, (data) => { + switch (data.status) { + case 'signed': + return cb(null, data.rawSig) + case 'rejected': + return cb(new Error('MetaMask Message Signature: User denied transaction signature.')) + default: + return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) + } + }) + } + signMessage (msgParams, cb) { + log.info('MetaMaskController - signMessage') const msgId = msgParams.metamaskId - promiseToCallback( - // sets the status op the message to 'approved' - // and removes the metamaskId for signing - this.messageManager.approveMessage(msgParams) - .then((cleanMsgParams) => { - // signs the message - return this.keyringController.signMessage(cleanMsgParams) - }) - .then((rawSig) => { - // tells the listener that the message has been signed - // and can be returned to the dapp - this.messageManager.setMsgStatusSigned(msgId, rawSig) - }) - )(cb) + + // sets the status op the message to 'approved' + // and removes the metamaskId for signing + return this.messageManager.approveMessage(msgParams) + .then((cleanMsgParams) => { + // signs the message + return this.keyringController.signMessage(cleanMsgParams) + }) + .then((rawSig) => { + // tells the listener that the message has been signed + // and can be returned to the dapp + this.messageManager.setMsgStatusSigned(msgId, rawSig) + return this.getState() + }) } // Prefixed Style Message Signing Methods: @@ -481,6 +497,7 @@ module.exports = class MetamaskController extends EventEmitter { } signPersonalMessage (msgParams) { + log.info('MetaMaskController - signPersonalMessage') const msgId = msgParams.metamaskId // sets the status op the message to 'approved' // and removes the metamaskId for signing @@ -493,7 +510,7 @@ module.exports = class MetamaskController extends EventEmitter { // tells the listener that the message has been signed // and can be returned to the dapp this.personalMessageManager.setMsgStatusSigned(msgId, rawSig) - return rawSig + return this.getState() }) } diff --git a/ui/app/actions.js b/ui/app/actions.js index 6060d4299..12ee0367a 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -180,7 +180,7 @@ function tryUnlockMetamask (password) { return (dispatch) => { dispatch(actions.showLoadingIndication()) dispatch(actions.unlockInProgress()) - if (global.METAMASK_DEBUG) console.log(`background.submitPassword`) + log.debug(`background.submitPassword`) background.submitPassword(password, (err) => { dispatch(actions.hideLoadingIndication()) if (err) { @@ -208,7 +208,7 @@ function transitionBackward () { function confirmSeedWords () { return (dispatch) => { dispatch(actions.showLoadingIndication()) - if (global.METAMASK_DEBUG) console.log(`background.clearSeedWordCache`) + log.debug(`background.clearSeedWordCache`) background.clearSeedWordCache((err, account) => { dispatch(actions.hideLoadingIndication()) if (err) { @@ -224,7 +224,7 @@ function confirmSeedWords () { function createNewVaultAndRestore (password, seed) { return (dispatch) => { dispatch(actions.showLoadingIndication()) - if (global.METAMASK_DEBUG) console.log(`background.createNewVaultAndRestore`) + log.debug(`background.createNewVaultAndRestore`) background.createNewVaultAndRestore(password, seed, (err) => { dispatch(actions.hideLoadingIndication()) if (err) return dispatch(actions.displayWarning(err.message)) @@ -236,12 +236,12 @@ function createNewVaultAndRestore (password, seed) { function createNewVaultAndKeychain (password) { return (dispatch) => { dispatch(actions.showLoadingIndication()) - if (global.METAMASK_DEBUG) console.log(`background.createNewVaultAndKeychain`) + log.debug(`background.createNewVaultAndKeychain`) background.createNewVaultAndKeychain(password, (err) => { if (err) { return dispatch(actions.displayWarning(err.message)) } - if (global.METAMASK_DEBUG) console.log(`background.placeSeedWords`) + log.debug(`background.placeSeedWords`) background.placeSeedWords((err) => { if (err) { return dispatch(actions.displayWarning(err.message)) @@ -262,10 +262,10 @@ function revealSeedConfirmation () { function requestRevealSeed (password) { return (dispatch) => { dispatch(actions.showLoadingIndication()) - if (global.METAMASK_DEBUG) console.log(`background.submitPassword`) + log.debug(`background.submitPassword`) background.submitPassword(password, (err) => { if (err) return dispatch(actions.displayWarning(err.message)) - if (global.METAMASK_DEBUG) console.log(`background.placeSeedWords`) + log.debug(`background.placeSeedWords`) background.placeSeedWords((err) => { if (err) return dispatch(actions.displayWarning(err.message)) dispatch(actions.hideLoadingIndication()) @@ -277,7 +277,7 @@ function requestRevealSeed (password) { function addNewKeyring (type, opts) { return (dispatch) => { dispatch(actions.showLoadingIndication()) - if (global.METAMASK_DEBUG) console.log(`background.addNewKeyring`) + log.debug(`background.addNewKeyring`) background.addNewKeyring(type, opts, (err) => { dispatch(actions.hideLoadingIndication()) if (err) return dispatch(actions.displayWarning(err.message)) @@ -289,11 +289,11 @@ function addNewKeyring (type, opts) { function importNewAccount (strategy, args) { return (dispatch) => { dispatch(actions.showLoadingIndication('This may take a while, be patient.')) - if (global.METAMASK_DEBUG) console.log(`background.importAccountWithStrategy`) + log.debug(`background.importAccountWithStrategy`) background.importAccountWithStrategy(strategy, args, (err) => { dispatch(actions.hideLoadingIndication()) if (err) return dispatch(actions.displayWarning(err.message)) - if (global.METAMASK_DEBUG) console.log(`background.getState`) + log.debug(`background.getState`) background.getState((err, newState) => { if (err) { return dispatch(actions.displayWarning(err.message)) @@ -315,7 +315,7 @@ function navigateToNewAccountScreen() { } function addNewAccount () { - if (global.METAMASK_DEBUG) console.log(`background.addNewAccount`) + log.debug(`background.addNewAccount`) return callBackgroundThenUpdate(background.addNewAccount) } @@ -328,7 +328,7 @@ function showInfoPage () { function setCurrentFiat (currencyCode) { return (dispatch) => { dispatch(this.showLoadingIndication()) - if (global.METAMASK_DEBUG) console.log(`background.setCurrentFiat`) + log.debug(`background.setCurrentFiat`) background.setCurrentCurrency(currencyCode, (err, data) => { dispatch(this.hideLoadingIndication()) if (err) { @@ -348,28 +348,38 @@ function setCurrentFiat (currencyCode) { } function signMsg (msgData) { + log.debug('action - signMsg') return (dispatch) => { dispatch(actions.showLoadingIndication()) - if (global.METAMASK_DEBUG) console.log(`background.signMessage`) - background.signMessage(msgData, (err) => { + log.debug(`actions calling background.signMessage`) + background.signMessage(msgData, (err, newState) => { + log.debug('signMessage called back') + dispatch(actions.updateMetamaskState(newState)) dispatch(actions.hideLoadingIndication()) + if (err) log.error(err) if (err) return dispatch(actions.displayWarning(err.message)) + dispatch(actions.completedTx(msgData.metamaskId)) }) } } function signPersonalMsg (msgData) { + log.debug('action - signPersonalMsg') return (dispatch) => { dispatch(actions.showLoadingIndication()) - if (global.METAMASK_DEBUG) console.log(`background.signMessage`) - background.signPersonalMessage(msgData, (err) => { + log.debug(`actions calling background.signPersonalMessage`) + background.signPersonalMessage(msgData, (err, newState) => { + log.debug('signPersonalMessage called back') + dispatch(actions.updateMetamaskState(newState)) dispatch(actions.hideLoadingIndication()) + if (err) log.error(err) if (err) return dispatch(actions.displayWarning(err.message)) + dispatch(actions.completedTx(msgData.metamaskId)) }) } @@ -377,7 +387,7 @@ function signPersonalMsg (msgData) { function signTx (txData) { return (dispatch) => { - if (global.METAMASK_DEBUG) console.log(`background.setGasMultiplier`) + log.debug(`background.setGasMultiplier`) background.setGasMultiplier(txData.gasMultiplier, (err) => { if (err) return dispatch(actions.displayWarning(err.message)) web3.eth.sendTransaction(txData, (err, data) => { @@ -392,8 +402,9 @@ function signTx (txData) { } function sendTx (txData) { + log.info('actions: sendTx') return (dispatch) => { - if (global.METAMASK_DEBUG) console.log(`background.approveTransaction`) + log.debug(`actions calling background.approveTransaction`) background.approveTransaction(txData.id, (err) => { if (err) { dispatch(actions.txError(err)) @@ -419,19 +430,19 @@ function txError (err) { } function cancelMsg (msgData) { - if (global.METAMASK_DEBUG) console.log(`background.cancelMessage`) + log.debug(`background.cancelMessage`) background.cancelMessage(msgData.id) return actions.completedTx(msgData.id) } function cancelPersonalMsg (msgData) { - if (global.METAMASK_DEBUG) console.log(`background.cancelMessage`) + log.debug(`background.cancelMessage`) background.cancelPersonalMessage(msgData.id) return actions.completedTx(msgData.id) } function cancelTx (txData) { - if (global.METAMASK_DEBUG) console.log(`background.cancelTransaction`) + log.debug(`background.cancelTransaction`) background.cancelTransaction(txData.id) return actions.completedTx(txData.id) } @@ -527,14 +538,14 @@ function updateMetamaskState (newState) { } function lockMetamask () { - if (global.METAMASK_DEBUG) console.log(`background.setLocked`) + log.debug(`background.setLocked`) return callBackgroundThenUpdate(background.setLocked) } function showAccountDetail (address) { return (dispatch) => { dispatch(actions.showLoadingIndication()) - if (global.METAMASK_DEBUG) console.log(`background.setSelectedAddress`) + log.debug(`background.setSelectedAddress`) background.setSelectedAddress(address, (err) => { dispatch(actions.hideLoadingIndication()) if (err) { @@ -607,7 +618,7 @@ function goBackToInitView () { function markNoticeRead (notice) { return (dispatch) => { dispatch(this.showLoadingIndication()) - if (global.METAMASK_DEBUG) console.log(`background.markNoticeRead`) + log.debug(`background.markNoticeRead`) background.markNoticeRead(notice, (err, notice) => { dispatch(this.hideLoadingIndication()) if (err) { @@ -639,7 +650,7 @@ function clearNotices () { } function markAccountsFound() { - if (global.METAMASK_DEBUG) console.log(`background.markAccountsFound`) + log.debug(`background.markAccountsFound`) return callBackgroundThenUpdate(background.markAccountsFound) } @@ -648,7 +659,7 @@ function markAccountsFound() { // function setRpcTarget (newRpc) { - if (global.METAMASK_DEBUG) console.log(`background.setRpcTarget`) + log.debug(`background.setRpcTarget`) background.setRpcTarget(newRpc) return { type: actions.SET_RPC_TARGET, @@ -657,7 +668,7 @@ function setRpcTarget (newRpc) { } function setProviderType (type) { - if (global.METAMASK_DEBUG) console.log(`background.setProviderType`) + log.debug(`background.setProviderType`) background.setProviderType(type) return { type: actions.SET_PROVIDER_TYPE, @@ -666,7 +677,7 @@ function setProviderType (type) { } function useEtherscanProvider () { - if (global.METAMASK_DEBUG) console.log(`background.useEtherscanProvider`) + log.debug(`background.useEtherscanProvider`) background.useEtherscanProvider() return { type: actions.USE_ETHERSCAN_PROVIDER, @@ -723,7 +734,7 @@ function exportAccount (address) { return function (dispatch) { dispatch(self.showLoadingIndication()) - if (global.METAMASK_DEBUG) console.log(`background.exportAccount`) + log.debug(`background.exportAccount`) background.exportAccount(address, function (err, result) { dispatch(self.hideLoadingIndication()) @@ -747,7 +758,7 @@ function showPrivateKey (key) { function saveAccountLabel (account, label) { return (dispatch) => { dispatch(actions.showLoadingIndication()) - if (global.METAMASK_DEBUG) console.log(`background.saveAccountLabel`) + log.debug(`background.saveAccountLabel`) background.saveAccountLabel(account, label, (err) => { dispatch(actions.hideLoadingIndication()) if (err) { @@ -769,7 +780,7 @@ function showSendPage () { function buyEth (address, amount) { return (dispatch) => { - if (global.METAMASK_DEBUG) console.log(`background.buyEth`) + log.debug(`background.buyEth`) background.buyEth(address, amount) dispatch({ type: actions.BUY_ETH, @@ -849,7 +860,7 @@ function coinShiftRquest (data, marketData) { if (response.error) return dispatch(actions.displayWarning(response.error)) var message = ` Deposit your ${response.depositType} to the address bellow:` - if (global.METAMASK_DEBUG) console.log(`background.createShapeShiftTx`) + log.debug(`background.createShapeShiftTx`) background.createShapeShiftTx(response.deposit, response.depositType) dispatch(actions.showQrView(response.deposit, [message].concat(marketData))) }) @@ -929,7 +940,7 @@ function callBackgroundThenUpdate (method, ...args) { } function forceUpdateMetamaskState(dispatch){ - if (global.METAMASK_DEBUG) console.log(`background.getState`) + log.debug(`background.getState`) background.getState((err, newState) => { if (err) { return dispatch(actions.displayWarning(err.message)) diff --git a/ui/app/app.js b/ui/app/app.js index 6e249b09e..63fab5db8 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -64,6 +64,7 @@ function mapStateToProps (state) { App.prototype.render = function () { var props = this.props const { isLoading, loadingMessage, transForward } = props + log.debug('Main ui render function') return ( @@ -347,6 +348,7 @@ App.prototype.renderBackButton = function (style, justArrow = false) { } App.prototype.renderPrimary = function () { + log.debug('rendering primary') var props = this.props // notices diff --git a/ui/app/components/pending-personal-msg-details.js b/ui/app/components/pending-personal-msg-details.js new file mode 100644 index 000000000..16308d121 --- /dev/null +++ b/ui/app/components/pending-personal-msg-details.js @@ -0,0 +1,50 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits + +const AccountPanel = require('./account-panel') + +module.exports = PendingMsgDetails + +inherits(PendingMsgDetails, Component) +function PendingMsgDetails () { + Component.call(this) +} + +PendingMsgDetails.prototype.render = function () { + var state = this.props + var msgData = state.txData + + var msgParams = msgData.msgParams || {} + var address = msgParams.from || state.selectedAddress + var identity = state.identities[address] || { address: address } + var account = state.accounts[address] || { address: address } + + return ( + h('div', { + key: msgData.id, + style: { + margin: '10px 20px', + }, + }, [ + + // account that will sign + h(AccountPanel, { + showFullAddress: true, + identity: identity, + account: account, + imageifyIdenticons: state.imageifyIdenticons, + }), + + // message data + h('.tx-data.flex-column.flex-justify-center.flex-grow.select-none', [ + h('.flex-row.flex-space-between', [ + h('label.font-small', 'MESSAGE'), + h('span.font-small', msgParams.data), + ]), + ]), + + ]) + ) +} + diff --git a/ui/app/components/pending-personal-msg.js b/ui/app/components/pending-personal-msg.js index f4bde91dc..d48dd5ecc 100644 --- a/ui/app/components/pending-personal-msg.js +++ b/ui/app/components/pending-personal-msg.js @@ -1,7 +1,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits -const PendingTxDetails = require('./pending-msg-details') +const PendingTxDetails = require('./pending-personal-msg-details') module.exports = PendingMsg @@ -37,7 +37,7 @@ PendingMsg.prototype.render = function () { onClick: state.cancelMessage, }, 'Cancel'), h('button', { - onClick: state.signMessage, + onClick: state.signPersonalMessage, }, 'Sign'), ]), ]) diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 571ae85b6..a2e5ee94c 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -109,6 +109,7 @@ ConfirmTxScreen.prototype.render = function () { sendTransaction: this.sendTransaction.bind(this, txData), cancelTransaction: this.cancelTransaction.bind(this, txData), signMessage: this.signMessage.bind(this, txData), + signPersonalMessage: this.signPersonalMessage.bind(this, txData), cancelMessage: this.cancelMessage.bind(this, txData), }), @@ -167,13 +168,24 @@ ConfirmTxScreen.prototype.cancelTransaction = function (txData, event) { } ConfirmTxScreen.prototype.signMessage = function (msgData, event) { + log.info('conf-tx.js: signing message') var params = msgData.msgParams + var type = msgData.type params.metamaskId = msgData.id event.stopPropagation() this.props.dispatch(actions.signMsg(params)) } +ConfirmTxScreen.prototype.signPersonalMessage = function (msgData, event) { + log.info('conf-tx.js: signing personal message') + var params = msgData.msgParams + params.metamaskId = msgData.id + event.stopPropagation() + this.props.dispatch(actions.signPersonalMsg(params)) +} + ConfirmTxScreen.prototype.cancelMessage = function (msgData, event) { + log.info('canceling message') event.stopPropagation() this.props.dispatch(actions.cancelMsg(msgData)) } @@ -185,7 +197,7 @@ ConfirmTxScreen.prototype.goHome = function (event) { function warningIfExists (warning) { if (warning && - // Do not display user rejections on this screen: + // Do not display user rejections on this screen: warning.indexOf('User denied transaction signature') === -1) { return h('.error', { style: { diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index de6536c2e..6d92764f1 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -6,6 +6,7 @@ const notification = require('../../../app/scripts/lib/notifications') module.exports = reduceApp function reduceApp (state, action) { + log.debug('App Reducer got ' + action.type) // clone and defaults const selectedAddress = state.metamask.selectedAddress const pendingTxs = hasPendingTxs(state) @@ -289,32 +290,36 @@ function reduceApp (state, action) { case actions.SHOW_CONF_TX_PAGE: return extend(appState, { currentView: { - name: 'confTx', + name: pendingTxs ? 'confTx' : 'account-detail', context: 0, }, transForward: action.transForward, warning: null, + isLoading: false, }) case actions.SHOW_CONF_MSG_PAGE: return extend(appState, { currentView: { - name: 'confTx', + name: pendingTxs ? 'confTx' : 'account-detail', context: 0, }, transForward: true, warning: null, + isLoading: false, }) case actions.COMPLETED_TX: - var unapprovedTxs = state.metamask.unapprovedTxs - var unapprovedMsgs = state.metamask.unapprovedMsgs - var network = state.metamask.network + log.debug('reducing COMPLETED_TX') + var { unapprovedTxs, unapprovedMsgs, + unapprovedPersonalMsgs, network } = state.metamask - var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, network) - .filter(tx => tx !== tx.id) + var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network) + .filter(tx => tx !== tx.id) + log.debug(`actions - COMPLETED_TX with ${unconfTxList.length} txs`) if (unconfTxList && unconfTxList.length > 0) { + log.debug('reducer detected txs - rendering confTx view') return extend(appState, { transForward: false, currentView: { @@ -324,6 +329,7 @@ function reduceApp (state, action) { warning: null, }) } else { + log.debug('attempting to close popup') notification.closePopup() return extend(appState, { @@ -572,10 +578,12 @@ function reduceApp (state, action) { } function hasPendingTxs (state) { - var unapprovedTxs = state.metamask.unapprovedTxs - var unapprovedMsgs = state.metamask.unapprovedMsgs - var network = state.metamask.network - var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, network) + var { unapprovedTxs, unapprovedMsgs, + unapprovedPersonalMsgs, network } = state.metamask + + var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network) + var has = unconfTxList.length > 0 + log.debug('checking if state has pending txs, concluded ' + has) return unconfTxList.length > 0 } diff --git a/ui/index.js b/ui/index.js index 844e6c417..6b65f12d4 100644 --- a/ui/index.js +++ b/ui/index.js @@ -6,9 +6,10 @@ const configureStore = require('./app/store') const txHelper = require('./lib/tx-helper') module.exports = launchApp +let debugMode = window.METAMASK_DEBUG const log = require('loglevel') window.log = log -log.setLevel('warn') +log.setLevel(debugMode ? 'debug' : 'warn') function launchApp (opts) { var accountManager = opts.accountManager diff --git a/ui/lib/tx-helper.js b/ui/lib/tx-helper.js index c8dc46c9d..2eefdff68 100644 --- a/ui/lib/tx-helper.js +++ b/ui/lib/tx-helper.js @@ -10,6 +10,7 @@ module.exports = function (unapprovedTxs, unapprovedMsgs, personalMsgs, network) log.debug(`tx helper found ${msgValues.length} unsigned messages`) let allValues = txValues.concat(msgValues) const personalValues = valuesFor(personalMsgs) + log.debug(`tx helper found ${personalValues.length} unsigned personal messages`) allValues = allValues.concat(personalValues) return allValues.sort(tx => tx.time) From 1d1d296a1eec2fc66927dd80bf0f1bbd1a6841cf Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 23 Feb 2017 14:40:18 -0800 Subject: [PATCH 11/52] Make personal sign view look nice --- .../pending-personal-msg-details.js | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/ui/app/components/pending-personal-msg-details.js b/ui/app/components/pending-personal-msg-details.js index 16308d121..ffd11ca0b 100644 --- a/ui/app/components/pending-personal-msg-details.js +++ b/ui/app/components/pending-personal-msg-details.js @@ -20,6 +20,8 @@ PendingMsgDetails.prototype.render = function () { var identity = state.identities[address] || { address: address } var account = state.accounts[address] || { address: address } + var { data } = msgParams + return ( h('div', { key: msgData.id, @@ -37,11 +39,20 @@ PendingMsgDetails.prototype.render = function () { }), // message data - h('.tx-data.flex-column.flex-justify-center.flex-grow.select-none', [ - h('.flex-row.flex-space-between', [ - h('label.font-small', 'MESSAGE'), - h('span.font-small', msgParams.data), - ]), + h('div', [ + h('label.font-small', { style: { display: 'block' } }, 'MESSAGE'), + h('textarea.font-small', { + readOnly: true, + style: { + width: '315px', + maxHeight: '210px', + resize: 'none', + border: 'none', + background: 'white', + padding: '3px', + }, + defaultValue: data, + }), ]), ]) From 961a83769bd46334f5ecf72d00a32730d19866c3 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 23 Feb 2017 16:00:43 -0800 Subject: [PATCH 12/52] Fix cancel msg signing behavior. --- app/scripts/metamask-controller.js | 31 ++++++++++++++++++----- ui/app/actions.js | 8 +++--- ui/app/components/pending-personal-msg.js | 2 +- ui/app/conf-tx.js | 8 +++++- ui/app/reducers/app.js | 15 ++++++----- 5 files changed, 45 insertions(+), 19 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index c301e2035..eace72c24 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -139,6 +139,7 @@ module.exports = class MetamaskController extends EventEmitter { this.ethStore.subscribe(this.sendUpdate.bind(this)) this.txManager.memStore.subscribe(this.sendUpdate.bind(this)) this.messageManager.memStore.subscribe(this.sendUpdate.bind(this)) + this.personalMessageManager.memStore.subscribe(this.sendUpdate.bind(this)) this.keyringController.memStore.subscribe(this.sendUpdate.bind(this)) this.preferencesController.store.subscribe(this.sendUpdate.bind(this)) this.currencyController.store.subscribe(this.sendUpdate.bind(this)) @@ -239,8 +240,6 @@ module.exports = class MetamaskController extends EventEmitter { const keyringController = this.keyringController const preferencesController = this.preferencesController const txManager = this.txManager - const messageManager = this.messageManager - const personalMessageManager = this.personalMessageManager const noticeController = this.noticeController return { @@ -283,11 +282,11 @@ module.exports = class MetamaskController extends EventEmitter { // messageManager signMessage: nodeify(this.signMessage).bind(this), - cancelMessage: messageManager.rejectMsg.bind(messageManager), + cancelMessage: this.cancelMessage.bind(this), // personalMessageManager signPersonalMessage: nodeify(this.signPersonalMessage).bind(this), - cancelPersonalMessage: personalMessageManager.rejectMsg.bind(personalMessageManager), + cancelPersonalMessage: this.cancelPersonalMessage.bind(this), // notices checkNotices: noticeController.updateNoticesList.bind(noticeController), @@ -437,7 +436,7 @@ module.exports = class MetamaskController extends EventEmitter { case 'signed': return cb(null, data.rawSig) case 'rejected': - return cb(new Error('MetaMask Message Signature: User denied transaction signature.')) + return cb(new Error('MetaMask Message Signature: User denied message signature.')) default: return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) } @@ -445,6 +444,10 @@ module.exports = class MetamaskController extends EventEmitter { } newUnsignedPersonalMessage (msgParams, cb) { + if (!msgParams.from) { + return cb(new Error('MetaMask Message Signature: from field is required.')) + } + let msgId = this.personalMessageManager.addUnapprovedMessage(msgParams) this.sendUpdate() this.opts.showUnconfirmedMessage() @@ -453,7 +456,7 @@ module.exports = class MetamaskController extends EventEmitter { case 'signed': return cb(null, data.rawSig) case 'rejected': - return cb(new Error('MetaMask Message Signature: User denied transaction signature.')) + return cb(new Error('MetaMask Message Signature: User denied message signature.')) default: return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) } @@ -479,6 +482,14 @@ module.exports = class MetamaskController extends EventEmitter { }) } + cancelMessage(msgId, cb) { + const messageManager = this.messageManager + messageManager.rejectMsg(msgId) + if (cb && typeof cb === 'function') { + cb(null, this.getState()) + } + } + // Prefixed Style Message Signing Methods: approvePersonalMessage (msgParams, cb) { let msgId = this.personalMessageManager.addUnapprovedMessage(msgParams) @@ -514,6 +525,14 @@ module.exports = class MetamaskController extends EventEmitter { }) } + cancelPersonalMessage(msgId, cb) { + const messageManager = this.personalMessageManager + messageManager.rejectMsg(msgId) + if (cb && typeof cb === 'function') { + cb(null, this.getState()) + } + } + recoverPersonalMessage (msgParams) { const keyringController = this.keyringController return keyringController.recoverPersonalMessage(msgParams) diff --git a/ui/app/actions.js b/ui/app/actions.js index 12ee0367a..89a4fadfa 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -418,7 +418,7 @@ function sendTx (txData) { function completedTx (id) { return { type: actions.COMPLETED_TX, - id, + value: id, } } @@ -436,9 +436,9 @@ function cancelMsg (msgData) { } function cancelPersonalMsg (msgData) { - log.debug(`background.cancelMessage`) - background.cancelPersonalMessage(msgData.id) - return actions.completedTx(msgData.id) + const id = msgData.id + background.cancelPersonalMessage(id) + return actions.completedTx(id) } function cancelTx (txData) { diff --git a/ui/app/components/pending-personal-msg.js b/ui/app/components/pending-personal-msg.js index d48dd5ecc..4542adb28 100644 --- a/ui/app/components/pending-personal-msg.js +++ b/ui/app/components/pending-personal-msg.js @@ -34,7 +34,7 @@ PendingMsg.prototype.render = function () { // sign + cancel h('.flex-row.flex-space-around', [ h('button', { - onClick: state.cancelMessage, + onClick: state.cancelPersonalMessage, }, 'Cancel'), h('button', { onClick: state.signPersonalMessage, diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index a2e5ee94c..2df6c5384 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -111,6 +111,7 @@ ConfirmTxScreen.prototype.render = function () { signMessage: this.signMessage.bind(this, txData), signPersonalMessage: this.signPersonalMessage.bind(this, txData), cancelMessage: this.cancelMessage.bind(this, txData), + cancelPersonalMessage: this.cancelPersonalMessage.bind(this, txData), }), ]), @@ -170,7 +171,6 @@ ConfirmTxScreen.prototype.cancelTransaction = function (txData, event) { ConfirmTxScreen.prototype.signMessage = function (msgData, event) { log.info('conf-tx.js: signing message') var params = msgData.msgParams - var type = msgData.type params.metamaskId = msgData.id event.stopPropagation() this.props.dispatch(actions.signMsg(params)) @@ -190,6 +190,12 @@ ConfirmTxScreen.prototype.cancelMessage = function (msgData, event) { this.props.dispatch(actions.cancelMsg(msgData)) } +ConfirmTxScreen.prototype.cancelPersonalMessage = function (msgData, event) { + log.info('canceling personal message') + event.stopPropagation() + this.props.dispatch(actions.cancelPersonalMsg(msgData)) +} + ConfirmTxScreen.prototype.goHome = function (event) { event.stopPropagation() this.props.dispatch(actions.goHome()) diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 6d92764f1..136326301 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -9,12 +9,13 @@ function reduceApp (state, action) { log.debug('App Reducer got ' + action.type) // clone and defaults const selectedAddress = state.metamask.selectedAddress - const pendingTxs = hasPendingTxs(state) + let pendingTxs = hasPendingTxs(state) let name = 'accounts' if (selectedAddress) { name = 'accountDetail' } if (pendingTxs) { + log.debug('pending txs detected, defaulting to conf-tx view.') name = 'confTx' } @@ -310,15 +311,16 @@ function reduceApp (state, action) { }) case actions.COMPLETED_TX: - log.debug('reducing COMPLETED_TX') + log.debug('reducing COMPLETED_TX for tx ' + action.value) var { unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network } = state.metamask var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network) - .filter(tx => tx !== tx.id) - log.debug(`actions - COMPLETED_TX with ${unconfTxList.length} txs`) + .filter(tx => tx.id !== action.value ) - if (unconfTxList && unconfTxList.length > 0) { + pendingTxs = unconfTxList.length > 0 + + if (pendingTxs) { log.debug('reducer detected txs - rendering confTx view') return extend(appState, { transForward: false, @@ -583,8 +585,7 @@ function hasPendingTxs (state) { var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network) var has = unconfTxList.length > 0 - log.debug('checking if state has pending txs, concluded ' + has) - return unconfTxList.length > 0 + return has } function indexForPending (state, txId) { From d1bce61996a8789759ea72dc76b6f7282afd4380 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 23 Feb 2017 17:45:23 -0800 Subject: [PATCH 13/52] Remove irrelevant tests --- test/unit/actions/tx_test.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/test/unit/actions/tx_test.js b/test/unit/actions/tx_test.js index 7ded5b1ef..bd72a666e 100644 --- a/test/unit/actions/tx_test.js +++ b/test/unit/actions/tx_test.js @@ -52,7 +52,7 @@ describe('tx confirmation screen', function() { clearSeedWordCache(cb) { cb() }, }) - let action = actions.cancelTx({id: firstTxId}) + let action = actions.cancelTx({value: firstTxId}) result = reducers(initialState, action) done() }) @@ -121,7 +121,7 @@ describe('tx confirmation screen', function() { metamask: { unapprovedTxs: { '1457634084250832': { - id: 1457634084250832, + id: firstTxId, status: "unconfirmed", time: 1457634084250, }, @@ -135,8 +135,9 @@ describe('tx confirmation screen', function() { } freeze(initialState) + // Mocking a background connection: actions._setBackgroundConnection({ - approveTransaction(txId, cb) { cb() }, + approveTransaction(firstTxId, cb) { cb() }, }) let action = actions.sendTx({id: firstTxId})(function(action) { @@ -152,11 +153,6 @@ describe('tx confirmation screen', function() { it('should transition to the first tx', function() { assert.equal(result.appState.currentView.context, 0) }) - - it('should only have one unconfirmed tx remaining', function() { - var count = getUnconfirmedTxCount(result) - assert.equal(count, 1) - }) }) }) }); From 42c2c3df3791e5c3f6cd744393c723c7e32e2403 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 23 Feb 2017 17:45:37 -0800 Subject: [PATCH 14/52] Improve pending tx blue dot style --- ui/app/components/transaction-list-item-icon.js | 6 +----- ui/app/css/index.css | 7 +++---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/ui/app/components/transaction-list-item-icon.js b/ui/app/components/transaction-list-item-icon.js index 90b4ec094..ca2781451 100644 --- a/ui/app/components/transaction-list-item-icon.js +++ b/ui/app/components/transaction-list-item-icon.js @@ -15,11 +15,7 @@ TransactionIcon.prototype.render = function () { const { transaction, txParams, isMsg } = this.props switch (transaction.status) { case 'unapproved': - return h( !isMsg ? '.unapproved-tx-icon' : 'i.fa.fa-certificate.fa-lg', { - style: { - width: '24px', - }, - }) + return h( !isMsg ? '.unapproved-tx-icon' : 'i.fa.fa-certificate.fa-lg') case 'rejected': return h('i.fa.fa-exclamation-triangle.fa-lg.warning', { diff --git a/ui/app/css/index.css b/ui/app/css/index.css index 4b9b5b67d..8c6ff29d3 100644 --- a/ui/app/css/index.css +++ b/ui/app/css/index.css @@ -410,11 +410,10 @@ input.large-input { } .unapproved-tx-icon { - height: 24px; - background: #4dffff; - border: solid; + height: 16px; + width: 16px; + background: rgb(47, 174, 244); border-color: #AEAEAE; - border-width: 0.5px; border-radius: 13px; } From 8f87bacc1b2eaa47cc4da7f8afdcbf10aafd694c Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 23 Feb 2017 18:46:17 -0800 Subject: [PATCH 15/52] Fix references in tests --- test/integration/lib/idStore-migrator-test.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/integration/lib/idStore-migrator-test.js b/test/integration/lib/idStore-migrator-test.js index f2a437a7c..1f6322a42 100644 --- a/test/integration/lib/idStore-migrator-test.js +++ b/test/integration/lib/idStore-migrator-test.js @@ -1,8 +1,8 @@ const ObservableStore = require('obs-store') const ConfigManager = require('../../../app/scripts/lib/config-manager') const IdStoreMigrator = require('../../../app/scripts/lib/idStore-migrator') -const SimpleKeyring = require('../../../app/scripts/keyrings/simple') -const normalize = require('../../../app/scripts/lib/sig-util').normalize +const SimpleKeyring = require('eth-keyring-simple') +const normalize = require('eth-sig-util').normalize const oldStyleVault = require('../mocks/oldVault.json').data const badStyleVault = require('../mocks/badVault.json').data @@ -15,7 +15,7 @@ const SEED = 'fringe damage bounce extend tunnel afraid alert sound all soldier QUnit.module('Old Style Vaults', { beforeEach: function () { let managers = managersFromInitState(oldStyleVault) - + this.configManager = managers.configManager this.migrator = managers.migrator } @@ -41,7 +41,7 @@ QUnit.test('migrator:migratedVaultForPassword', function (assert) { QUnit.module('Old Style Vaults with bad HD seed', { beforeEach: function () { let managers = managersFromInitState(badStyleVault) - + this.configManager = managers.configManager this.migrator = managers.migrator } @@ -89,4 +89,4 @@ function managersFromInitState(initState){ }) return { configManager, migrator } -} \ No newline at end of file +} From a97cfffe15ebdf7afb714be30561db6ec1ea7490 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 23 Feb 2017 19:03:03 -0800 Subject: [PATCH 16/52] Fixed reference --- test/integration/lib/idStore-migrator-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/lib/idStore-migrator-test.js b/test/integration/lib/idStore-migrator-test.js index 1f6322a42..290216ae8 100644 --- a/test/integration/lib/idStore-migrator-test.js +++ b/test/integration/lib/idStore-migrator-test.js @@ -1,7 +1,7 @@ const ObservableStore = require('obs-store') const ConfigManager = require('../../../app/scripts/lib/config-manager') const IdStoreMigrator = require('../../../app/scripts/lib/idStore-migrator') -const SimpleKeyring = require('eth-keyring-simple') +const SimpleKeyring = require('eth-simple-keyring') const normalize = require('eth-sig-util').normalize const oldStyleVault = require('../mocks/oldVault.json').data From dfc89d6c6dd622e7dff79544c0885594000ffd37 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Fri, 24 Feb 2017 15:06:55 -0800 Subject: [PATCH 17/52] Make gasPrice accessible to the UI. --- app/scripts/transaction-manager.js | 1 + ui/app/components/pending-tx.js | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/scripts/transaction-manager.js b/app/scripts/transaction-manager.js index 6299091f2..eaeee2d72 100644 --- a/app/scripts/transaction-manager.js +++ b/app/scripts/transaction-manager.js @@ -157,6 +157,7 @@ module.exports = class TransactionManager extends EventEmitter { txMeta.txFee = txFee txMeta.txValue = txValue txMeta.maxCost = maxCost + txMeta.gasPrice = gasPrice this.updateTx(txMeta) } diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 761c75be3..f1f479794 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -18,7 +18,7 @@ PendingTx.prototype.render = function () { const txParams = txData.txParams const gas = state.gas || txParams.gas - const gasPrice = state.gasPrice || txParams.gasPrice + const gasPrice = state.gasPrice || txData.gasPrice return ( @@ -96,4 +96,3 @@ PendingTx.prototype.render = function () { ]) ) } - From 3ebf0dc11bf3c27b5d0a5e8df20cf148c926d956 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 24 Feb 2017 16:15:24 -0800 Subject: [PATCH 18/52] Bump provider engine to require compliant personal_recover --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1542853ad..9b7d43fd6 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "valid-url": "^1.0.9", "vreme": "^3.0.2", "web3": "0.18.2", - "web3-provider-engine": "^9.1.0", + "web3-provider-engine": "^9.1.1", "web3-stream-provider": "^2.0.6", "xtend": "^4.0.1" }, From a35229e8d409245df4302964b184a34d1e48ca63 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 24 Feb 2017 16:15:31 -0800 Subject: [PATCH 19/52] Bump changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf726a336..815b56356 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,8 @@ ## Current Master -- Add personal_sign and personal_ecRecover support. +- Add personal_sign method support. +- Add personal_Recover method support. ## 3.3.0 2017-2-20 From f2851402f39a12c2a11e5dea7312c55b81c481cb Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 24 Feb 2017 16:36:29 -0800 Subject: [PATCH 20/52] Mostly fix personal_recover --- app/scripts/keyring-controller.js | 11 +++++------ app/scripts/metamask-controller.js | 1 + 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js index 8c379b5b9..f2891db37 100644 --- a/app/scripts/keyring-controller.js +++ b/app/scripts/keyring-controller.js @@ -5,7 +5,8 @@ const EventEmitter = require('events').EventEmitter const ObservableStore = require('obs-store') const filter = require('promise-filter') const encryptor = require('browser-passworder') -const normalizeAddress = require('eth-sig-util').normalize +const sigUtil = require('eth-sig-util') +const normalizeAddress = sigUtil.normalize // Keyrings: const SimpleKeyring = require('eth-simple-keyring') const HdKeyring = require('eth-hd-keyring') @@ -284,11 +285,8 @@ class KeyringController extends EventEmitter { // // recovers a signature of the prefixed-style personalMessage signature. recoverPersonalMessage (msgParams) { - const address = normalizeAddress(msgParams.from) - return this.getKeyringForAccount(address) - .then((keyring) => { - return keyring.recoverPersonalMessage(address, msgParams.data) - }) + const address = sigUtil.recoverPersonalSignature(msgParams) + return Promise.resolve(address) } // PRIVATE METHODS @@ -500,6 +498,7 @@ class KeyringController extends EventEmitter { // the specified `address` if one exists. getKeyringForAccount (address) { const hexed = normalizeAddress(address) + log.debug(`KeyringController - getKeyringForAccount: ${hexed}`) return Promise.all(this.keyrings.map((keyring) => { return Promise.all([ diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index eace72c24..995db1c0a 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -534,6 +534,7 @@ module.exports = class MetamaskController extends EventEmitter { } recoverPersonalMessage (msgParams) { + log.debug(`MetaMaskController - recoverPersonalMessage: ${JSON.stringify(msgParams)}`) const keyringController = this.keyringController return keyringController.recoverPersonalMessage(msgParams) } From 1077c79e259b97b7458547580fb882034e438a89 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 24 Feb 2017 16:36:45 -0800 Subject: [PATCH 21/52] Add personal_sign development ui state --- development/states/personal-sign.json | 99 +++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 development/states/personal-sign.json diff --git a/development/states/personal-sign.json b/development/states/personal-sign.json new file mode 100644 index 000000000..2fc71f448 --- /dev/null +++ b/development/states/personal-sign.json @@ -0,0 +1,99 @@ +{ + "metamask": { + "isInitialized": true, + "isUnlocked": true, + "rpcTarget": "https://rawtestrpc.metamask.io/", + "identities": { + "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": { + "address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "name": "Account 1" + }, + "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb": { + "address": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb", + "name": "Account 2" + }, + "0x2f8d4a878cfa04a6e60d46362f5644deab66572d": { + "address": "0x2f8d4a878cfa04a6e60d46362f5644deab66572d", + "name": "Account 3" + } + }, + "unapprovedTxs": {}, + "currentFiat": "USD", + "conversionRate": 13.2126613, + "conversionDate": 1487888522, + "noActiveNotices": true, + "network": "3", + "accounts": { + "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": { + "balance": "0x6ae7c45a61c0e8d2", + "nonce": "0x12", + "code": "0x", + "address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825" + }, + "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb": { + "balance": "0x2892a7aece555480", + "nonce": "0x7", + "code": "0x", + "address": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb" + }, + "0x2f8d4a878cfa04a6e60d46362f5644deab66572d": { + "balance": "0x0", + "nonce": "0x0", + "code": "0x", + "address": "0x2f8d4a878cfa04a6e60d46362f5644deab66572d" + } + }, + "transactions": {}, + "selectedAddressTxList": [], + "unapprovedMsgs": {}, + "unapprovedMsgCount": 0, + "unapprovedPersonalMsgs": { + "2971973686529444": { + "id": 2971973686529444, + "msgParams": { + "from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "data": "0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e01" + }, + "time": 1487888668426, + "status": "unapproved", + "type": "personal_sign" + } + }, + "unapprovedPersonalMsgCount": 1, + "keyringTypes": [ + "Simple Key Pair", + "HD Key Tree" + ], + "keyrings": [ + { + "type": "HD Key Tree", + "accounts": [ + "fdea65c8e26263f6d9a1b5de9555d2931a33b825", + "c5b8dbac4c1d3f152cdeb400e2313f309c410acb", + "2f8d4a878cfa04a6e60d46362f5644deab66572d" + ] + } + ], + "selectedAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", + "currentCurrency": "USD", + "provider": { + "type": "testnet" + }, + "shapeShiftTxList": [], + "lostAccounts": [] + }, + "appState": { + "menuOpen": false, + "currentView": { + "name": "confTx", + "context": 0 + }, + "accountDetail": { + "subview": "transactions" + }, + "transForward": true, + "isLoading": false, + "warning": null + }, + "identities": {} +} \ No newline at end of file From 8c66260bdb903185aaf55944a04b850af2dd64b6 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 24 Feb 2017 17:07:54 -0800 Subject: [PATCH 22/52] Removed redundant personal_recover logic --- app/scripts/keyring-controller.js | 11 ----------- app/scripts/metamask-controller.js | 7 ------- 2 files changed, 18 deletions(-) diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js index f2891db37..e1b1c4335 100644 --- a/app/scripts/keyring-controller.js +++ b/app/scripts/keyring-controller.js @@ -278,17 +278,6 @@ class KeyringController extends EventEmitter { }) } - // Recover Personal Message - // @object msgParams - // - // returns Promise(@buffer signer) - // - // recovers a signature of the prefixed-style personalMessage signature. - recoverPersonalMessage (msgParams) { - const address = sigUtil.recoverPersonalSignature(msgParams) - return Promise.resolve(address) - } - // PRIVATE METHODS // // THESE METHODS ARE ONLY USED INTERNALLY TO THE KEYRING-CONTROLLER diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 995db1c0a..f172c67a8 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -172,7 +172,6 @@ module.exports = class MetamaskController extends EventEmitter { // new style msg signing processPersonalMessage: this.newUnsignedPersonalMessage.bind(this), - personalRecoverSigner: nodeify(this.recoverPersonalMessage).bind(this), }) return provider } @@ -533,12 +532,6 @@ module.exports = class MetamaskController extends EventEmitter { } } - recoverPersonalMessage (msgParams) { - log.debug(`MetaMaskController - recoverPersonalMessage: ${JSON.stringify(msgParams)}`) - const keyringController = this.keyringController - return keyringController.recoverPersonalMessage(msgParams) - } - markAccountsFound (cb) { this.configManager.setLostAccounts([]) this.sendUpdate() From 7cbb2fc689795d21fa157c1ccfd8e3f5a838de7e Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 24 Feb 2017 17:38:11 -0800 Subject: [PATCH 23/52] Reduce provider-engine requirement --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9b7d43fd6..1542853ad 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "valid-url": "^1.0.9", "vreme": "^3.0.2", "web3": "0.18.2", - "web3-provider-engine": "^9.1.1", + "web3-provider-engine": "^9.1.0", "web3-stream-provider": "^2.0.6", "xtend": "^4.0.1" }, From c831043a5125c093a83857a335c7816627a7e291 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 27 Feb 2017 10:16:16 -0800 Subject: [PATCH 24/52] Remove claim for ecRecover support --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 815b56356..761831b79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,6 @@ ## Current Master - Add personal_sign method support. -- Add personal_Recover method support. ## 3.3.0 2017-2-20 From c46f3d59de1201e800e7cee22a593d26dfb575bd Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 27 Feb 2017 13:53:08 -0800 Subject: [PATCH 25/52] Fix state to render gasPrice for ui dev. --- development/states/conf-tx.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/development/states/conf-tx.json b/development/states/conf-tx.json index 11b0eec89..b44d23ad8 100644 --- a/development/states/conf-tx.json +++ b/development/states/conf-tx.json @@ -44,13 +44,14 @@ "estimatedGas": "0x5209", "txFee": "17e0186e60800", "txValue": "de0b6b3a7640000", - "maxCost": "de234b52e4a0800" + "maxCost": "de234b52e4a0800", + "gasPrice": "4a817c800" } }, "currentFiat": "USD", "conversionRate": 12.7200827, "conversionDate": 1487363041, - "noActiveNotices": false, + "noActiveNotices": true, "lastUnreadNotice": { "read": true, "date": "Thu Feb 09 2017", From a77a5f0ab329433404893ff1280fb90ff2a12029 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 27 Feb 2017 13:53:43 -0800 Subject: [PATCH 26/52] Move input boxes into table and into details component. --- ui/app/components/hex-as-decimal-input.js | 5 +++- ui/app/components/pending-tx-details.js | 35 +++++++++++++++++++++++ ui/app/components/pending-tx.js | 16 ----------- 3 files changed, 39 insertions(+), 17 deletions(-) diff --git a/ui/app/components/hex-as-decimal-input.js b/ui/app/components/hex-as-decimal-input.js index 4d3105b8d..34f628f7f 100644 --- a/ui/app/components/hex-as-decimal-input.js +++ b/ui/app/components/hex-as-decimal-input.js @@ -27,6 +27,10 @@ HexAsDecimalInput.prototype.render = function () { return ( h('input', { + style: { + display: 'block', + textAlign: 'right', + }, value: decimalValue, onChange: (event) => { const hexString = hexify(event.target.value) @@ -46,4 +50,3 @@ function decimalize (input) { const inputBN = new BN(strippedInput, 'hex') return inputBN.toString(10) } - diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js index e8615404e..973f6ac92 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -7,6 +7,8 @@ const EthBalance = require('./eth-balance') const util = require('../util') const addressSummary = util.addressSummary const nameForAddress = require('../../lib/contract-namer') +const HexInput = require('./hex-as-decimal-input') + module.exports = PendingTxDetails @@ -20,6 +22,7 @@ const PTXP = PendingTxDetails.prototype PTXP.render = function () { var props = this.props var txData = props.txData + var state = this.state || {} var txParams = txData.txParams || {} var address = txParams.from || props.selectedAddress @@ -27,6 +30,9 @@ PTXP.render = function () { var account = props.accounts[address] var balance = account ? account.balance : '0x0' + const gas = state.gas || txParams.gas + const gasPrice = state.gasPrice || txData.gasPrice + var txFee = txData.txFee || '' var maxCost = txData.maxCost || '' var dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0 @@ -129,7 +135,36 @@ PTXP.render = function () { }), ]), ]), + h('.cell.row', { + }, [ + h('.cell.label', 'Total Gas'), + h('.cell.value', { + + }, [ + h(HexInput, { + value: gas, + onChange: (newHex) => { + this.setState({ gas: newHex }) + }, + }), + ]) + ]), + h('.cell.row', { + + }, [ + h('.cell.label', 'Gas Price'), + h('.cell.value', { + + }, [ + h(HexInput, { + value: gasPrice, + onChange: (newHex) => { + this.setState({ gas: newHex }) + }, + }), + ]) + ]), h('.cell.row', { style: { background: '#f7f7f7', diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index f1f479794..23b0dc00f 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -2,7 +2,6 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits const PendingTxDetails = require('./pending-tx-details') -const HexInput = require('./hex-as-decimal-input') module.exports = PendingTx @@ -78,21 +77,6 @@ PendingTx.prototype.render = function () { onClick: props.cancelTransaction, }, 'Reject'), ]), - - h(HexInput, { - value: gas, - onChange: (newHex) => { - this.setState({ gas: newHex }) - }, - }), - - h(HexInput, { - value: gasPrice, - onChange: (newHex) => { - this.setState({ gasPrice: newHex }) - }, - }), - ]) ) } From 57fec36a7d999e4fdfc06e0b1f8649ec39d7eed4 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 27 Feb 2017 16:06:28 -0800 Subject: [PATCH 27/52] Add non-working gas recalculating logic to tx-details view --- ui/app/components/pending-tx-details.js | 75 +++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 6 deletions(-) diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js index 973f6ac92..a4ca2d32b 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -1,7 +1,10 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits +const debounce = require('debounce') +const extend = require('xtend') +const TxUtils = require('../../../app/scripts/lib/tx-utils') const MiniAccountPanel = require('./mini-account-panel') const EthBalance = require('./eth-balance') const util = require('../util') @@ -21,7 +24,8 @@ const PTXP = PendingTxDetails.prototype PTXP.render = function () { var props = this.props - var txData = props.txData + var state = this.state || {} + var txData = state.txMeta || props.txData var state = this.state || {} var txParams = txData.txParams || {} @@ -33,11 +37,13 @@ PTXP.render = function () { const gas = state.gas || txParams.gas const gasPrice = state.gasPrice || txData.gasPrice - var txFee = txData.txFee || '' - var maxCost = txData.maxCost || '' + var txFee = state.txFee || txData.txFee || '' + var maxCost = state.maxCost || txData.maxCost || '' var dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0 var imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons + log.debug(`rendering gas: ${gas}, gasPrice: ${gasPrice}, txFee: ${txFee}, maxCost: ${maxCost}`) + return ( h('div', [ @@ -138,13 +144,14 @@ PTXP.render = function () { h('.cell.row', { }, [ - h('.cell.label', 'Total Gas'), + h('.cell.label', 'Gas Limit'), h('.cell.value', { }, [ h(HexInput, { value: gas, onChange: (newHex) => { + log.info(`Gas limit changed to ${newHex}`) this.setState({ gas: newHex }) }, }), @@ -155,12 +162,12 @@ PTXP.render = function () { }, [ h('.cell.label', 'Gas Price'), h('.cell.value', { - }, [ h(HexInput, { value: gasPrice, onChange: (newHex) => { - this.setState({ gas: newHex }) + log.info(`Gas price changed to: ${newHex}`) + this.setState({ gasPrice: newHex }) }, }), ]) @@ -226,6 +233,62 @@ PTXP.miniAccountPanelForRecipient = function () { } } +PTXP.componentDidMount = function () { + this.txUtils = new TxUtils(web3.currentProvider) + this.recalculateGas = debounce(this.calculateGas.bind(this), 300) +} + +PTXP.componentDidUpdate = function (prevProps, prevState) { + const state = this.state || {} + log.debug(`pending-tx-details componentDidUpdate`) + console.log(arguments) + + // Only if gas or gasPrice changed: + if (prevState && + (state.gas !== prevState.gas || + state.gasPrice !== prevState.gasPrice) && + this.recalculateGas) { + log.debug(`recalculating gas since prev state change: ${JSON.stringify({ prevState, state })}`) + this.recalculateGas() + } +} + +PTXP.gatherParams = function () { + log.debug(`pending-tx-details#gatherParams`) + const props = this.props + const state = this.state || {} + const txData = state.txData || props.txData + const txParams = txData.txParams + + const gas = state.gas || txParams.gas + const gasPrice = state.gasPrice || txParams.gasPrice + const resultTx = extend(txParams, { + gas, + gasPrice, + }) + const resultTxMeta = extend(txData, { + txParams: resultTx, + }) + log.debug(`gathered params: ${JSON.stringify(resultTxMeta)}`) + return resultTxMeta +} + +PTXP.calculateGas = function () { + const txMeta = this.gatherParams() + log.debug(`pending-tx-details calculating gas for ${ JSON.stringify(txMeta) }`) + const txUtils = this.txUtils + this.txUtils.analyzeGasUsage(txMeta, (err, result) => { + console.log('ANALYZED') + console.dir(arguments) + const { txFee, maxCost } = result || txMeta + if (txFee === txMeta.txFee && maxCost === txMeta.maxCost) { + log.warn(`Recalculating gas resulted in no change.`) + } + log.debug(`pending-tx-details calculated tx fee: ${txFee} and max cost: ${maxCost}`) + this.setState({ txFee, maxCost }) + }) +} + function forwardCarrat () { return ( From c4e935457589b9bb503a430906515b48a24a6d3d Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 27 Feb 2017 16:09:05 -0800 Subject: [PATCH 28/52] Linted --- ui/app/components/pending-tx-details.js | 13 ++++++------- ui/app/components/pending-tx.js | 5 ----- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js index a4ca2d32b..c82a46328 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -26,7 +26,6 @@ PTXP.render = function () { var props = this.props var state = this.state || {} var txData = state.txMeta || props.txData - var state = this.state || {} var txParams = txData.txParams || {} var address = txParams.from || props.selectedAddress @@ -155,7 +154,7 @@ PTXP.render = function () { this.setState({ gas: newHex }) }, }), - ]) + ]), ]), h('.cell.row', { @@ -170,7 +169,7 @@ PTXP.render = function () { this.setState({ gasPrice: newHex }) }, }), - ]) + ]), ]), h('.cell.row', { style: { @@ -275,11 +274,11 @@ PTXP.gatherParams = function () { PTXP.calculateGas = function () { const txMeta = this.gatherParams() - log.debug(`pending-tx-details calculating gas for ${ JSON.stringify(txMeta) }`) - const txUtils = this.txUtils + log.debug(`pending-tx-details calculating gas for ${JSON.stringify(txMeta)}`) this.txUtils.analyzeGasUsage(txMeta, (err, result) => { - console.log('ANALYZED') - console.dir(arguments) + if (err) { + return this.setState({ error: err }) + } const { txFee, maxCost } = result || txMeta if (txFee === txMeta.txFee && maxCost === txMeta.maxCost) { log.warn(`Recalculating gas resulted in no change.`) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 23b0dc00f..3c898edec 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -12,12 +12,7 @@ function PendingTx () { PendingTx.prototype.render = function () { const props = this.props - const state = this.state || {} const txData = props.txData - const txParams = txData.txParams - - const gas = state.gas || txParams.gas - const gasPrice = state.gasPrice || txData.gasPrice return ( From 5d1a4db5e51158523c2c8f3979c91800ddfc041e Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 27 Feb 2017 16:33:58 -0800 Subject: [PATCH 29/52] Further styling to get hex component working. Fix some typos. --- ui/app/components/hex-as-decimal-input.js | 43 +++++++++++++++++------ ui/app/components/pending-tx-details.js | 14 ++++++-- ui/app/components/pending-tx.js | 3 -- 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/ui/app/components/hex-as-decimal-input.js b/ui/app/components/hex-as-decimal-input.js index 34f628f7f..bdc0ed191 100644 --- a/ui/app/components/hex-as-decimal-input.js +++ b/ui/app/components/hex-as-decimal-input.js @@ -3,6 +3,7 @@ const h = require('react-hyperscript') const inherits = require('util').inherits const ethUtil = require('ethereumjs-util') const BN = ethUtil.BN +const extend = require('xtend') module.exports = HexAsDecimalInput @@ -23,20 +24,40 @@ function HexAsDecimalInput () { HexAsDecimalInput.prototype.render = function () { const props = this.props const { value, onChange } = props - const decimalValue = decimalize(value) + const toEth = props.toEth + const suffix = props.suffix + const decimalValue = decimalize(value, toEth) + const style = props.style return ( - h('input', { + h('.flex-row', { style: { - display: 'block', - textAlign: 'right', + alignItems: 'flex-end', + lineHeight: '13px', + fontFamily: 'Montserrat Light', + textRendering: 'geometricPrecision', }, - value: decimalValue, - onChange: (event) => { - const hexString = hexify(event.target.value) - onChange(hexString) - }, - }) + }, [ + h('input.ether-balance.ether-balance-amount', { + style: extend({ + display: 'block', + textAlign: 'right', + backgroundColor: 'transparent', + }, style), + value: decimalValue, + onChange: (event) => { + const hexString = hexify(event.target.value) + onChange(hexString) + }, + }), + h('div', { + style: { + color: ' #AEAEAE', + fontSize: '12px', + marginLeft: '5px', + }, + }, suffix), + ]) ) } @@ -45,7 +66,7 @@ function hexify (decimalString) { return '0x' + hexBN.toString('hex') } -function decimalize (input) { +function decimalize (input, toEth) { const strippedInput = ethUtil.stripHexPrefix(input) const inputBN = new BN(strippedInput, 'hex') return inputBN.toString(10) diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js index 973f6ac92..65eb1d2af 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -138,12 +138,17 @@ PTXP.render = function () { h('.cell.row', { }, [ - h('.cell.label', 'Total Gas'), + h('.cell.label', 'Gas Limit'), h('.cell.value', { }, [ h(HexInput, { value: gas, + suffix: 'UNITS', + style: { + position: 'relative', + top: '5px', + }, onChange: (newHex) => { this.setState({ gas: newHex }) }, @@ -159,8 +164,13 @@ PTXP.render = function () { }, [ h(HexInput, { value: gasPrice, + suffix: 'WEI', + style: { + position: 'relative', + top: '5px', + }, onChange: (newHex) => { - this.setState({ gas: newHex }) + this.setState({ gasPrice: newHex }) }, }), ]) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 23b0dc00f..2b0bb1dfb 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -16,9 +16,6 @@ PendingTx.prototype.render = function () { const txData = props.txData const txParams = txData.txParams - const gas = state.gas || txParams.gas - const gasPrice = state.gasPrice || txData.gasPrice - return ( h('div', { From 2b0e939abdfe8207d47cce863aab48898dbf4e8c Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 27 Feb 2017 16:55:58 -0800 Subject: [PATCH 30/52] Align input fields for gas. --- ui/app/components/hex-as-decimal-input.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/app/components/hex-as-decimal-input.js b/ui/app/components/hex-as-decimal-input.js index bdc0ed191..95d805335 100644 --- a/ui/app/components/hex-as-decimal-input.js +++ b/ui/app/components/hex-as-decimal-input.js @@ -55,6 +55,7 @@ HexAsDecimalInput.prototype.render = function () { color: ' #AEAEAE', fontSize: '12px', marginLeft: '5px', + width: '20px', }, }, suffix), ]) From 4370ca0cef1f58e78b7d596c0976d6b18fe998f8 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 27 Feb 2017 18:18:39 -0800 Subject: [PATCH 31/52] Got gas live re-estimating --- ui/app/components/hex-as-decimal-input.js | 1 + ui/app/components/pending-tx-details.js | 42 ++++++++++++----------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/ui/app/components/hex-as-decimal-input.js b/ui/app/components/hex-as-decimal-input.js index 95d805335..254232a0a 100644 --- a/ui/app/components/hex-as-decimal-input.js +++ b/ui/app/components/hex-as-decimal-input.js @@ -43,6 +43,7 @@ HexAsDecimalInput.prototype.render = function () { display: 'block', textAlign: 'right', backgroundColor: 'transparent', + border: '1px solid #bdbdbd', }, style), value: decimalValue, onChange: (event) => { diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js index d6ae8b85f..d94d65d5b 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -1,10 +1,10 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits -const debounce = require('debounce') const extend = require('xtend') +const ethUtil = require('ethereumjs-util') +const BN = ethUtil.BN -const TxUtils = require('../../../app/scripts/lib/tx-utils') const MiniAccountPanel = require('./mini-account-panel') const EthBalance = require('./eth-balance') const util = require('../util') @@ -242,11 +242,6 @@ PTXP.miniAccountPanelForRecipient = function () { } } -PTXP.componentDidMount = function () { - this.txUtils = new TxUtils(web3.currentProvider) - this.recalculateGas = debounce(this.calculateGas.bind(this), 300) -} - PTXP.componentDidUpdate = function (prevProps, prevState) { const state = this.state || {} log.debug(`pending-tx-details componentDidUpdate`) @@ -255,10 +250,9 @@ PTXP.componentDidUpdate = function (prevProps, prevState) { // Only if gas or gasPrice changed: if (prevState && (state.gas !== prevState.gas || - state.gasPrice !== prevState.gasPrice) && - this.recalculateGas) { + state.gasPrice !== prevState.gasPrice)) { log.debug(`recalculating gas since prev state change: ${JSON.stringify({ prevState, state })}`) - this.recalculateGas() + this.calculateGas() } } @@ -285,19 +279,27 @@ PTXP.gatherParams = function () { PTXP.calculateGas = function () { const txMeta = this.gatherParams() log.debug(`pending-tx-details calculating gas for ${JSON.stringify(txMeta)}`) - this.txUtils.analyzeGasUsage(txMeta, (err, result) => { - if (err) { - return this.setState({ error: err }) - } - const { txFee, maxCost } = result || txMeta - if (txFee === txMeta.txFee && maxCost === txMeta.maxCost) { - log.warn(`Recalculating gas resulted in no change.`) - } - log.debug(`pending-tx-details calculated tx fee: ${txFee} and max cost: ${maxCost}`) - this.setState({ txFee, maxCost }) + + var txParams = txMeta.txParams + var gasMultiplier = txMeta.gasMultiplier + var gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16) + var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || '0x4a817c800'), 16) + gasPrice = gasPrice.mul(new BN(gasMultiplier * 100), 10).div(new BN(100, 10)) + var txFee = gasCost.mul(gasPrice) + var txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16) + var maxCost = txValue.add(txFee) + + this.setState({ + txFee: '0x' + txFee.toString('hex'), + maxCost: '0x' + maxCost.toString('hex'), }) } +function bnFromHex (hex) { + var bn = new BN(ethUtil.stripHexPrefix(hex), 16) + return bn +} + function forwardCarrat () { return ( From 1eb4a5d62c4fdc8a16c7913c9027e0b11c01da52 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 27 Feb 2017 18:25:46 -0800 Subject: [PATCH 32/52] Add background method for updating and approving a tx in one call --- app/scripts/metamask-controller.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index f172c67a8..105bb3d19 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -276,8 +276,9 @@ module.exports = class MetamaskController extends EventEmitter { exportAccount: nodeify(keyringController.exportAccount).bind(keyringController), // txManager - approveTransaction: txManager.approveTransaction.bind(txManager), - cancelTransaction: txManager.cancelTransaction.bind(txManager), + approveTransaction: txManager.approveTransaction.bind(txManager), + cancelTransaction: txManager.cancelTransaction.bind(txManager), + updateAndApproveTransaction: this.updateAndApproveTx.bind(this), // messageManager signMessage: nodeify(this.signMessage).bind(this), @@ -462,6 +463,12 @@ module.exports = class MetamaskController extends EventEmitter { }) } + updateAndApproveTx(txMeta, cb) { + const txManager = this.txManager + txManager.updateTx(txMeta) + txManager.approveTransaction(txMeta.id, cb) + } + signMessage (msgParams, cb) { log.info('MetaMaskController - signMessage') const msgId = msgParams.metamaskId From d844769c926ebc0c43363b4228892e7b2e4e74ee Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 27 Feb 2017 18:26:04 -0800 Subject: [PATCH 33/52] Add action for updating and approving a tx in one action --- ui/app/actions.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/ui/app/actions.js b/ui/app/actions.js index 89a4fadfa..b9169a106 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -94,6 +94,7 @@ var actions = { cancelPersonalMsg, sendTx: sendTx, signTx: signTx, + updateAndApproveTx, cancelTx: cancelTx, completedTx: completedTx, txError: txError, @@ -415,6 +416,20 @@ function sendTx (txData) { } } +function updateAndApproveTx (txData) { + log.info('actions: updateAndApproveTx') + return (dispatch) => { + log.debug(`actions calling background.updateAndApproveTx`) + background.updateAndApproveTransaction(txData, (err) => { + if (err) { + dispatch(actions.txError(err)) + return console.error(err.message) + } + dispatch(actions.completedTx(txData.id)) + }) + } +} + function completedTx (id) { return { type: actions.COMPLETED_TX, From 2e80e8f722e60573764cddfea0b66739626f717a Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 27 Feb 2017 18:26:18 -0800 Subject: [PATCH 34/52] Remove unused function --- ui/app/components/pending-tx-details.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js index d94d65d5b..778651d61 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -295,11 +295,6 @@ PTXP.calculateGas = function () { }) } -function bnFromHex (hex) { - var bn = new BN(ethUtil.stripHexPrefix(hex), 16) - return bn -} - function forwardCarrat () { return ( From 3ddfdfff98806065291bb39f31d73879aa4939e8 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 27 Feb 2017 18:33:33 -0800 Subject: [PATCH 35/52] Emit updated tx values on accept click --- ui/app/components/pending-tx-details.js | 7 +++++++ ui/app/conf-tx.js | 14 +++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js index 778651d61..1e6299902 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -253,6 +253,13 @@ PTXP.componentDidUpdate = function (prevProps, prevState) { state.gasPrice !== prevState.gasPrice)) { log.debug(`recalculating gas since prev state change: ${JSON.stringify({ prevState, state })}`) this.calculateGas() + + // Otherwise this was a recalculation, + // so we should inform the parent: + } else { + if (this.props.onTxChange) { + this.props.onTxChange(this.gatherParams) + } } } diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index cd4bef2b9..9741d5f32 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -104,6 +104,8 @@ ConfirmTxScreen.prototype.render = function () { accounts: props.accounts, identities: props.identities, insufficientBalance: this.checkBalanceAgainstTx(txData), + // State actions + onTxChange: this.updateTxCache.bind(this), // Actions buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress), sendTransaction: this.sendTransaction.bind(this, txData), @@ -159,9 +161,19 @@ ConfirmTxScreen.prototype.buyEth = function (address, event) { this.props.dispatch(actions.buyEthView(address)) } +// Allows the detail view to update the gas calculations, +// for manual gas controls. +ConfirmTxScreen.prototype.onTxChange = function (txData) { + this.setState({ txData }) +} + +// Must default to any local state txData, +// to allow manual override of gas calculations. ConfirmTxScreen.prototype.sendTransaction = function (txData, event) { event.stopPropagation() - this.props.dispatch(actions.sendTx(txData)) + const state = this.state || {} + const txMeta = state.txData + this.props.dispatch(actions.sendTx(txMeta || txData)) } ConfirmTxScreen.prototype.cancelTransaction = function (txData, event) { From 0e817c9e7fae4c60f0baf52be60fd7489c977b38 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 27 Feb 2017 18:36:43 -0800 Subject: [PATCH 36/52] Reorder rows for better table logic --- ui/app/components/pending-tx-details.js | 85 +++++++++++++------------ 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js index 1e6299902..510ef78e6 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -108,11 +108,55 @@ PTXP.render = function () { h('.table-box', [ + // Ether Value + // Currently not customizable, but easily modified + // in the way that gas and gasLimit currently are. h('.row', [ h('.cell.label', 'Amount'), h(EthBalance, { value: txParams.value }), ]), + // Gas Limit (customizable) + h('.cell.row', [ + h('.cell.label', 'Gas Limit'), + h('.cell.value', { + }, [ + h(HexInput, { + value: gas, + suffix: 'UNITS', + style: { + position: 'relative', + top: '5px', + }, + onChange: (newHex) => { + log.info(`Gas limit changed to ${newHex}`) + this.setState({ gas: newHex }) + }, + }), + ]), + ]), + + // Gas Price (customizable) + h('.cell.row', [ + h('.cell.label', 'Gas Price'), + h('.cell.value', { + }, [ + h(HexInput, { + value: gasPrice, + suffix: 'WEI', + style: { + position: 'relative', + top: '5px', + }, + onChange: (newHex) => { + log.info(`Gas price changed to: ${newHex}`) + this.setState({ gasPrice: newHex }) + }, + }), + ]), + ]), + + // Max Transaction Fee (calculated) h('.cell.row', [ h('.cell.label', 'Max Transaction Fee'), h(EthBalance, { value: txFee.toString(16) }), @@ -140,47 +184,8 @@ PTXP.render = function () { }), ]), ]), - h('.cell.row', { - }, [ - h('.cell.label', 'Gas Limit'), - h('.cell.value', { - - }, [ - h(HexInput, { - value: gas, - suffix: 'UNITS', - style: { - position: 'relative', - top: '5px', - }, - onChange: (newHex) => { - log.info(`Gas limit changed to ${newHex}`) - this.setState({ gas: newHex }) - }, - }), - ]), - ]), - h('.cell.row', { - - }, [ - h('.cell.label', 'Gas Price'), - h('.cell.value', { - }, [ - h(HexInput, { - value: gasPrice, - suffix: 'WEI', - style: { - position: 'relative', - top: '5px', - }, - onChange: (newHex) => { - log.info(`Gas price changed to: ${newHex}`) - this.setState({ gasPrice: newHex }) - }, - }), - ]), - ]), + // Data size row: h('.cell.row', { style: { background: '#f7f7f7', From 04df5c1f2ded53229edbff85401c659c958389f0 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 28 Feb 2017 10:06:59 -0800 Subject: [PATCH 37/52] Fix reference --- ui/app/conf-tx.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 9741d5f32..de2c73cbc 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -105,7 +105,7 @@ ConfirmTxScreen.prototype.render = function () { identities: props.identities, insufficientBalance: this.checkBalanceAgainstTx(txData), // State actions - onTxChange: this.updateTxCache.bind(this), + onTxChange: this.onTxChange.bind(this), // Actions buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress), sendTransaction: this.sendTransaction.bind(this, txData), From 666044d417d45ee0e5e491cac388d1f57ee83932 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 28 Feb 2017 10:23:47 -0800 Subject: [PATCH 38/52] Add margins to align. --- ui/app/components/hex-as-decimal-input.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/app/components/hex-as-decimal-input.js b/ui/app/components/hex-as-decimal-input.js index 254232a0a..523c1264b 100644 --- a/ui/app/components/hex-as-decimal-input.js +++ b/ui/app/components/hex-as-decimal-input.js @@ -56,6 +56,7 @@ HexAsDecimalInput.prototype.render = function () { color: ' #AEAEAE', fontSize: '12px', marginLeft: '5px', + marginRight: '6px', width: '20px', }, }, suffix), From 45138af6c6b97f795bac954ffb1229a3569591bc Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 28 Feb 2017 11:35:04 -0800 Subject: [PATCH 39/52] Fix infinite loop bug --- ui/app/components/pending-tx-details.js | 70 ++++++++++++++----------- ui/app/conf-tx.js | 1 + 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js index 510ef78e6..643e69902 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -247,27 +247,55 @@ PTXP.miniAccountPanelForRecipient = function () { } } -PTXP.componentDidUpdate = function (prevProps, prevState) { +PTXP.componentDidUpdate = function (prevProps, previousState) { const state = this.state || {} + const prevState = previousState || {} + const { gas, gasPrice } = state + log.debug(`pending-tx-details componentDidUpdate`) console.log(arguments) // Only if gas or gasPrice changed: - if (prevState && - (state.gas !== prevState.gas || - state.gasPrice !== prevState.gasPrice)) { + if (!prevState || + (gas !== prevState.gas || + gasPrice !== prevState.gasPrice)) { log.debug(`recalculating gas since prev state change: ${JSON.stringify({ prevState, state })}`) this.calculateGas() - - // Otherwise this was a recalculation, - // so we should inform the parent: - } else { - if (this.props.onTxChange) { - this.props.onTxChange(this.gatherParams) - } } } +PTXP.calculateGas = function () { + const txMeta = this.gatherParams() + log.debug(`pending-tx-details calculating gas for ${JSON.stringify(txMeta)}`) + + var txParams = txMeta.txParams + var gasMultiplier = txMeta.gasMultiplier + var gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16) + var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || '0x4a817c800'), 16) + gasPrice = gasPrice.mul(new BN(gasMultiplier * 100), 10).div(new BN(100, 10)) + var txFee = gasCost.mul(gasPrice) + var txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16) + var maxCost = txValue.add(txFee) + + const txFeeHex = '0x' + txFee.toString('hex') + const maxCostHex = '0x' + maxCost.toString('hex') + const gasPriceHex = '0x' + gasPrice.toString('hex') + + txMeta.txFee = txFeeHex + txMeta.maxCost = maxCostHex + txMeta.txParams.gasPrice = gasPriceHex + + this.setState({ + txFee: '0x' + txFee.toString('hex'), + maxCost: '0x' + maxCost.toString('hex'), + }) + + if (this.props.onTxChange) { + this.props.onTxChange(txMeta) + } +} + +// After a customizable state value has been updated, PTXP.gatherParams = function () { log.debug(`pending-tx-details#gatherParams`) const props = this.props @@ -284,29 +312,9 @@ PTXP.gatherParams = function () { const resultTxMeta = extend(txData, { txParams: resultTx, }) - log.debug(`gathered params: ${JSON.stringify(resultTxMeta)}`) return resultTxMeta } -PTXP.calculateGas = function () { - const txMeta = this.gatherParams() - log.debug(`pending-tx-details calculating gas for ${JSON.stringify(txMeta)}`) - - var txParams = txMeta.txParams - var gasMultiplier = txMeta.gasMultiplier - var gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16) - var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || '0x4a817c800'), 16) - gasPrice = gasPrice.mul(new BN(gasMultiplier * 100), 10).div(new BN(100, 10)) - var txFee = gasCost.mul(gasPrice) - var txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16) - var maxCost = txValue.add(txFee) - - this.setState({ - txFee: '0x' + txFee.toString('hex'), - maxCost: '0x' + maxCost.toString('hex'), - }) -} - function forwardCarrat () { return ( diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index de2c73cbc..0a59eee37 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -164,6 +164,7 @@ ConfirmTxScreen.prototype.buyEth = function (address, event) { // Allows the detail view to update the gas calculations, // for manual gas controls. ConfirmTxScreen.prototype.onTxChange = function (txData) { + log.debug(`conf-tx onTxChange triggered with ${JSON.stringify(txData)}`) this.setState({ txData }) } From 2e16e1eb94d0ec68da74d3ee4ce1c9ac2115c13d Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 28 Feb 2017 12:00:07 -0800 Subject: [PATCH 40/52] Fixed bug that made send screen sometimes transition to account detail --- app/scripts/lib/tx-utils.js | 1 + ui/app/conf-tx.js | 3 ++- ui/app/reducers/app.js | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/scripts/lib/tx-utils.js b/app/scripts/lib/tx-utils.js index 240a6ab47..b152effcc 100644 --- a/app/scripts/lib/tx-utils.js +++ b/app/scripts/lib/tx-utils.js @@ -106,6 +106,7 @@ module.exports = class txProviderUtils { txParams.gasLimit = normalize(txParams.gasLimit || txParams.gas) txParams.nonce = normalize(txParams.nonce) // build ethTx + log.info(`Prepared tx for signing: ${JSON.stringify(txParams)}`) const ethTx = new Transaction(txParams) return ethTx } diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 0a59eee37..ed633553b 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -13,6 +13,7 @@ const BN = ethUtil.BN const PendingTx = require('./components/pending-tx') const PendingMsg = require('./components/pending-msg') const PendingPersonalMsg = require('./components/pending-personal-msg') +const Loading = require('./components/loading') module.exports = connect(mapStateToProps)(ConfirmTxScreen) @@ -48,7 +49,7 @@ ConfirmTxScreen.prototype.render = function () { var isNotification = isPopupOrNotification() === 'notification' log.info(`rendering a combined ${unconfTxList.length} unconf msg & txs`) - if (unconfTxList.length === 0) return null + if (unconfTxList.length === 0) return h(Loading) return ( diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 136326301..7ea1e1d7c 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -291,7 +291,7 @@ function reduceApp (state, action) { case actions.SHOW_CONF_TX_PAGE: return extend(appState, { currentView: { - name: pendingTxs ? 'confTx' : 'account-detail', + name: 'confTx', context: 0, }, transForward: action.transForward, From 5a74c0fcad92cf4192eefedcb092d4525157902f Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 28 Feb 2017 12:12:18 -0800 Subject: [PATCH 41/52] Fix bug that showed conf-tx screen on boot at wrong times --- ui/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/index.js b/ui/index.js index 6b65f12d4..1a65f813c 100644 --- a/ui/index.js +++ b/ui/index.js @@ -37,7 +37,7 @@ function startApp (metamaskState, accountManager, opts) { }) // if unconfirmed txs, start on txConf page - var unapprovedTxsAll = txHelper(metamaskState.unapprovedTxs, metamaskState.unapprovedMsgs, metamaskState.network) + var unapprovedTxsAll = txHelper(metamaskState.unapprovedTxs, metamaskState.unapprovedMsgs, metamaskState.unapprovedPersonalMsgs, metamaskState.network) if (unapprovedTxsAll.length > 0) { store.dispatch(actions.showConfTxPage()) } From acfb6ff0f87309f45ade0ec4c5a3bd3d7cbad35c Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 28 Feb 2017 14:07:19 -0800 Subject: [PATCH 42/52] Hide gas options behind an advanced options checkbox. --- ui/app/components/pending-tx-details.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js index 643e69902..9bb75bd98 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -40,6 +40,7 @@ PTXP.render = function () { var maxCost = state.maxCost || txData.maxCost || '' var dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0 var imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons + var advanced = state.advanced || false log.debug(`rendering gas: ${gas}, gasPrice: ${gasPrice}, txFee: ${txFee}, maxCost: ${maxCost}`) @@ -108,6 +109,17 @@ PTXP.render = function () { h('.table-box', [ + h('.row', [ + h('.cell.label', 'Advanced Options'), + h('input', { + type: 'checkbox', + selected: advanced, + onChange: () => { + this.setState({advanced: !advanced}) + } + }) + ]), + // Ether Value // Currently not customizable, but easily modified // in the way that gas and gasLimit currently are. @@ -117,7 +129,7 @@ PTXP.render = function () { ]), // Gas Limit (customizable) - h('.cell.row', [ + advanced ? h('.cell.row', [ h('.cell.label', 'Gas Limit'), h('.cell.value', { }, [ @@ -134,10 +146,10 @@ PTXP.render = function () { }, }), ]), - ]), + ]) : null, // Gas Price (customizable) - h('.cell.row', [ + advanced ? h('.cell.row', [ h('.cell.label', 'Gas Price'), h('.cell.value', { }, [ @@ -154,7 +166,7 @@ PTXP.render = function () { }, }), ]), - ]), + ]) : null, // Max Transaction Fee (calculated) h('.cell.row', [ From da88481560f0f2ad7e12b3e94082aa10147b900e Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 28 Feb 2017 14:08:00 -0800 Subject: [PATCH 43/52] Remove gasMultiplier txMeta param This was used by the custom gas slider on the `send` screen, and it was used to modify the gas value before sending it out, breaking our new custom gas field logic. Removed it and the logic that referred to this now-outdated parameter. --- app/scripts/lib/config-manager.js | 12 ----- app/scripts/lib/id-management.js | 8 +-- app/scripts/lib/idStore.js | 1 - app/scripts/lib/tx-utils.js | 3 +- app/scripts/metamask-controller.js | 10 ---- app/scripts/transaction-manager.js | 14 +---- ui/app/actions.js | 14 ++--- ui/app/components/pending-tx-details.js | 6 +-- ui/app/send.js | 71 +------------------------ 9 files changed, 11 insertions(+), 128 deletions(-) diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index ea5e49b19..6868637e5 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -228,18 +228,6 @@ ConfigManager.prototype._emitUpdates = function (state) { }) } -ConfigManager.prototype.getGasMultiplier = function () { - var data = this.getData() - return data.gasMultiplier -} - -ConfigManager.prototype.setGasMultiplier = function (gasMultiplier) { - var data = this.getData() - - data.gasMultiplier = gasMultiplier - this.setData(data) -} - ConfigManager.prototype.setLostAccounts = function (lostAccounts) { var data = this.getData() data.lostAccounts = lostAccounts diff --git a/app/scripts/lib/id-management.js b/app/scripts/lib/id-management.js index 421f2105f..30631d479 100644 --- a/app/scripts/lib/id-management.js +++ b/app/scripts/lib/id-management.js @@ -25,13 +25,9 @@ function IdManagement (opts) { } this.signTx = function (txParams) { - // calculate gas with custom gas multiplier - var gasMultiplier = this.configManager.getGasMultiplier() || 1 - var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice), 16) - gasPrice = gasPrice.mul(new BN(gasMultiplier * 100, 10)).div(new BN(100, 10)) - txParams.gasPrice = ethUtil.intToHex(gasPrice.toNumber()) - // normalize values + // normalize values + txParams.gasPrice = ethUtil.intToHex(txParams.gasPrice) txParams.to = ethUtil.addHexPrefix(txParams.to) txParams.from = ethUtil.addHexPrefix(txParams.from.toLowerCase()) txParams.value = ethUtil.addHexPrefix(txParams.value) diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js index 7a6968c6c..01474035e 100644 --- a/app/scripts/lib/idStore.js +++ b/app/scripts/lib/idStore.js @@ -95,7 +95,6 @@ IdentityStore.prototype.getState = function () { isUnlocked: this._isUnlocked(), seedWords: seedWords, selectedAddress: configManager.getSelectedAccount(), - gasMultiplier: configManager.getGasMultiplier(), })) } diff --git a/app/scripts/lib/tx-utils.js b/app/scripts/lib/tx-utils.js index b152effcc..19a2d430e 100644 --- a/app/scripts/lib/tx-utils.js +++ b/app/scripts/lib/tx-utils.js @@ -92,11 +92,10 @@ module.exports = class txProviderUtils { } // builds ethTx from txParams object - buildEthTxFromParams (txParams, gasMultiplier = 1) { + buildEthTxFromParams (txParams) { // apply gas multiplyer let gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice), 16) // multiply and divide by 100 so as to add percision to integer mul - gasPrice = gasPrice.mul(new BN(gasMultiplier * 100, 10)).div(new BN(100, 10)) txParams.gasPrice = ethUtil.intToHex(gasPrice.toNumber()) // normalize values txParams.to = normalize(txParams.to) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 105bb3d19..caaa44767 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -248,7 +248,6 @@ module.exports = class MetamaskController extends EventEmitter { setProviderType: this.setProviderType.bind(this), useEtherscanProvider: this.useEtherscanProvider.bind(this), setCurrentCurrency: this.setCurrentCurrency.bind(this), - setGasMultiplier: this.setGasMultiplier.bind(this), markAccountsFound: this.markAccountsFound.bind(this), // coinbase buyEth: this.buyEth.bind(this), @@ -651,15 +650,6 @@ module.exports = class MetamaskController extends EventEmitter { this.shapeshiftController.createShapeShiftTx(depositAddress, depositType) } - setGasMultiplier (gasMultiplier, cb) { - try { - this.txManager.setGasMultiplier(gasMultiplier) - cb() - } catch (err) { - cb(err) - } - } - // // network // diff --git a/app/scripts/transaction-manager.js b/app/scripts/transaction-manager.js index eaeee2d72..07c90af7e 100644 --- a/app/scripts/transaction-manager.js +++ b/app/scripts/transaction-manager.js @@ -13,7 +13,6 @@ module.exports = class TransactionManager extends EventEmitter { super() this.store = new ObservableStore(extend({ transactions: [], - gasMultiplier: 1, }, opts.initState)) this.memStore = new ObservableStore({}) this.networkStore = opts.networkStore || new ObservableStore({}) @@ -52,14 +51,6 @@ module.exports = class TransactionManager extends EventEmitter { return fullTxList.filter(txMeta => txMeta.metamaskNetworkId === network) } - getGasMultiplier () { - return this.store.getState().gasMultiplier - } - - setGasMultiplier (gasMultiplier) { - return this.store.updateState({ gasMultiplier }) - } - // Adds a tx to the txlist addTx (txMeta) { var txList = this.getTxList() @@ -129,7 +120,6 @@ module.exports = class TransactionManager extends EventEmitter { id: txId, time: time, status: 'unapproved', - gasMultiplier: this.getGasMultiplier(), metamaskNetworkId: this.getNetwork(), txParams: txParams, } @@ -147,10 +137,8 @@ module.exports = class TransactionManager extends EventEmitter { setMaxTxCostAndFee (txMeta) { var txParams = txMeta.txParams - var gasMultiplier = txMeta.gasMultiplier var gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16) var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || '0x4a817c800'), 16) - gasPrice = gasPrice.mul(new BN(gasMultiplier * 100), 10).div(new BN(100, 10)) var txFee = gasCost.mul(gasPrice) var txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16) var maxCost = txValue.add(txFee) @@ -210,7 +198,7 @@ module.exports = class TransactionManager extends EventEmitter { let txMeta = this.getTx(txId) let txParams = txMeta.txParams let fromAddress = txParams.from - let ethTx = this.txProviderUtils.buildEthTxFromParams(txParams, txMeta.gasMultiplier) + let ethTx = this.txProviderUtils.buildEthTxFromParams(txParams) this.signEthTx(ethTx, fromAddress).then(() => { this.setTxStatusSigned(txMeta.id) cb(null, ethUtil.bufferToHex(ethTx.serialize())) diff --git a/ui/app/actions.js b/ui/app/actions.js index b9169a106..8177696f1 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -388,17 +388,13 @@ function signPersonalMsg (msgData) { function signTx (txData) { return (dispatch) => { - log.debug(`background.setGasMultiplier`) - background.setGasMultiplier(txData.gasMultiplier, (err) => { + web3.eth.sendTransaction(txData, (err, data) => { + dispatch(actions.hideLoadingIndication()) if (err) return dispatch(actions.displayWarning(err.message)) - web3.eth.sendTransaction(txData, (err, data) => { - dispatch(actions.hideLoadingIndication()) - if (err) return dispatch(actions.displayWarning(err.message)) - dispatch(actions.hideWarning()) - dispatch(actions.goHome()) - }) - dispatch(this.showConfTxPage()) + dispatch(actions.hideWarning()) + dispatch(actions.goHome()) }) + dispatch(this.showConfTxPage()) } } diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js index 643e69902..f5651bb1d 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -248,13 +248,11 @@ PTXP.miniAccountPanelForRecipient = function () { } PTXP.componentDidUpdate = function (prevProps, previousState) { + log.debug(`pending-tx-details componentDidUpdate`) const state = this.state || {} const prevState = previousState || {} const { gas, gasPrice } = state - log.debug(`pending-tx-details componentDidUpdate`) - console.log(arguments) - // Only if gas or gasPrice changed: if (!prevState || (gas !== prevState.gas || @@ -269,10 +267,8 @@ PTXP.calculateGas = function () { log.debug(`pending-tx-details calculating gas for ${JSON.stringify(txMeta)}`) var txParams = txMeta.txParams - var gasMultiplier = txMeta.gasMultiplier var gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16) var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || '0x4a817c800'), 16) - gasPrice = gasPrice.mul(new BN(gasMultiplier * 100), 10).div(new BN(100, 10)) var txFee = gasCost.mul(gasPrice) var txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16) var maxCost = txValue.add(txFee) diff --git a/ui/app/send.js b/ui/app/send.js index d16270b41..752eed15a 100644 --- a/ui/app/send.js +++ b/ui/app/send.js @@ -208,73 +208,6 @@ SendTransactionScreen.prototype.render = function () { }, }), ]), - // custom gasPrice field - h('h3.flex-center.text-transform-uppercase', { - style: { - background: '#EBEBEB', - color: '#AEAEAE', - marginBottom: '5px', - }, - }, [ - 'Transaction Fee (optional)', - h(Tooltip, { - title: ` - This is used to set the transaction's gas price. - Setting it to 100% will use the full recommended value. `, - }, [ - h('i.fa.fa-question-circle', { - style: { - marginLeft: '5px', - }, - }), - ]), - ]), - - h('section.flex-column.flex-center', [ - h('.flex-row', [ - h(RangeSlider, { - name: 'gasInput', - options: { - mirrorInput: true, - defaultValue: 100, - min: 80, - max: 220, - }, - style: { - container: { - marginBottom: '16px', - }, - range: { - width: '68vw', - }, - input: { - width: '5em', - marginLeft: '5px', - }, - }, - }), - - h('div', { - style: { - fontSize: '12px', - paddingTop: '8px', - paddingLeft: '5px', - }, - }, '%'), - ]), - h('.flex-row', { - style: { - justifyContent: 'space-between', - width: '243px', - position: 'relative', - fontSize: '12px', - right: '42px', - bottom: '30px', - }, - }, [ - h('span', 'Cheaper'), h('span', 'Faster'), - ]), - ]), ]) ) } @@ -289,12 +222,11 @@ SendTransactionScreen.prototype.back = function () { this.props.dispatch(actions.backToAccountDetail(address)) } -SendTransactionScreen.prototype.onSubmit = function (gasPrice) { +SendTransactionScreen.prototype.onSubmit = function () { const recipient = document.querySelector('input[name="address"]').value const input = document.querySelector('input[name="amount"]').value const value = util.normalizeEthStringToWei(input) const txData = document.querySelector('input[name="txData"]').value - const gasMultiplier = document.querySelector('input[name="gasInput"]').value const balance = this.props.balance let message @@ -323,7 +255,6 @@ SendTransactionScreen.prototype.onSubmit = function (gasPrice) { var txParams = { from: this.props.address, value: '0x' + value.toString(16), - gasMultiplier: gasMultiplier * 0.01, } if (recipient) txParams.to = ethUtil.addHexPrefix(recipient) From 486583e20371204d3a406a3984da0006fcfd8c56 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 28 Feb 2017 14:12:50 -0800 Subject: [PATCH 44/52] lint --- ui/app/components/pending-tx-details.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js index 9bb75bd98..b3260e112 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -116,8 +116,8 @@ PTXP.render = function () { selected: advanced, onChange: () => { this.setState({advanced: !advanced}) - } - }) + }, + }), ]), // Ether Value @@ -168,6 +168,8 @@ PTXP.render = function () { ]), ]) : null, + + // Max Transaction Fee (calculated) h('.cell.row', [ h('.cell.label', 'Max Transaction Fee'), From e07e4b7bc73da1d5a2712893cc03e6f4bd90fb6d Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 28 Feb 2017 14:12:07 -0800 Subject: [PATCH 45/52] Linted --- app/scripts/lib/id-management.js | 1 - ui/app/components/pending-tx-details.js | 4 ++-- ui/app/send.js | 2 -- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/app/scripts/lib/id-management.js b/app/scripts/lib/id-management.js index 30631d479..90b3fdb13 100644 --- a/app/scripts/lib/id-management.js +++ b/app/scripts/lib/id-management.js @@ -7,7 +7,6 @@ */ const ethUtil = require('ethereumjs-util') -const BN = ethUtil.BN const Transaction = require('ethereumjs-tx') module.exports = IdManagement diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js index 892ef4ad6..9a8f202be 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -116,8 +116,8 @@ PTXP.render = function () { selected: advanced, onChange: () => { this.setState({advanced: !advanced}) - } - }) + }, + }), ]), // Ether Value diff --git a/ui/app/send.js b/ui/app/send.js index 752eed15a..581e3afa0 100644 --- a/ui/app/send.js +++ b/ui/app/send.js @@ -10,8 +10,6 @@ const addressSummary = require('./util').addressSummary const isHex = require('./util').isHex const EthBalance = require('./components/eth-balance') const ethUtil = require('ethereumjs-util') -const RangeSlider = require('./components/range-slider') -const Tooltip = require('./components/tooltip') module.exports = connect(mapStateToProps)(SendTransactionScreen) function mapStateToProps (state) { From d21915c605a502d306acd8b728d920217d0f13df Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 28 Feb 2017 14:19:32 -0800 Subject: [PATCH 46/52] Remove advanced options for now. --- ui/app/components/pending-tx-details.js | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js index a9172d4ad..040fc2f34 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -109,17 +109,6 @@ PTXP.render = function () { h('.table-box', [ - h('.row', [ - h('.cell.label', 'Advanced Options'), - h('input', { - type: 'checkbox', - selected: advanced, - onChange: () => { - this.setState({advanced: !advanced}) - }, - }), - ]), - // Ether Value // Currently not customizable, but easily modified // in the way that gas and gasLimit currently are. @@ -129,7 +118,7 @@ PTXP.render = function () { ]), // Gas Limit (customizable) - advanced ? h('.cell.row', [ + h('.cell.row', [ h('.cell.label', 'Gas Limit'), h('.cell.value', { }, [ @@ -146,10 +135,10 @@ PTXP.render = function () { }, }), ]), - ]) : null, + ]), // Gas Price (customizable) - advanced ? h('.cell.row', [ + h('.cell.row', [ h('.cell.label', 'Gas Price'), h('.cell.value', { }, [ @@ -166,7 +155,7 @@ PTXP.render = function () { }, }), ]), - ]) : null, + ]), From 9fb4b4a77fb97e5bf1081127071c232d9729e8eb Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 28 Feb 2017 14:21:44 -0800 Subject: [PATCH 47/52] lints --- ui/app/components/pending-tx-details.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js index 040fc2f34..f5651bb1d 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -40,7 +40,6 @@ PTXP.render = function () { var maxCost = state.maxCost || txData.maxCost || '' var dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0 var imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons - var advanced = state.advanced || false log.debug(`rendering gas: ${gas}, gasPrice: ${gasPrice}, txFee: ${txFee}, maxCost: ${maxCost}`) @@ -157,8 +156,6 @@ PTXP.render = function () { ]), ]), - - // Max Transaction Fee (calculated) h('.cell.row', [ h('.cell.label', 'Max Transaction Fee'), From f908aaafbc0c3ed94ef8892b9bd16a9c1b49269f Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 28 Feb 2017 14:45:21 -0800 Subject: [PATCH 48/52] Use correct action to update and submit tx --- app/scripts/metamask-controller.js | 1 + ui/app/actions.js | 4 ++-- ui/app/conf-tx.js | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index caaa44767..295d0e998 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -463,6 +463,7 @@ module.exports = class MetamaskController extends EventEmitter { } updateAndApproveTx(txMeta, cb) { + log.debug(`MetaMaskController - updateAndApproveTx: ${JSON.stringify(txMeta)}`) const txManager = this.txManager txManager.updateTx(txMeta) txManager.approveTransaction(txMeta.id, cb) diff --git a/ui/app/actions.js b/ui/app/actions.js index 8177696f1..7f972fb37 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -399,7 +399,7 @@ function signTx (txData) { } function sendTx (txData) { - log.info('actions: sendTx') + log.info(`actions - sendTx: ${JSON.stringify(txData.txParams)}`) return (dispatch) => { log.debug(`actions calling background.approveTransaction`) background.approveTransaction(txData.id, (err) => { @@ -413,7 +413,7 @@ function sendTx (txData) { } function updateAndApproveTx (txData) { - log.info('actions: updateAndApproveTx') + log.info('actions: updateAndApproveTx: ' + JSON.stringify(txData)) return (dispatch) => { log.debug(`actions calling background.updateAndApproveTx`) background.updateAndApproveTransaction(txData, (err) => { diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index ed633553b..7e93ea29f 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -175,7 +175,7 @@ ConfirmTxScreen.prototype.sendTransaction = function (txData, event) { event.stopPropagation() const state = this.state || {} const txMeta = state.txData - this.props.dispatch(actions.sendTx(txMeta || txData)) + this.props.dispatch(actions.updateAndApproveTx(txMeta || txData)) } ConfirmTxScreen.prototype.cancelTransaction = function (txData, event) { From 576cc9eb754258f3a534633af460ff657c8e112a Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 28 Feb 2017 15:21:48 -0800 Subject: [PATCH 49/52] Gas and Gaslimit revert to default if set to 0 --- ui/app/components/pending-tx-details.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js index f5651bb1d..5eade525a 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -35,6 +35,8 @@ PTXP.render = function () { const gas = state.gas || txParams.gas const gasPrice = state.gasPrice || txData.gasPrice + const gasDefault = gas + const gasPriceDefault = gasPrice var txFee = state.txFee || txData.txFee || '' var maxCost = state.maxCost || txData.maxCost || '' @@ -130,7 +132,11 @@ PTXP.render = function () { }, onChange: (newHex) => { log.info(`Gas limit changed to ${newHex}`) - this.setState({ gas: newHex }) + if (newHex === '0x0') { + this.setState({gas: gasDefault}) + } else { + this.setState({ gas: newHex }) + } }, }), ]), @@ -150,7 +156,11 @@ PTXP.render = function () { }, onChange: (newHex) => { log.info(`Gas price changed to: ${newHex}`) - this.setState({ gasPrice: newHex }) + if (newHex === '0x0') { + this.setState({gasPrice: gasPriceDefault}) + } else { + this.setState({ gasPrice: newHex }) + } }, }), ]), From ddc136a7c25d9b0f8a1fca2b9ea2fc490e601c83 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 28 Feb 2017 15:41:17 -0800 Subject: [PATCH 50/52] Add a couple more debug logs --- app/scripts/metamask-controller.js | 1 + ui/app/components/pending-tx-details.js | 1 + 2 files changed, 2 insertions(+) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 295d0e998..bd01a260d 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -407,6 +407,7 @@ module.exports = class MetamaskController extends EventEmitter { // newUnapprovedTransaction (txParams, cb) { + log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`) const self = this self.txManager.addUnapprovedTransaction(txParams, (err, txMeta) => { if (err) return cb(err) diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js index f5651bb1d..eddf7db4c 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -308,6 +308,7 @@ PTXP.gatherParams = function () { const resultTxMeta = extend(txData, { txParams: resultTx, }) + log.debug(`UI has computed tx params ${JSON.stringify(resultTx)}`) return resultTxMeta } From 865764dff55a29443b45a4f0e720820f67be9bcd Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 28 Feb 2017 15:46:26 -0800 Subject: [PATCH 51/52] Bump changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 761831b79..d07154bdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Current Master - Add personal_sign method support. +- Add ability to customize gas and gasPrice on the transaction approval screen. ## 3.3.0 2017-2-20 From a600ccd4f863d7a473392fc283f4cec248225a27 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 28 Feb 2017 16:36:05 -0800 Subject: [PATCH 52/52] Add reset button to reset gas fields. --- ui/app/components/pending-tx-details.js | 14 +++++++++++--- ui/app/components/pending-tx.js | 10 +++++++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js index 61e18c706..b1ab9576b 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -12,7 +12,6 @@ const addressSummary = util.addressSummary const nameForAddress = require('../../lib/contract-namer') const HexInput = require('./hex-as-decimal-input') - module.exports = PendingTxDetails inherits(PendingTxDetails, Component) @@ -35,8 +34,8 @@ PTXP.render = function () { const gas = state.gas || txParams.gas const gasPrice = state.gasPrice || txData.gasPrice - const gasDefault = gas - const gasPriceDefault = gasPrice + const gasDefault = txParams.gas + const gasPriceDefault = txData.gasPrice var txFee = state.txFee || txData.txFee || '' var maxCost = state.maxCost || txData.maxCost || '' @@ -301,6 +300,15 @@ PTXP.calculateGas = function () { } } +PTXP.resetGasFields = function () { + log.debug(`pending-tx-details#resetGasFields`) + const txData = this.props.txData + this.setState({ + gas: txData.txParams.gas, + gasPrice: txData.gasPrice, + }) +} + // After a customizable state value has been updated, PTXP.gatherParams = function () { log.debug(`pending-tx-details#gatherParams`) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 3c898edec..d39cbc0f8 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -2,6 +2,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits const PendingTxDetails = require('./pending-tx-details') +const extend = require('xtend') module.exports = PendingTx @@ -12,6 +13,7 @@ function PendingTx () { PendingTx.prototype.render = function () { const props = this.props + const newProps = extend(props, {ref: 'details'}) const txData = props.txData return ( @@ -21,7 +23,7 @@ PendingTx.prototype.render = function () { }, [ // tx info - h(PendingTxDetails, props), + h(PendingTxDetails, newProps), h('style', ` .conf-buttons button { @@ -71,6 +73,12 @@ PendingTx.prototype.render = function () { h('button.cancel.btn-red', { onClick: props.cancelTransaction, }, 'Reject'), + + h('button', { + onClick: () => { + this.refs.details.resetGasFields() + }, + }, 'Reset'), ]), ]) )