feature/default_network_editable
Alexander Tseung 7 years ago
commit ecc39c5a7a
  1. 36
      CHANGELOG.md
  2. 4
      README.md
  3. 10
      app/_locales/ko/messages.json
  4. BIN
      app/images/coinbase logo.png
  5. 128
      app/images/metamask-fox.svg
  6. 21
      app/images/popout.svg
  7. BIN
      app/images/shapeshift logo.png
  8. 2
      app/manifest.json
  9. 22
      app/scripts/config.js
  10. 12
      app/scripts/contentscript.js
  11. 1
      app/scripts/controllers/blacklist.js
  12. 104
      app/scripts/controllers/network.js
  13. 8
      app/scripts/controllers/preferences.js
  14. 110
      app/scripts/controllers/recent-blocks.js
  15. 46
      app/scripts/controllers/transactions.js
  16. 2
      app/scripts/lib/account-tracker.js
  17. 10
      app/scripts/lib/pending-tx-tracker.js
  18. 57
      app/scripts/lib/tx-gas-utils.js
  19. 6
      app/scripts/lib/tx-state-manager.js
  20. 100
      app/scripts/metamask-controller.js
  21. 2
      app/scripts/notice-controller.js
  22. 13
      app/scripts/popup.js
  23. 1
      development/states/first-time.json
  24. 739
      development/states/pending-tx.json
  25. 6
      docker-compose.yml
  26. 43
      gulpfile.js
  27. 3
      mascara/server/index.js
  28. 4
      mascara/server/util.js
  29. 39
      mascara/src/app/first-time/create-password-screen.js
  30. 34
      mascara/src/app/first-time/index.css
  31. 2
      notices/archive/notice_2.md
  32. 2
      notices/notices.json
  33. 35
      old-ui/app/account-detail.js
  34. 19
      old-ui/app/app.js
  35. 12
      old-ui/app/components/pending-tx.js
  36. 6
      old-ui/app/conf-tx.js
  37. 2
      old-ui/app/config.js
  38. 13
      old-ui/app/css/index.css
  39. 18
      old-ui/app/send.js
  40. 27
      package.json
  41. 4
      test/base.conf.js
  42. 15
      test/stub/provider.js
  43. 3
      test/unit/actions/tx_test.js
  44. 68
      test/unit/metamask-controller-test.js
  45. 61
      test/unit/pending-tx-test.js
  46. 48
      test/unit/preferences-controller-test.js
  47. 42
      test/unit/tx-controller-test.js
  48. 32
      test/unit/tx-gas-util-test.js
  49. 12
      test/unit/util_test.js
  50. 34
      ui/app/accounts/import/index.js
  51. 96
      ui/app/accounts/new-account/create-form.js
  52. 81
      ui/app/accounts/new-account/index.js
  53. 104
      ui/app/actions.js
  54. 29
      ui/app/app.js
  55. 28
      ui/app/components/account-menu/index.js
  56. 5
      ui/app/components/balance-component.js
  57. 2
      ui/app/components/coinbase-form.js
  58. 12
      ui/app/components/currency-input.js
  59. 10
      ui/app/components/dropdowns/components/account-dropdowns.js
  60. 6
      ui/app/components/mascot.js
  61. 4
      ui/app/components/modals/account-details-modal.js
  62. 2
      ui/app/components/modals/buy-options-modal.js
  63. 184
      ui/app/components/modals/deposit-ether-modal.js
  64. 10
      ui/app/components/modals/export-private-key-modal.js
  65. 4
      ui/app/components/modals/hide-token-confirmation-modal.js
  66. 32
      ui/app/components/modals/modal.js
  67. 41
      ui/app/components/network.js
  68. 30
      ui/app/components/pages/add-token.js
  69. 40
      ui/app/components/pages/import-account/json.js
  70. 61
      ui/app/components/pages/import-account/private-key.js
  71. 5
      ui/app/components/pages/keychains/restore-vault.js
  72. 14
      ui/app/components/pages/settings/settings.js
  73. 28
      ui/app/components/pending-tx/confirm-send-ether.js
  74. 43
      ui/app/components/pending-tx/confirm-send-token.js
  75. 3
      ui/app/components/send/gas-fee-display-v2.js
  76. 3
      ui/app/components/send/send-constants.js
  77. 4
      ui/app/components/send/send-v2-container.js
  78. 468
      ui/app/components/shapeshift-form.js
  79. 50
      ui/app/components/shift-list-item.js
  80. 6
      ui/app/components/token-cell.js
  81. 129
      ui/app/components/transaction-list-item.js
  82. 10
      ui/app/components/tx-list-item.js
  83. 19
      ui/app/components/tx-list.js
  84. 20
      ui/app/components/tx-view.js
  85. 4
      ui/app/components/wallet-view.js
  86. 22
      ui/app/css/itcss/components/add-token.scss
  87. 38
      ui/app/css/itcss/components/buttons.scss
  88. 26
      ui/app/css/itcss/components/confirm.scss
  89. 21
      ui/app/css/itcss/components/header.scss
  90. 46
      ui/app/css/itcss/components/hero-balance.scss
  91. 2
      ui/app/css/itcss/components/index.scss
  92. 304
      ui/app/css/itcss/components/modal.scss
  93. 42
      ui/app/css/itcss/components/network.scss
  94. 192
      ui/app/css/itcss/components/new-account.scss
  95. 52
      ui/app/css/itcss/components/newui-sections.scss
  96. 34
      ui/app/css/itcss/components/send.scss
  97. 15
      ui/app/css/itcss/components/token-list.scss
  98. 30
      ui/app/css/itcss/components/transaction-list.scss
  99. 19
      ui/app/css/itcss/components/wallet-balance.scss
  100. 4
      ui/app/css/itcss/settings/typography.scss
  101. Some files were not shown because too many files have changed in this diff Show More

@ -2,6 +2,42 @@
## Current Master ## Current Master
## 3.13.5 2018-1-16
- Estimating gas limit for simple ether sends now faster & cheaper, by avoiding VM usage on recipients with no code.
- Add an extra px to address for Firefox clipping.
- Fix Firefox scrollbar.
- Open metamask popup for transaction confirmation before gas estimation finishes and add a loading screen over transaction confirmation.
- Fix bug that prevented eth_signTypedData from signing bytes.
- Further improve gas price estimation.
## 3.13.4 2018-1-9
- Remove recipient field if application initializes a tx with an empty string, or 0x, and tx data. Throw an error with the same condition, but without tx data.
- Improve gas price suggestion to be closer to the lowest that will be accepted.
- Throw an error if a application tries to submit a tx whose value is a decimal, and inform that it should be in wei.
- Fix bug that prevented updating custom token details.
- No longer mark long-pending transactions as failed, since we now have button to retry with higher gas.
- Fix rounding error when specifying an ether amount that has too much precision.
- Fix bug where incorrectly inputting seed phrase would prevent any future attempts from succeeding.
## 3.13.3 2017-12-14
- Show tokens that are held that have no balance.
- Reduce load on Infura by using a new block polling endpoint.
## 3.13.2 2017-12-9
- Reduce new block polling interval to 8000 ms, to ease server load.
## 3.13.1 2017-12-7
- Allow Dapps to specify a transaction nonce, allowing dapps to propose resubmit and force-cancel transactions.
## 3.13.0 2017-12-7
- Allow resubmitting transactions that are taking long to complete.
## 3.12.1 2017-11-29 ## 3.12.1 2017-11-29
- Fix bug where a user could be shown two different seed phrases. - Fix bug where a user could be shown two different seed phrases.

@ -1,10 +1,10 @@
# MetaMask Plugin # MetaMask Browser Extension
[![Build Status](https://circleci.com/gh/MetaMask/metamask-extension.svg?style=shield&circle-token=a1ddcf3cd38e29267f254c9c59d556d513e3a1fd)](https://circleci.com/gh/MetaMask/metamask-extension) [![Coverage Status](https://coveralls.io/repos/github/MetaMask/metamask-extension/badge.svg?branch=master)](https://coveralls.io/github/MetaMask/metamask-extension?branch=master) [![Greenkeeper badge](https://badges.greenkeeper.io/MetaMask/metamask-extension.svg)](https://greenkeeper.io/) [![Stories in Ready](https://badge.waffle.io/MetaMask/metamask-extension.png?label=in%20progress&title=waffle.io)](http://waffle.io/MetaMask/metamask-extension) [![Build Status](https://circleci.com/gh/MetaMask/metamask-extension.svg?style=shield&circle-token=a1ddcf3cd38e29267f254c9c59d556d513e3a1fd)](https://circleci.com/gh/MetaMask/metamask-extension) [![Coverage Status](https://coveralls.io/repos/github/MetaMask/metamask-extension/badge.svg?branch=master)](https://coveralls.io/github/MetaMask/metamask-extension?branch=master) [![Greenkeeper badge](https://badges.greenkeeper.io/MetaMask/metamask-extension.svg)](https://greenkeeper.io/) [![Stories in Ready](https://badge.waffle.io/MetaMask/metamask-extension.png?label=in%20progress&title=waffle.io)](http://waffle.io/MetaMask/metamask-extension)
## Support ## Support
If you're a user seeking support, [here is our support site](http://metamask.consensyssupport.happyfox.com). If you're a user seeking support, [here is our support site](https://metamask.helpscoutdocs.com/).
## Developing Compatible Dapps ## Developing Compatible Dapps

@ -0,0 +1,10 @@
{
"appName": {
"message": "MetaMask",
"description": "The name of the application"
},
"appDescription": {
"message": "이더리움 계좌 관리",
"description": "The description of the application"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

@ -0,0 +1,128 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 318.6 318.6"
style="enable-background:new 0 0 318.6 318.6;" xml:space="preserve">
<style type="text/css">
.st0{fill:#161616;stroke:#161616;}
.st1{fill:#E4761B;stroke:#E4761B;stroke-linecap:round;stroke-linejoin:round;}
.st2{fill:#763D16;stroke:#763D16;stroke-linecap:round;stroke-linejoin:round;}
.st3{fill:#F6851B;stroke:#F6851B;stroke-linecap:round;stroke-linejoin:round;}
.st4{fill:#E2761B;stroke:#E2761B;stroke-linecap:round;stroke-linejoin:round;}
.st5{fill:#CD6116;stroke:#CD6116;stroke-linecap:round;stroke-linejoin:round;}
.st6{fill:#C0AD9E;stroke:#C0AD9E;stroke-linecap:round;stroke-linejoin:round;}
.st7{fill:#D7C1B3;stroke:#D7C1B3;stroke-linecap:round;stroke-linejoin:round;}
.st8{fill:#E4751F;stroke:#E4751F;stroke-linecap:round;stroke-linejoin:round;}
.st9{fill:#233447;stroke:#233447;stroke-linecap:round;stroke-linejoin:round;}
.st10{fill:#161616;stroke:#161616;stroke-linecap:round;stroke-linejoin:round;}
</style>
<polygon class="st0" points="277.3,145.6 272.3,142 280.3,134.7 274.2,129.9 282.2,123.8 276.9,119.8 285.3,79 272.7,41.1
191.6,71.4 124.1,71.4 43,41.1 30.4,79 38.9,119.8 33.5,123.8 41.5,129.9 35.4,134.7 43.4,142 38.4,145.6 49.9,159.1 32.5,213.3
48.6,268.6 105.3,253 116.3,262 138.7,277.5 177,277.5 199.4,262 210.4,253 267.1,268.6 283.3,213.3 265.8,159.1 "/>
<g>
<polygon class="st1" points="105.3,253 48.6,268.6 32.5,213.3 "/>
<polygon class="st1" points="283.3,213.3 267.1,268.6 210.4,253 "/>
<polygon class="st2" points="265.8,159.1 213.5,143.8 231.8,139 "/>
<polygon class="st2" points="49.9,159.1 84,139 102.2,143.8 "/>
<polygon class="st2" points="43.4,142 41.5,129.9 84,139 "/>
<polygon class="st2" points="272.3,142 231.8,139 274.2,129.9 "/>
<polygon class="st2" points="272.3,142 265.8,159.1 231.8,139 "/>
<polygon class="st2" points="43.4,142 84,139 49.9,159.1 "/>
<polygon class="st2" points="231.8,139 276.9,119.8 274.2,129.9 "/>
<polygon class="st2" points="84,139 41.5,129.9 38.9,119.8 "/>
<polygon class="st3" points="124.1,71.4 191.6,71.4 176.5,112.5 "/>
<polygon class="st3" points="176.5,112.5 139.2,112.5 124.1,71.4 "/>
<polygon class="st2" points="276.9,119.8 231.8,139 231,87.4 "/>
<polygon class="st2" points="102.2,143.8 84,139 84.7,87.4 "/>
<polygon class="st2" points="84.7,87.4 84,139 38.9,119.8 "/>
<polygon class="st2" points="231,87.4 231.8,139 213.5,143.8 "/>
<polygon class="st1" points="139.2,112.5 43,41.1 124.1,71.4 "/>
<polygon class="st4" points="272.7,41.1 176.5,112.5 191.6,71.4 "/>
<polygon class="st1" points="210.4,253 236.9,213.3 283.3,213.3 "/>
<polygon class="st1" points="32.5,213.3 78.9,213.3 105.3,253 "/>
<polygon class="st3" points="229.3,167.7 283.3,213.3 236.9,213.3 "/>
<polygon class="st3" points="86.4,167.7 32.5,213.3 49.9,159.1 "/>
<polygon class="st3" points="78.9,213.3 32.5,213.3 86.4,167.7 "/>
<polygon class="st3" points="229.3,167.7 265.8,159.1 283.3,213.3 "/>
<polygon class="st2" points="84.7,87.4 139.2,112.5 102.2,143.8 "/>
<polygon class="st2" points="213.5,143.8 176.5,112.5 231,87.4 "/>
<polygon class="st2" points="265.8,159.1 272.3,142 277.3,145.6 "/>
<polygon class="st2" points="49.9,159.1 38.4,145.6 43.4,142 "/>
<polygon class="st2" points="272.3,142 274.2,129.9 280.3,134.7 "/>
<polygon class="st2" points="43.4,142 35.4,134.7 41.5,129.9 "/>
<polygon class="st2" points="33.5,123.8 38.9,119.8 41.5,129.9 "/>
<polygon class="st2" points="282.2,123.8 274.2,129.9 276.9,119.8 "/>
<polygon class="st3" points="49.9,159.1 102.2,143.8 86.4,167.7 "/>
<polygon class="st3" points="265.8,159.1 229.3,167.7 213.5,143.8 "/>
<polygon class="st2" points="38.9,119.8 30.4,79 84.7,87.4 "/>
<polygon class="st2" points="231,87.4 285.3,79 276.9,119.8 "/>
<polygon class="st1" points="102.2,143.8 139.2,112.5 142.6,170.2 "/>
<polygon class="st1" points="213.5,143.8 229.3,167.7 173.1,170.2 "/>
<polygon class="st1" points="173.1,170.2 176.5,112.5 213.5,143.8 "/>
<polygon class="st1" points="142.6,170.2 86.4,167.7 102.2,143.8 "/>
<polygon class="st2" points="272.7,41.1 285.3,79 231,87.4 "/>
<polygon class="st2" points="43,41.1 139.2,112.5 84.7,87.4 "/>
<polygon class="st2" points="231,87.4 176.5,112.5 272.7,41.1 "/>
<polygon class="st2" points="84.7,87.4 30.4,79 43,41.1 "/>
<polygon class="st5" points="105.3,253 78.9,213.3 110,213.7 "/>
<polygon class="st5" points="210.4,253 205.7,213.7 236.9,213.3 "/>
<polygon class="st3" points="173.1,170.2 142.6,170.2 139.2,112.5 "/>
<polygon class="st3" points="139.2,112.5 176.5,112.5 173.1,170.2 "/>
<polygon class="st6" points="116.3,262 105.3,253 136.8,267.9 "/>
<polygon class="st6" points="178.9,267.9 210.4,253 199.4,262 "/>
<polygon class="st7" points="136.6,258.6 136.8,267.9 105.3,253 "/>
<polygon class="st7" points="179.2,258.6 210.4,253 178.9,267.9 "/>
<polygon class="st3" points="86.4,167.7 110,213.7 78.9,213.3 "/>
<polygon class="st3" points="236.9,213.3 205.7,213.7 229.3,167.7 "/>
<polygon class="st8" points="86.4,167.7 109.2,190.8 110,213.7 "/>
<polygon class="st8" points="229.3,167.7 205.7,213.7 206.6,190.8 "/>
<polygon class="st7" points="105.3,253 139.2,236.5 136.6,258.6 "/>
<polygon class="st7" points="210.4,253 179.2,258.6 176.5,236.5 "/>
<polygon class="st1" points="139.2,236.5 105.3,253 110,213.7 "/>
<polygon class="st1" points="176.5,236.5 205.7,213.7 210.4,253 "/>
<polygon class="st5" points="173.1,170.2 229.3,167.7 206.6,190.8 "/>
<polygon class="st5" points="109.2,190.8 86.4,167.7 142.6,170.2 "/>
<polygon class="st5" points="142.6,170.2 129.1,181.7 109.2,190.8 "/>
<polygon class="st5" points="206.6,190.8 186.6,181.7 173.1,170.2 "/>
<polygon class="st3" points="205.7,213.7 178.3,199.1 206.6,190.8 "/>
<polygon class="st3" points="110,213.7 109.2,190.8 137.4,199.1 "/>
<polygon class="st9" points="137.4,199.1 109.2,190.8 129.1,181.7 "/>
<polygon class="st9" points="178.3,199.1 186.6,181.7 206.6,190.8 "/>
<polygon class="st5" points="186.6,181.7 178.3,199.1 173.1,170.2 "/>
<polygon class="st5" points="129.1,181.7 142.6,170.2 137.4,199.1 "/>
<polygon class="st6" points="199.4,262 177,277.5 178.9,267.9 "/>
<polygon class="st6" points="136.8,267.9 138.7,277.5 116.3,262 "/>
<polygon class="st4" points="178.3,199.1 171.8,188.4 173.1,170.2 "/>
<polygon class="st8" points="137.4,199.1 142.6,170.2 143.9,188.4 "/>
<polygon class="st3" points="173.1,170.2 171.8,188.4 143.9,188.4 "/>
<polygon class="st3" points="143.9,188.4 142.6,170.2 173.1,170.2 "/>
<polygon class="st3" points="178.3,199.1 205.7,213.7 176.5,236.5 "/>
<polygon class="st3" points="139.2,236.5 110,213.7 137.4,199.1 "/>
<polygon class="st3" points="137.4,199.1 144,233.2 139.2,236.5 "/>
<polygon class="st3" points="176.5,236.5 171.7,233.2 178.3,199.1 "/>
<polygon class="st8" points="171.8,188.4 178.3,199.1 171.7,233.2 "/>
<polygon class="st8" points="143.9,188.4 144,233.2 137.4,199.1 "/>
<polygon class="st3" points="143.9,188.4 171.8,188.4 171.7,233.2 "/>
<polygon class="st3" points="171.7,233.2 144,233.2 143.9,188.4 "/>
<polygon class="st6" points="179.2,258.6 178.9,267.9 177,277.5 "/>
<polygon class="st6" points="138.7,277.5 136.8,267.9 136.6,258.6 "/>
<polygon class="st6" points="136.6,258.6 139,256.4 138.7,277.5 "/>
<polygon class="st6" points="177,277.5 176.7,256.4 179.2,258.6 "/>
<polygon class="st6" points="138.7,277.5 139,256.4 176.7,256.4 "/>
<polygon class="st6" points="176.7,256.4 177,277.5 138.7,277.5 "/>
<polygon class="st10" points="176.5,236.5 179.2,258.6 176.7,256.4 "/>
<polygon class="st10" points="139,256.4 136.6,258.6 139.2,236.5 "/>
<polygon class="st10" points="139.2,236.5 140.7,241.2 139,256.4 "/>
<polygon class="st10" points="176.7,256.4 175,241.2 176.5,236.5 "/>
<polygon class="st10" points="143.7,237.7 140.7,241.2 139.2,236.5 "/>
<polygon class="st10" points="176.5,236.5 175,241.2 172,237.7 "/>
<polygon class="st10" points="172,237.7 171.7,233.2 176.5,236.5 "/>
<polygon class="st10" points="139.2,236.5 144,233.2 143.7,237.7 "/>
<polygon class="st10" points="171.7,233.2 172,237.7 143.7,237.7 "/>
<polygon class="st10" points="143.7,237.7 144,233.2 171.7,233.2 "/>
<polygon class="st10" points="140.7,241.2 175,241.2 176.7,256.4 "/>
<polygon class="st10" points="176.7,256.4 139,256.4 140.7,241.2 "/>
<polygon class="st10" points="140.7,241.2 143.7,237.7 172,237.7 "/>
<polygon class="st10" points="172,237.7 175,241.2 140.7,241.2 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.6 KiB

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 48.2 (47327) - http://www.bohemiancoding.com/sketch -->
<title>popout</title>
<desc>Created with Sketch.</desc>
<defs>
<polygon id="path-1" points="-0.00035 0 10.9999 0 10.9999 10.9997 -0.00035 10.9997"></polygon>
</defs>
<g id="MetaMascara-Mobile---structured-TOKEN" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(-327.000000, -96.000000)">
<g id="popout" transform="translate(327.000000, 96.000000)">
<g id="Group-3" transform="translate(11.000000, 0.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="Clip-2"></g>
<path d="M10.9229,0.6177 C10.8209,0.3737 10.6269,0.1787 10.3819,0.0767 C10.2599,0.0267 10.1309,-0.0003 9.9999,-0.0003 L3.9999,-0.0003 C3.4479,-0.0003 2.9999,0.4477 2.9999,0.9997 C2.9999,1.5527 3.4479,1.9997 3.9999,1.9997 L7.5859,1.9997 L0.2929,9.2927 C-0.0981,9.6837 -0.0981,10.3167 0.2929,10.7067 C0.4879,10.9027 0.7439,10.9997 0.9999,10.9997 C1.2559,10.9997 1.5119,10.9027 1.7069,10.7067 L8.9999,3.4137 L8.9999,6.9997 C8.9999,7.5527 9.4479,7.9997 9.9999,7.9997 C10.5519,7.9997 10.9999,7.5527 10.9999,6.9997 L10.9999,0.9997 C10.9999,0.8697 10.9739,0.7407 10.9229,0.6177" id="Fill-1" fill="#4A4A4A" mask="url(#mask-2)"></path>
</g>
<path d="M19,10 C18.448,10 18,10.448 18,11 L18,19 C18,19.551 17.551,20 17,20 L3,20 C2.449,20 2,19.551 2,19 L2,5 C2,4.449 2.449,4 3,4 L11,4 C11.552,4 12,3.552 12,3 C12,2.448 11.552,2 11,2 L3,2 C1.346,2 0,3.346 0,5 L0,19 C0,20.654 1.346,22 3,22 L17,22 C18.654,22 20,20.654 20,19 L20,11 C20,10.448 19.552,10 19,10" id="Fill-4" fill="#4A4A4A"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

@ -1,7 +1,7 @@
{ {
"name": "MetaMask", "name": "MetaMask",
"short_name": "Metamask", "short_name": "Metamask",
"version": "4.0.4", "version": "4.0.9",
"manifest_version": 2, "manifest_version": 2,
"author": "https://metamask.io", "author": "https://metamask.io",
"description": "Ethereum Browser Extension", "description": "Ethereum Browser Extension",

@ -4,6 +4,15 @@ const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask'
const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask' const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask'
const LOCALHOST_RPC_URL = 'http://localhost:8545' const LOCALHOST_RPC_URL = 'http://localhost:8545'
const MAINET_RPC_URL_BETA = 'https://mainnet.infura.io/metamask2'
const ROPSTEN_RPC_URL_BETA = 'https://ropsten.infura.io/metamask2'
const KOVAN_RPC_URL_BETA = 'https://kovan.infura.io/metamask2'
const RINKEBY_RPC_URL_BETA = 'https://rinkeby.infura.io/metamask2'
const DEFAULT_RPC = 'rinkeby'
const OLD_UI_NETWORK_TYPE = 'network'
const BETA_UI_NETWORK_TYPE = 'networkBeta'
global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
module.exports = { module.exports = {
@ -14,9 +23,22 @@ module.exports = {
kovan: KOVAN_RPC_URL, kovan: KOVAN_RPC_URL,
rinkeby: RINKEBY_RPC_URL, rinkeby: RINKEBY_RPC_URL,
}, },
// Used for beta UI
networkBeta: {
localhost: LOCALHOST_RPC_URL,
mainnet: MAINET_RPC_URL_BETA,
ropsten: ROPSTEN_RPC_URL_BETA,
kovan: KOVAN_RPC_URL_BETA,
rinkeby: RINKEBY_RPC_URL_BETA,
},
networkNames: { networkNames: {
3: 'Ropsten', 3: 'Ropsten',
4: 'Rinkeby', 4: 'Rinkeby',
42: 'Kovan', 42: 'Kovan',
}, },
enums: {
DEFAULT_RPC,
OLD_UI_NETWORK_TYPE,
BETA_UI_NETWORK_TYPE,
},
} }

@ -96,7 +96,7 @@ function logStreamDisconnectWarning (remoteLabel, err) {
} }
function shouldInjectWeb3 () { function shouldInjectWeb3 () {
return doctypeCheck() || suffixCheck() return doctypeCheck() && suffixCheck() && documentElementCheck()
} }
function doctypeCheck () { function doctypeCheck () {
@ -104,7 +104,7 @@ function doctypeCheck () {
if (doctype) { if (doctype) {
return doctype.name === 'html' return doctype.name === 'html'
} else { } else {
return false return true
} }
} }
@ -121,6 +121,14 @@ function suffixCheck () {
return true return true
} }
function documentElementCheck () {
var documentElement = document.documentElement.nodeName
if (documentElement) {
return documentElement.toLowerCase() === 'html'
}
return true
}
function redirectToPhishingWarning () { function redirectToPhishingWarning () {
console.log('MetaMask - redirecting to phishing warning') console.log('MetaMask - redirecting to phishing warning')
window.location.href = 'https://metamask.io/phishing.html' window.location.href = 'https://metamask.io/phishing.html'

@ -57,3 +57,4 @@ class BlacklistController {
} }
module.exports = BlacklistController module.exports = BlacklistController

@ -1,18 +1,26 @@
const assert = require('assert') const assert = require('assert')
const EventEmitter = require('events') const EventEmitter = require('events')
const createMetamaskProvider = require('web3-provider-engine/zero.js') const createMetamaskProvider = require('web3-provider-engine/zero.js')
const SubproviderFromProvider = require('web3-provider-engine/subproviders/web3.js')
const createInfuraProvider = require('eth-json-rpc-infura/src/createProvider')
const ObservableStore = require('obs-store') const ObservableStore = require('obs-store')
const ComposedStore = require('obs-store/lib/composed') const ComposedStore = require('obs-store/lib/composed')
const extend = require('xtend') const extend = require('xtend')
const EthQuery = require('eth-query') const EthQuery = require('eth-query')
const createEventEmitterProxy = require('../lib/events-proxy.js') const createEventEmitterProxy = require('../lib/events-proxy.js')
const RPC_ADDRESS_LIST = require('../config.js').network const networkConfig = require('../config.js')
const DEFAULT_RPC = RPC_ADDRESS_LIST['rinkeby'] const { OLD_UI_NETWORK_TYPE, DEFAULT_RPC } = networkConfig.enums
const INFURA_PROVIDER_TYPES = ['ropsten', 'rinkeby', 'kovan', 'mainnet']
module.exports = class NetworkController extends EventEmitter { module.exports = class NetworkController extends EventEmitter {
constructor (config) { constructor (config) {
super() super()
this._networkEndpointVersion = OLD_UI_NETWORK_TYPE
this._networkEndpoints = this.getNetworkEndpoints(OLD_UI_NETWORK_TYPE)
this._defaultRpc = this._networkEndpoints[DEFAULT_RPC]
config.provider.rpcTarget = this.getRpcAddressForType(config.provider.type, config.provider) config.provider.rpcTarget = this.getRpcAddressForType(config.provider.type, config.provider)
this.networkStore = new ObservableStore('loading') this.networkStore = new ObservableStore('loading')
this.providerStore = new ObservableStore(config.provider) this.providerStore = new ObservableStore(config.provider)
@ -22,10 +30,32 @@ module.exports = class NetworkController extends EventEmitter {
this.on('networkDidChange', this.lookupNetwork) this.on('networkDidChange', this.lookupNetwork)
} }
async setNetworkEndpoints (version) {
if (version === this._networkEndpointVersion) {
return
}
this._networkEndpointVersion = version
this._networkEndpoints = this.getNetworkEndpoints(version)
this._defaultRpc = this._networkEndpoints[DEFAULT_RPC]
const { type } = this.getProviderConfig()
return this.setProviderType(type, true)
}
getNetworkEndpoints (version = OLD_UI_NETWORK_TYPE) {
return networkConfig[version]
}
initializeProvider (_providerParams) { initializeProvider (_providerParams) {
this._baseProviderParams = _providerParams this._baseProviderParams = _providerParams
const rpcUrl = this.getCurrentRpcAddress() const { type, rpcTarget } = this.providerStore.getState()
this._configureStandardProvider({ rpcUrl }) // map rpcTarget to rpcUrl
const opts = {
type,
rpcUrl: rpcTarget,
}
this._configureProvider(opts)
this._proxy.on('block', this._logBlock.bind(this)) this._proxy.on('block', this._logBlock.bind(this))
this._proxy.on('error', this.verifyNetwork.bind(this)) this._proxy.on('error', this.verifyNetwork.bind(this))
this.ethQuery = new EthQuery(this._proxy) this.ethQuery = new EthQuery(this._proxy)
@ -76,14 +106,17 @@ module.exports = class NetworkController extends EventEmitter {
return this.getRpcAddressForType(provider.type) return this.getRpcAddressForType(provider.type)
} }
async setProviderType (type) { async setProviderType (type, forceUpdate = false) {
assert(type !== 'rpc', `NetworkController.setProviderType - cannot connect by type "rpc"`) assert(type !== 'rpc', `NetworkController.setProviderType - cannot connect by type "rpc"`)
// skip if type already matches // skip if type already matches
if (type === this.getProviderConfig().type) return if (type === this.getProviderConfig().type && !forceUpdate) {
return
}
const rpcTarget = this.getRpcAddressForType(type) const rpcTarget = this.getRpcAddressForType(type)
assert(rpcTarget, `NetworkController - unknown rpc address for type "${type}"`) assert(rpcTarget, `NetworkController - unknown rpc address for type "${type}"`)
this.providerStore.updateState({ type, rpcTarget }) this.providerStore.updateState({ type, rpcTarget })
this._switchNetwork({ rpcUrl: rpcTarget }) this._switchNetwork({ type })
} }
getProviderConfig () { getProviderConfig () {
@ -91,22 +124,67 @@ module.exports = class NetworkController extends EventEmitter {
} }
getRpcAddressForType (type, provider = this.getProviderConfig()) { getRpcAddressForType (type, provider = this.getProviderConfig()) {
if (RPC_ADDRESS_LIST[type]) return RPC_ADDRESS_LIST[type] if (this._networkEndpoints[type]) {
return provider && provider.rpcTarget ? provider.rpcTarget : DEFAULT_RPC return this._networkEndpoints[type]
}
return provider && provider.rpcTarget ? provider.rpcTarget : this._defaultRpc
} }
// //
// Private // Private
// //
_switchNetwork (providerParams) { _switchNetwork (opts) {
this.setNetworkState('loading') this.setNetworkState('loading')
this._configureStandardProvider(providerParams) this._configureProvider(opts)
this.emit('networkDidChange') this.emit('networkDidChange')
} }
_configureStandardProvider (_providerParams) { _configureProvider (opts) {
const providerParams = extend(this._baseProviderParams, _providerParams) // type-based rpc endpoints
const { type } = opts
if (type) {
// type-based infura rpc endpoints
const isInfura = INFURA_PROVIDER_TYPES.includes(type)
opts.rpcUrl = this.getRpcAddressForType(type)
if (isInfura) {
this._configureInfuraProvider(opts)
// other type-based rpc endpoints
} else {
this._configureStandardProvider(opts)
}
// url-based rpc endpoints
} else {
this._configureStandardProvider(opts)
}
}
_configureInfuraProvider (opts) {
log.info('_configureInfuraProvider', opts)
const infuraProvider = createInfuraProvider({
network: opts.type,
})
const infuraSubprovider = new SubproviderFromProvider(infuraProvider)
const providerParams = extend(this._baseProviderParams, {
rpcUrl: opts.rpcUrl,
engineParams: {
pollingInterval: 8000,
blockTrackerProvider: infuraProvider,
},
dataSubprovider: infuraSubprovider,
})
const provider = createMetamaskProvider(providerParams)
this._setProvider(provider)
}
_configureStandardProvider ({ rpcUrl }) {
const providerParams = extend(this._baseProviderParams, {
rpcUrl,
engineParams: {
pollingInterval: 8000,
},
})
const provider = createMetamaskProvider(providerParams) const provider = createMetamaskProvider(providerParams)
this._setProvider(provider) this._setProvider(provider)
} }

@ -36,22 +36,24 @@ class PreferencesController {
return this.store.getState().selectedAddress return this.store.getState().selectedAddress
} }
addToken (rawAddress, symbol, decimals) { async addToken (rawAddress, symbol, decimals) {
const address = normalizeAddress(rawAddress) const address = normalizeAddress(rawAddress)
const newEntry = { address, symbol, decimals } const newEntry = { address, symbol, decimals }
const tokens = this.store.getState().tokens const tokens = this.store.getState().tokens
const previousIndex = tokens.find((token, index) => { const previousEntry = tokens.find((token, index) => {
return token.address === address return token.address === address
}) })
const previousIndex = tokens.indexOf(previousEntry)
if (previousIndex) { if (previousEntry) {
tokens[previousIndex] = newEntry tokens[previousIndex] = newEntry
} else { } else {
tokens.push(newEntry) tokens.push(newEntry)
} }
this.store.updateState({ tokens }) this.store.updateState({ tokens })
return Promise.resolve(tokens) return Promise.resolve(tokens)
} }

@ -0,0 +1,110 @@
const ObservableStore = require('obs-store')
const extend = require('xtend')
const BN = require('ethereumjs-util').BN
const EthQuery = require('eth-query')
class RecentBlocksController {
constructor (opts = {}) {
const { blockTracker, provider } = opts
this.blockTracker = blockTracker
this.ethQuery = new EthQuery(provider)
this.historyLength = opts.historyLength || 40
const initState = extend({
recentBlocks: [],
}, opts.initState)
this.store = new ObservableStore(initState)
this.blockTracker.on('block', this.processBlock.bind(this))
this.backfill()
}
resetState () {
this.store.updateState({
recentBlocks: [],
})
}
processBlock (newBlock) {
const block = this.mapTransactionsToPrices(newBlock)
const state = this.store.getState()
state.recentBlocks.push(block)
while (state.recentBlocks.length > this.historyLength) {
state.recentBlocks.shift()
}
this.store.updateState(state)
}
backfillBlock (newBlock) {
const block = this.mapTransactionsToPrices(newBlock)
const state = this.store.getState()
if (state.recentBlocks.length < this.historyLength) {
state.recentBlocks.unshift(block)
}
this.store.updateState(state)
}
mapTransactionsToPrices (newBlock) {
const block = extend(newBlock, {
gasPrices: newBlock.transactions.map((tx) => {
return tx.gasPrice
}),
})
delete block.transactions
return block
}
async backfill() {
this.blockTracker.once('block', async (block) => {
let blockNum = block.number
let recentBlocks
let state = this.store.getState()
recentBlocks = state.recentBlocks
while (recentBlocks.length < this.historyLength) {
try {
let blockNumBn = new BN(blockNum.substr(2), 16)
const newNum = blockNumBn.subn(1).toString(10)
const newBlock = await this.getBlockByNumber(newNum)
if (newBlock) {
this.backfillBlock(newBlock)
blockNum = newBlock.number
}
state = this.store.getState()
recentBlocks = state.recentBlocks
} catch (e) {
log.error(e)
}
await this.wait()
}
})
}
async wait () {
return new Promise((resolve) => {
setTimeout(resolve, 100)
})
}
async getBlockByNumber (number) {
const bn = new BN(number)
return new Promise((resolve, reject) => {
this.ethQuery.getBlockByNumber('0x' + bn.toString(16), true, (err, block) => {
if (err) reject(err)
resolve(block)
})
})
}
}
module.exports = RecentBlocksController

@ -32,6 +32,7 @@ module.exports = class TransactionController extends EventEmitter {
this.provider = opts.provider this.provider = opts.provider
this.blockTracker = opts.blockTracker this.blockTracker = opts.blockTracker
this.signEthTx = opts.signTransaction this.signEthTx = opts.signTransaction
this.getGasPrice = opts.getGasPrice
this.memStore = new ObservableStore({}) this.memStore = new ObservableStore({})
this.query = new EthQuery(this.provider) this.query = new EthQuery(this.provider)
@ -59,7 +60,6 @@ module.exports = class TransactionController extends EventEmitter {
this.pendingTxTracker = new PendingTransactionTracker({ this.pendingTxTracker = new PendingTransactionTracker({
provider: this.provider, provider: this.provider,
nonceTracker: this.nonceTracker, nonceTracker: this.nonceTracker,
retryTimePeriod: 86400000, // Retry 3500 blocks, or about 1 day.
publishTransaction: (rawTx) => this.query.sendRawTransaction(rawTx), publishTransaction: (rawTx) => this.query.sendRawTransaction(rawTx),
getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager), getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager),
getCompletedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager), getCompletedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager),
@ -138,18 +138,19 @@ module.exports = class TransactionController extends EventEmitter {
async newUnapprovedTransaction (txParams) { async newUnapprovedTransaction (txParams) {
log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`) log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`)
const txMeta = await this.addUnapprovedTransaction(txParams) const initialTxMeta = await this.addUnapprovedTransaction(txParams)
this.emit('newUnapprovedTx', txMeta)
// listen for tx completion (success, fail) // listen for tx completion (success, fail)
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.txStateManager.once(`${txMeta.id}:finished`, (completedTx) => { this.txStateManager.once(`${initialTxMeta.id}:finished`, (finishedTxMeta) => {
switch (completedTx.status) { switch (finishedTxMeta.status) {
case 'submitted': case 'submitted':
return resolve(completedTx.hash) return resolve(finishedTxMeta.hash)
case 'rejected': case 'rejected':
return reject(new Error('MetaMask Tx Signature: User denied transaction signature.')) return reject(new Error('MetaMask Tx Signature: User denied transaction signature.'))
case 'failed':
return reject(new Error(finishedTxMeta.err.message))
default: default:
return reject(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(completedTx.txParams)}`)) return reject(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`))
} }
}) })
}) })
@ -165,11 +166,16 @@ module.exports = class TransactionController extends EventEmitter {
status: 'unapproved', status: 'unapproved',
metamaskNetworkId: this.getNetwork(), metamaskNetworkId: this.getNetwork(),
txParams: txParams, txParams: txParams,
loadingDefaults: true,
} }
this.addTx(txMeta)
this.emit('newUnapprovedTx', txMeta)
// add default tx params // add default tx params
await this.addTxDefaults(txMeta) await this.addTxDefaults(txMeta)
txMeta.loadingDefaults = false
// save txMeta // save txMeta
this.addTx(txMeta) this.txStateManager.updateTx(txMeta)
return txMeta return txMeta
} }
@ -177,13 +183,28 @@ module.exports = class TransactionController extends EventEmitter {
const txParams = txMeta.txParams const txParams = txMeta.txParams
// ensure value // ensure value
txMeta.gasPriceSpecified = Boolean(txParams.gasPrice) txMeta.gasPriceSpecified = Boolean(txParams.gasPrice)
const gasPrice = txParams.gasPrice || await this.query.gasPrice() txMeta.nonceSpecified = Boolean(txParams.nonce)
let gasPrice = txParams.gasPrice
if (!gasPrice) {
gasPrice = this.getGasPrice ? this.getGasPrice() : await this.query.gasPrice()
}
txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16)) txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16))
txParams.value = txParams.value || '0x0' txParams.value = txParams.value || '0x0'
// set gasLimit // set gasLimit
return await this.txGasUtil.analyzeGasUsage(txMeta) return await this.txGasUtil.analyzeGasUsage(txMeta)
} }
async retryTransaction (txId) {
this.txStateManager.setTxStatusUnapproved(txId)
const txMeta = this.txStateManager.getTx(txId)
txMeta.lastGasPrice = txMeta.txParams.gasPrice
this.txStateManager.updateTx(txMeta, 'retryTransaction: manual retry')
}
async updateTransaction (txMeta) {
this.txStateManager.updateTx(txMeta, 'confTx: user updated transaction')
}
async updateAndApproveTransaction (txMeta) { async updateAndApproveTransaction (txMeta) {
this.txStateManager.updateTx(txMeta, 'confTx: user approved transaction') this.txStateManager.updateTx(txMeta, 'confTx: user approved transaction')
await this.approveTransaction(txMeta.id) await this.approveTransaction(txMeta.id)
@ -200,7 +221,12 @@ module.exports = class TransactionController extends EventEmitter {
// wait for a nonce // wait for a nonce
nonceLock = await this.nonceTracker.getNonceLock(fromAddress) nonceLock = await this.nonceTracker.getNonceLock(fromAddress)
// add nonce to txParams // add nonce to txParams
txMeta.txParams.nonce = ethUtil.addHexPrefix(nonceLock.nextNonce.toString(16)) const nonce = txMeta.nonceSpecified ? txMeta.txParams.nonce : nonceLock.nextNonce
if (nonce > nonceLock.nextNonce) {
const message = `Specified nonce may not be larger than account's next valid nonce.`
throw new Error(message)
}
txMeta.txParams.nonce = ethUtil.addHexPrefix(nonce.toString(16))
// add nonce debugging information to txMeta // add nonce debugging information to txMeta
txMeta.nonceDetails = nonceLock.nonceDetails txMeta.nonceDetails = nonceLock.nonceDetails
this.txStateManager.updateTx(txMeta, 'transactions#approveTransaction') this.txStateManager.updateTx(txMeta, 'transactions#approveTransaction')

@ -117,8 +117,6 @@ class AccountTracker extends EventEmitter {
const query = this._query const query = this._query
async.parallel({ async.parallel({
balance: query.getBalance.bind(query, address), balance: query.getBalance.bind(query, address),
nonce: query.getTransactionCount.bind(query, address),
code: query.getCode.bind(query, address),
}, cb) }, cb)
} }

@ -23,7 +23,6 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
this.query = new EthQuery(config.provider) this.query = new EthQuery(config.provider)
this.nonceTracker = config.nonceTracker this.nonceTracker = config.nonceTracker
// default is one day // default is one day
this.retryTimePeriod = config.retryTimePeriod || 86400000
this.getPendingTransactions = config.getPendingTransactions this.getPendingTransactions = config.getPendingTransactions
this.getCompletedTransactions = config.getCompletedTransactions this.getCompletedTransactions = config.getCompletedTransactions
this.publishTransaction = config.publishTransaction this.publishTransaction = config.publishTransaction
@ -106,12 +105,6 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
this.emit('tx:block-update', txMeta, latestBlockNumber) this.emit('tx:block-update', txMeta, latestBlockNumber)
} }
if (Date.now() > txMeta.time + this.retryTimePeriod) {
const hours = (this.retryTimePeriod / 3.6e+6).toFixed(1)
const err = new Error(`Gave up submitting after ${hours} hours.`)
return this.emit('tx:failed', txMeta.id, err)
}
const firstRetryBlockNumber = txMeta.firstRetryBlockNumber || latestBlockNumber const firstRetryBlockNumber = txMeta.firstRetryBlockNumber || latestBlockNumber
const txBlockDistance = Number.parseInt(latestBlockNumber, 16) - Number.parseInt(firstRetryBlockNumber, 16) const txBlockDistance = Number.parseInt(latestBlockNumber, 16) - Number.parseInt(firstRetryBlockNumber, 16)
@ -185,7 +178,8 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
} }
async _checkIfNonceIsTaken (txMeta) { async _checkIfNonceIsTaken (txMeta) {
const completed = this.getCompletedTransactions() const address = txMeta.txParams.from
const completed = this.getCompletedTransactions(address)
const sameNonce = completed.filter((otherMeta) => { const sameNonce = completed.filter((otherMeta) => {
return otherMeta.txParams.nonce === txMeta.txParams.nonce return otherMeta.txParams.nonce === txMeta.txParams.nonce
}) })

@ -4,6 +4,7 @@ const {
BnMultiplyByFraction, BnMultiplyByFraction,
bnToHex, bnToHex,
} = require('./util') } = require('./util')
const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send.
/* /*
tx-utils are utility methods for Transaction manager tx-utils are utility methods for Transaction manager
@ -22,7 +23,11 @@ module.exports = class txProvideUtil {
try { try {
estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit) estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit)
} catch (err) { } catch (err) {
if (err.message.includes('Transaction execution error.')) { const simulationFailed = (
err.message.includes('Transaction execution error.') ||
err.message.includes('gas required exceeds allowance or always failing transaction')
)
if (simulationFailed) {
txMeta.simulationFails = true txMeta.simulationFails = true
return txMeta return txMeta
} }
@ -33,14 +38,30 @@ module.exports = class txProvideUtil {
async estimateTxGas (txMeta, blockGasLimitHex) { async estimateTxGas (txMeta, blockGasLimitHex) {
const txParams = txMeta.txParams const txParams = txMeta.txParams
// check if gasLimit is already specified // check if gasLimit is already specified
txMeta.gasLimitSpecified = Boolean(txParams.gas) txMeta.gasLimitSpecified = Boolean(txParams.gas)
// if not, fallback to block gasLimit
if (!txMeta.gasLimitSpecified) { // if it is, use that value
const blockGasLimitBN = hexToBn(blockGasLimitHex) if (txMeta.gasLimitSpecified) {
const saferGasLimitBN = BnMultiplyByFraction(blockGasLimitBN, 19, 20) return txParams.gas
txParams.gas = bnToHex(saferGasLimitBN)
} }
// if recipient has no code, gas is 21k max:
const recipient = txParams.to
const hasRecipient = Boolean(recipient)
const code = await this.query.getCode(recipient)
if (hasRecipient && (!code || code === '0x')) {
txParams.gas = SIMPLE_GAS_COST
txMeta.simpleSend = true // Prevents buffer addition
return SIMPLE_GAS_COST
}
// if not, fall back to block gasLimit
const blockGasLimitBN = hexToBn(blockGasLimitHex)
const saferGasLimitBN = BnMultiplyByFraction(blockGasLimitBN, 19, 20)
txParams.gas = bnToHex(saferGasLimitBN)
// run tx // run tx
return await this.query.estimateGas(txParams) return await this.query.estimateGas(txParams)
} }
@ -51,7 +72,7 @@ module.exports = class txProvideUtil {
// if gasLimit was specified and doesnt OOG, // if gasLimit was specified and doesnt OOG,
// use original specified amount // use original specified amount
if (txMeta.gasLimitSpecified) { if (txMeta.gasLimitSpecified || txMeta.simpleSend) {
txMeta.estimatedGas = txParams.gas txMeta.estimatedGas = txParams.gas
return return
} }
@ -77,8 +98,26 @@ module.exports = class txProvideUtil {
} }
async validateTxParams (txParams) { async validateTxParams (txParams) {
if (('value' in txParams) && txParams.value.indexOf('-') === 0) { this.validateRecipient(txParams)
throw new Error(`Invalid transaction value of ${txParams.value} not a positive number.`) if ('value' in txParams) {
const value = txParams.value.toString()
if (value.includes('-')) {
throw new Error(`Invalid transaction value of ${txParams.value} not a positive number.`)
}
if (value.includes('.')) {
throw new Error(`Invalid transaction value of ${txParams.value} number must be in wei`)
}
}
}
validateRecipient (txParams) {
if (txParams.to === '0x') {
if (txParams.data) {
delete txParams.to
} else {
throw new Error('Invalid recipient address')
}
} }
return txParams
} }
} }

@ -187,6 +187,10 @@ module.exports = class TransactionStateManger extends EventEmitter {
this._setTxStatus(txId, 'rejected') this._setTxStatus(txId, 'rejected')
} }
// should update the status of the tx to 'unapproved'.
setTxStatusUnapproved (txId) {
this._setTxStatus(txId, 'unapproved')
}
// should update the status of the tx to 'approved'. // should update the status of the tx to 'approved'.
setTxStatusApproved (txId) { setTxStatusApproved (txId) {
this._setTxStatus(txId, 'approved') this._setTxStatus(txId, 'approved')
@ -236,7 +240,7 @@ module.exports = class TransactionStateManger extends EventEmitter {
txMeta.status = status txMeta.status = status
this.emit(`${txMeta.id}:${status}`, txId) this.emit(`${txMeta.id}:${status}`, txId)
this.emit(`tx:status-update`, txId, status) this.emit(`tx:status-update`, txId, status)
if (status === 'submitted' || status === 'rejected') { if (['submitted', 'rejected', 'failed'].includes(status)) {
this.emit(`${txMeta.id}:finished`, txMeta) this.emit(`${txMeta.id}:finished`, txMeta)
} }
this.updateTx(txMeta, `txStateManager: setting status to ${status}`) this.updateTx(txMeta, `txStateManager: setting status to ${status}`)

@ -5,7 +5,6 @@ const Dnode = require('dnode')
const ObservableStore = require('obs-store') const ObservableStore = require('obs-store')
const asStream = require('obs-store/lib/asStream') const asStream = require('obs-store/lib/asStream')
const AccountTracker = require('./lib/account-tracker') const AccountTracker = require('./lib/account-tracker')
const EthQuery = require('eth-query')
const RpcEngine = require('json-rpc-engine') const RpcEngine = require('json-rpc-engine')
const debounce = require('debounce') const debounce = require('debounce')
const createEngineStream = require('json-rpc-middleware-stream/engineStream') const createEngineStream = require('json-rpc-middleware-stream/engineStream')
@ -23,6 +22,7 @@ const ShapeShiftController = require('./controllers/shapeshift')
const AddressBookController = require('./controllers/address-book') const AddressBookController = require('./controllers/address-book')
const InfuraController = require('./controllers/infura') const InfuraController = require('./controllers/infura')
const BlacklistController = require('./controllers/blacklist') const BlacklistController = require('./controllers/blacklist')
const RecentBlocksController = require('./controllers/recent-blocks')
const MessageManager = require('./lib/message-manager') const MessageManager = require('./lib/message-manager')
const PersonalMessageManager = require('./lib/personal-message-manager') const PersonalMessageManager = require('./lib/personal-message-manager')
const TypedMessageManager = require('./lib/typed-message-manager') const TypedMessageManager = require('./lib/typed-message-manager')
@ -34,13 +34,15 @@ const accountImporter = require('./account-import-strategies')
const getBuyEthUrl = require('./lib/buy-eth-url') const getBuyEthUrl = require('./lib/buy-eth-url')
const Mutex = require('await-semaphore').Mutex const Mutex = require('await-semaphore').Mutex
const version = require('../manifest.json').version const version = require('../manifest.json').version
const BN = require('ethereumjs-util').BN
const GWEI_BN = new BN('1000000000')
const percentile = require('percentile')
module.exports = class MetamaskController extends EventEmitter { module.exports = class MetamaskController extends EventEmitter {
constructor (opts) { constructor (opts) {
super() super()
this.sendUpdate = debounce(this.privateSendUpdate.bind(this), 200) this.sendUpdate = debounce(this.privateSendUpdate.bind(this), 200)
this.opts = opts this.opts = opts
@ -91,8 +93,11 @@ module.exports = class MetamaskController extends EventEmitter {
this.provider = this.initializeProvider() this.provider = this.initializeProvider()
this.blockTracker = this.provider._blockTracker this.blockTracker = this.provider._blockTracker
// eth data query tools this.recentBlocksController = new RecentBlocksController({
this.ethQuery = new EthQuery(this.provider) blockTracker: this.blockTracker,
provider: this.provider,
})
// account tracker watches balances, nonces, and any code at their address. // account tracker watches balances, nonces, and any code at their address.
this.accountTracker = new AccountTracker({ this.accountTracker = new AccountTracker({
provider: this.provider, provider: this.provider,
@ -133,7 +138,7 @@ module.exports = class MetamaskController extends EventEmitter {
signTransaction: this.keyringController.signTransaction.bind(this.keyringController), signTransaction: this.keyringController.signTransaction.bind(this.keyringController),
provider: this.provider, provider: this.provider,
blockTracker: this.blockTracker, blockTracker: this.blockTracker,
ethQuery: this.ethQuery, getGasPrice: this.getGasPrice.bind(this),
}) })
this.txController.on('newUnapprovedTx', opts.showUnapprovedTx.bind(opts)) this.txController.on('newUnapprovedTx', opts.showUnapprovedTx.bind(opts))
@ -196,25 +201,30 @@ module.exports = class MetamaskController extends EventEmitter {
this.blacklistController.store.subscribe((state) => { this.blacklistController.store.subscribe((state) => {
this.store.updateState({ BlacklistController: state }) this.store.updateState({ BlacklistController: state })
}) })
this.recentBlocksController.store.subscribe((state) => {
this.store.updateState({ RecentBlocks: state })
})
this.infuraController.store.subscribe((state) => { this.infuraController.store.subscribe((state) => {
this.store.updateState({ InfuraController: state }) this.store.updateState({ InfuraController: state })
}) })
// manual mem state subscriptions // manual mem state subscriptions
this.networkController.store.subscribe(this.sendUpdate.bind(this)) const sendUpdate = this.sendUpdate.bind(this)
this.accountTracker.store.subscribe(this.sendUpdate.bind(this)) this.networkController.store.subscribe(sendUpdate)
this.txController.memStore.subscribe(this.sendUpdate.bind(this)) this.accountTracker.store.subscribe(sendUpdate)
this.balancesController.store.subscribe(this.sendUpdate.bind(this)) this.txController.memStore.subscribe(sendUpdate)
this.messageManager.memStore.subscribe(this.sendUpdate.bind(this)) this.balancesController.store.subscribe(sendUpdate)
this.personalMessageManager.memStore.subscribe(this.sendUpdate.bind(this)) this.messageManager.memStore.subscribe(sendUpdate)
this.typedMessageManager.memStore.subscribe(this.sendUpdate.bind(this)) this.personalMessageManager.memStore.subscribe(sendUpdate)
this.keyringController.memStore.subscribe(this.sendUpdate.bind(this)) this.typedMessageManager.memStore.subscribe(sendUpdate)
this.preferencesController.store.subscribe(this.sendUpdate.bind(this)) this.keyringController.memStore.subscribe(sendUpdate)
this.addressBookController.store.subscribe(this.sendUpdate.bind(this)) this.preferencesController.store.subscribe(sendUpdate)
this.currencyController.store.subscribe(this.sendUpdate.bind(this)) this.recentBlocksController.store.subscribe(sendUpdate)
this.noticeController.memStore.subscribe(this.sendUpdate.bind(this)) this.addressBookController.store.subscribe(sendUpdate)
this.shapeshiftController.store.subscribe(this.sendUpdate.bind(this)) this.currencyController.store.subscribe(sendUpdate)
this.infuraController.store.subscribe(this.sendUpdate.bind(this)) this.noticeController.memStore.subscribe(sendUpdate)
this.shapeshiftController.store.subscribe(sendUpdate)
this.infuraController.store.subscribe(sendUpdate)
} }
// //
@ -298,6 +308,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.currencyController.store.getState(), this.currencyController.store.getState(),
this.noticeController.memStore.getState(), this.noticeController.memStore.getState(),
this.infuraController.store.getState(), this.infuraController.store.getState(),
this.recentBlocksController.store.getState(),
// config manager // config manager
this.configManager.getConfig(), this.configManager.getConfig(),
this.shapeshiftController.store.getState(), this.shapeshiftController.store.getState(),
@ -342,6 +353,7 @@ module.exports = class MetamaskController extends EventEmitter {
submitPassword: nodeify(keyringController.submitPassword, keyringController), submitPassword: nodeify(keyringController.submitPassword, keyringController),
// network management // network management
setNetworkEndpoints: nodeify(networkController.setNetworkEndpoints, networkController),
setProviderType: nodeify(networkController.setProviderType, networkController), setProviderType: nodeify(networkController.setProviderType, networkController),
setCustomRpc: nodeify(this.setCustomRpc, this), setCustomRpc: nodeify(this.setCustomRpc, this),
@ -365,7 +377,9 @@ module.exports = class MetamaskController extends EventEmitter {
// txController // txController
cancelTransaction: nodeify(txController.cancelTransaction, txController), cancelTransaction: nodeify(txController.cancelTransaction, txController),
updateTransaction: nodeify(txController.updateTransaction, txController),
updateAndApproveTransaction: nodeify(txController.updateAndApproveTransaction, txController), updateAndApproveTransaction: nodeify(txController.updateAndApproveTransaction, txController),
retryTransaction: nodeify(this.retryTransaction, this),
// messageManager // messageManager
signMessage: nodeify(this.signMessage, this), signMessage: nodeify(this.signMessage, this),
@ -475,6 +489,33 @@ module.exports = class MetamaskController extends EventEmitter {
this.emit('update', this.getState()) this.emit('update', this.getState())
} }
getGasPrice () {
const { recentBlocksController } = this
const { recentBlocks } = recentBlocksController.store.getState()
// Return 1 gwei if no blocks have been observed:
if (recentBlocks.length === 0) {
return '0x' + GWEI_BN.toString(16)
}
const lowestPrices = recentBlocks.map((block) => {
if (!block.gasPrices || block.gasPrices.length < 1) {
return GWEI_BN
}
return block.gasPrices
.map(hexPrefix => hexPrefix.substr(2))
.map(hex => new BN(hex, 16))
.sort((a, b) => {
return a.gt(b) ? 1 : -1
})[0]
})
.map(number => number.div(GWEI_BN).toNumber())
const percentileNum = percentile(50, lowestPrices)
const percentileNumBn = new BN(percentileNum)
return '0x' + percentileNumBn.mul(GWEI_BN).toString(16)
}
// //
// Vault Management // Vault Management
// //
@ -504,10 +545,15 @@ module.exports = class MetamaskController extends EventEmitter {
async createNewVaultAndRestore (password, seed) { async createNewVaultAndRestore (password, seed) {
const release = await this.createVaultMutex.acquire() const release = await this.createVaultMutex.acquire()
const vault = await this.keyringController.createNewVaultAndRestore(password, seed) try {
this.selectFirstIdentity(vault) const vault = await this.keyringController.createNewVaultAndRestore(password, seed)
release() this.selectFirstIdentity(vault)
return vault release()
return vault
} catch (err) {
release()
throw err
}
} }
selectFirstIdentity (vault) { selectFirstIdentity (vault) {
@ -576,6 +622,14 @@ module.exports = class MetamaskController extends EventEmitter {
// //
// Identity Management // Identity Management
// //
//
async retryTransaction (txId, cb) {
await this.txController.retryTransaction(txId)
const state = await this.getState()
return state
}
newUnsignedMessage (msgParams, cb) { newUnsignedMessage (msgParams, cb) {
const msgId = this.messageManager.addUnapprovedMessage(msgParams) const msgId = this.messageManager.addUnapprovedMessage(msgParams)

@ -77,7 +77,7 @@ module.exports = class NoticeController extends EventEmitter {
return uniqBy(oldNotices.concat(newNotices), 'id') return uniqBy(oldNotices.concat(newNotices), 'id')
} }
_filterNotices(notices) { _filterNotices (notices) {
return notices.filter((newNotice) => { return notices.filter((newNotice) => {
if ('version' in newNotice) { if ('version' in newNotice) {
const satisfied = semver.satisfies(this.version, newNotice.version) const satisfied = semver.satisfies(this.version, newNotice.version)

@ -26,8 +26,17 @@ const container = document.getElementById('app-content')
startPopup({ container, connectionStream }, (err, store) => { startPopup({ container, connectionStream }, (err, store) => {
if (err) return displayCriticalError(err) if (err) return displayCriticalError(err)
let betaUIState = store.getState().metamask.featureFlags.betaUI // Code commented out until we begin auto adding users to NewUI
let css = betaUIState ? NewMetaMaskUiCss() : OldMetaMaskUiCss() // const { isMascara, identities = {}, featureFlags = {} } = store.getState().metamask
// const firstTime = Object.keys(identities).length === 0
const { isMascara, featureFlags = {} } = store.getState().metamask
let betaUIState = featureFlags.betaUI
// Code commented out until we begin auto adding users to NewUI
// const useBetaCss = isMascara || firstTime || betaUIState
const useBetaCss = isMascara || betaUIState
let css = useBetaCss ? NewMetaMaskUiCss() : OldMetaMaskUiCss()
let deleteInjectedCss = injectCss(css) let deleteInjectedCss = injectCss(css)
let newBetaUIState let newBetaUIState

@ -8,6 +8,7 @@
"frequentRpcList": [], "frequentRpcList": [],
"unapprovedTxs": {}, "unapprovedTxs": {},
"currentCurrency": "USD", "currentCurrency": "USD",
"featureFlags": {"betaUI": true},
"conversionRate": 12.7527416, "conversionRate": 12.7527416,
"conversionDate": 1487624341, "conversionDate": 1487624341,
"noActiveNotices": false, "noActiveNotices": false,

@ -0,0 +1,739 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"isMascara": false,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": {
"address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"name": "Account 1"
}
},
"unapprovedTxs": {},
"noActiveNotices": true,
"frequentRpcList": [
"http://192.168.1.34:7545/"
],
"addressBook": [],
"tokenExchangeRates": {},
"coinOptions": {},
"provider": {
"type": "mainnet",
"rpcTarget": "https://mainnet.infura.io/metamask"
},
"network": "1",
"accounts": {
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": {
"code": "0x",
"balance": "0x1b3f641ed0c2f62",
"nonce": "0x35",
"address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
}
},
"currentBlockGasLimit": "0x66df83",
"selectedAddressTxList": [
{
"id": 3516145537630216,
"time": 1512615655535,
"status": "submitted",
"metamaskNetworkId": "1",
"txParams": {
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"value": "0x16345785d8a0000",
"gasPrice": "0xc1b710800",
"gas": "0x7b0c",
"nonce": "0x35",
"chainId": "0x1"
},
"gasPriceSpecified": false,
"gasLimitSpecified": false,
"estimatedGas": "5208",
"history": [
{
"id": 3516145537630216,
"time": 1512615655535,
"status": "unapproved",
"metamaskNetworkId": "1",
"txParams": {
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"value": "0x16345785d8a0000",
"gasPrice": "0xe6f7cec00",
"gas": "0x7b0c"
},
"gasPriceSpecified": false,
"gasLimitSpecified": false,
"estimatedGas": "5208"
},
[
{
"op": "replace",
"path": "/txParams/gasPrice",
"value": "0xc1b710800",
"note": "confTx: user approved transaction"
}
],
[
{
"op": "replace",
"path": "/status",
"value": "approved",
"note": "txStateManager: setting status to approved"
}
],
[
{
"op": "add",
"path": "/txParams/nonce",
"value": "0x35",
"note": "transactions#approveTransaction"
},
{
"op": "add",
"path": "/nonceDetails",
"value": {
"params": {
"highestLocalNonce": 53,
"highestSuggested": 53,
"nextNetworkNonce": 53
},
"local": {
"name": "local",
"nonce": 53,
"details": {
"startPoint": 53,
"highest": 53
}
},
"network": {
"name": "network",
"nonce": 53,
"details": {
"baseCount": 53
}
}
}
}
],
[
{
"op": "add",
"path": "/txParams/chainId",
"value": "0x1",
"note": "txStateManager: setting status to signed"
},
{
"op": "replace",
"path": "/status",
"value": "signed"
}
],
[
{
"op": "add",
"path": "/rawTx",
"value": "0xf86c35850c1b710800827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a0f5142ba79a13ca7ec65548953017edafb217803244bbf9821d9ad077d89921e9a03afcb614169c90be9905d5b469d06984825c76675d3a535937cdb8f2ad1c0a95",
"note": "transactions#publishTransaction"
}
],
[
{
"op": "add",
"path": "/hash",
"value": "0x7ce19c0d128ca11293b44a4e6d3cc9063665c00ea8c8eb400f548e132c147353",
"note": "transactions#setTxHash"
}
],
[
{
"op": "replace",
"path": "/status",
"value": "submitted",
"note": "txStateManager: setting status to submitted"
}
],
[
{
"op": "add",
"path": "/firstRetryBlockNumber",
"value": "0x478ab3",
"note": "transactions/pending-tx-tracker#event: tx:block-update"
}
]
],
"nonceDetails": {
"params": {
"highestLocalNonce": 53,
"highestSuggested": 53,
"nextNetworkNonce": 53
},
"local": {
"name": "local",
"nonce": 53,
"details": {
"startPoint": 53,
"highest": 53
}
},
"network": {
"name": "network",
"nonce": 53,
"details": {
"baseCount": 53
}
}
},
"rawTx": "0xf86c35850c1b710800827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a0f5142ba79a13ca7ec65548953017edafb217803244bbf9821d9ad077d89921e9a03afcb614169c90be9905d5b469d06984825c76675d3a535937cdb8f2ad1c0a95",
"hash": "0x7ce19c0d128ca11293b44a4e6d3cc9063665c00ea8c8eb400f548e132c147353",
"firstRetryBlockNumber": "0x478ab3"
},
{
"id": 3516145537630211,
"time": 1512613432658,
"status": "confirmed",
"metamaskNetworkId": "1",
"txParams": {
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"value": "0x16345785d8a0000",
"gasPrice": "0xba43b7400",
"gas": "0x7b0c",
"nonce": "0x34",
"chainId": "0x1"
},
"gasPriceSpecified": false,
"gasLimitSpecified": false,
"estimatedGas": "5208",
"history": [
{
"id": 3516145537630211,
"time": 1512613432658,
"status": "unapproved",
"metamaskNetworkId": "1",
"txParams": {
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"value": "0x16345785d8a0000",
"gasPrice": "0xdf8475800",
"gas": "0x7b0c"
},
"gasPriceSpecified": false,
"gasLimitSpecified": false,
"estimatedGas": "5208"
},
[
{
"op": "replace",
"path": "/txParams/gasPrice",
"value": "0xba43b7400",
"note": "confTx: user approved transaction"
}
],
[
{
"op": "replace",
"path": "/status",
"value": "approved",
"note": "txStateManager: setting status to approved"
}
],
[
{
"op": "add",
"path": "/txParams/nonce",
"value": "0x34",
"note": "transactions#approveTransaction"
},
{
"op": "add",
"path": "/nonceDetails",
"value": {
"params": {
"highestLocalNonce": 52,
"highestSuggested": 52,
"nextNetworkNonce": 52
},
"local": {
"name": "local",
"nonce": 52,
"details": {
"startPoint": 52,
"highest": 52
}
},
"network": {
"name": "network",
"nonce": 52,
"details": {
"baseCount": 52
}
}
}
}
],
[
{
"op": "add",
"path": "/txParams/chainId",
"value": "0x1",
"note": "txStateManager: setting status to signed"
},
{
"op": "replace",
"path": "/status",
"value": "signed"
}
],
[
{
"op": "add",
"path": "/rawTx",
"value": "0xf86c34850ba43b7400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a073a4afdb8e8ad32b0cf9039af56c66baffd60d30e75cee5c1b783208824eafb8a0021ca6c1714a2c71281333ab77f776d3514348ab77967280fca8a5b4be44285e",
"note": "transactions#publishTransaction"
}
],
[
{
"op": "add",
"path": "/hash",
"value": "0x5c98409883fdfd3cd24058a83b91470da6c40ffae41a40eb90d7dee0b837d26d",
"note": "transactions#setTxHash"
}
],
[
{
"op": "replace",
"path": "/status",
"value": "submitted",
"note": "txStateManager: setting status to submitted"
}
],
[
{
"op": "add",
"path": "/firstRetryBlockNumber",
"value": "0x478a2c",
"note": "transactions/pending-tx-tracker#event: tx:block-update"
}
],
[
{
"op": "replace",
"path": "/status",
"value": "confirmed",
"note": "txStateManager: setting status to confirmed"
}
]
],
"nonceDetails": {
"params": {
"highestLocalNonce": 52,
"highestSuggested": 52,
"nextNetworkNonce": 52
},
"local": {
"name": "local",
"nonce": 52,
"details": {
"startPoint": 52,
"highest": 52
}
},
"network": {
"name": "network",
"nonce": 52,
"details": {
"baseCount": 52
}
}
},
"rawTx": "0xf86c34850ba43b7400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a073a4afdb8e8ad32b0cf9039af56c66baffd60d30e75cee5c1b783208824eafb8a0021ca6c1714a2c71281333ab77f776d3514348ab77967280fca8a5b4be44285e",
"hash": "0x5c98409883fdfd3cd24058a83b91470da6c40ffae41a40eb90d7dee0b837d26d",
"firstRetryBlockNumber": "0x478a2c"
},
{
"id": 3516145537630210,
"time": 1512612826136,
"status": "confirmed",
"metamaskNetworkId": "1",
"txParams": {
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"value": "0x16345785d8a0000",
"gasPrice": "0xa7a358200",
"gas": "0x7b0c",
"nonce": "0x33",
"chainId": "0x1"
},
"gasPriceSpecified": false,
"gasLimitSpecified": false,
"estimatedGas": "5208",
"history": [
{
"id": 3516145537630210,
"time": 1512612826136,
"status": "unapproved",
"metamaskNetworkId": "1",
"txParams": {
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"value": "0x16345785d8a0000",
"gasPrice": "0xba43b7400",
"gas": "0x7b0c"
},
"gasPriceSpecified": false,
"gasLimitSpecified": false,
"estimatedGas": "5208"
},
[
{
"op": "replace",
"path": "/txParams/gasPrice",
"value": "0xa7a358200",
"note": "confTx: user approved transaction"
}
],
[
{
"op": "replace",
"path": "/status",
"value": "approved",
"note": "txStateManager: setting status to approved"
}
],
[
{
"op": "add",
"path": "/txParams/nonce",
"value": "0x33",
"note": "transactions#approveTransaction"
},
{
"op": "add",
"path": "/nonceDetails",
"value": {
"params": {
"highestLocalNonce": 0,
"highestSuggested": 51,
"nextNetworkNonce": 51
},
"local": {
"name": "local",
"nonce": 51,
"details": {
"startPoint": 51,
"highest": 51
}
},
"network": {
"name": "network",
"nonce": 51,
"details": {
"baseCount": 51
}
}
}
}
],
[
{
"op": "add",
"path": "/txParams/chainId",
"value": "0x1",
"note": "txStateManager: setting status to signed"
},
{
"op": "replace",
"path": "/status",
"value": "signed"
}
],
[
{
"op": "add",
"path": "/rawTx",
"value": "0xf86c33850a7a358200827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a0021a8cd6c10208cc593e22af53637e5d127cee5cc6f9443a3e758a02afff1d7ca025f7420e974d3f2c668c165040987c72543a8e709bfea3528a62836a6ced9ce8",
"note": "transactions#publishTransaction"
}
],
[
{
"op": "add",
"path": "/hash",
"value": "0x289772800898bc9cd414530d8581c0da257a9055e4aaaa6d10d92d700bfbd044",
"note": "transactions#setTxHash"
}
],
[
{
"op": "replace",
"path": "/status",
"value": "submitted",
"note": "txStateManager: setting status to submitted"
}
],
[
{
"op": "add",
"path": "/firstRetryBlockNumber",
"value": "0x478a04",
"note": "transactions/pending-tx-tracker#event: tx:block-update"
}
],
[
{
"op": "replace",
"path": "/status",
"value": "confirmed",
"note": "txStateManager: setting status to confirmed"
}
]
],
"nonceDetails": {
"params": {
"highestLocalNonce": 0,
"highestSuggested": 51,
"nextNetworkNonce": 51
},
"local": {
"name": "local",
"nonce": 51,
"details": {
"startPoint": 51,
"highest": 51
}
},
"network": {
"name": "network",
"nonce": 51,
"details": {
"baseCount": 51
}
}
},
"rawTx": "0xf86c33850a7a358200827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a0021a8cd6c10208cc593e22af53637e5d127cee5cc6f9443a3e758a02afff1d7ca025f7420e974d3f2c668c165040987c72543a8e709bfea3528a62836a6ced9ce8",
"hash": "0x289772800898bc9cd414530d8581c0da257a9055e4aaaa6d10d92d700bfbd044",
"firstRetryBlockNumber": "0x478a04"
},
{
"id": 3516145537630209,
"time": 1512612809252,
"status": "failed",
"metamaskNetworkId": "1",
"txParams": {
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"value": "0x16345785d8a0000",
"gasPrice": "0x77359400",
"gas": "0x7b0c",
"nonce": "0x33",
"chainId": "0x1"
},
"gasPriceSpecified": false,
"gasLimitSpecified": false,
"estimatedGas": "5208",
"history": [
{
"id": 3516145537630209,
"time": 1512612809252,
"status": "unapproved",
"metamaskNetworkId": "1",
"txParams": {
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"value": "0x16345785d8a0000",
"gasPrice": "0xba43b7400",
"gas": "0x7b0c"
},
"gasPriceSpecified": false,
"gasLimitSpecified": false,
"estimatedGas": "5208"
},
[
{
"op": "replace",
"path": "/txParams/gasPrice",
"value": "0x77359400",
"note": "confTx: user approved transaction"
}
],
[
{
"op": "replace",
"path": "/status",
"value": "approved",
"note": "txStateManager: setting status to approved"
}
],
[
{
"op": "add",
"path": "/txParams/nonce",
"value": "0x33",
"note": "transactions#approveTransaction"
},
{
"op": "add",
"path": "/nonceDetails",
"value": {
"params": {
"highestLocalNonce": 0,
"highestSuggested": 51,
"nextNetworkNonce": 51
},
"local": {
"name": "local",
"nonce": 51,
"details": {
"startPoint": 51,
"highest": 51
}
},
"network": {
"name": "network",
"nonce": 51,
"details": {
"baseCount": 51
}
}
}
}
],
[
{
"op": "add",
"path": "/txParams/chainId",
"value": "0x1",
"note": "txStateManager: setting status to signed"
},
{
"op": "replace",
"path": "/status",
"value": "signed"
}
],
[
{
"op": "add",
"path": "/rawTx",
"value": "0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7",
"note": "transactions#publishTransaction"
}
],
[
{
"op": "add",
"path": "/err",
"value": {
"message": "Error: [ethjs-rpc] rpc error with payload {\"id\":7801900228852,\"jsonrpc\":\"2.0\",\"params\":[\"0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7\"],\"method\":\"eth_sendRawTransaction\"} Error: transaction underpriced",
"stack": "Error: [ethjs-rpc] rpc error with payload {\"id\":7801900228852,\"jsonrpc\":\"2.0\",\"params\":[\"0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7\"],\"method\":\"eth_sendRawTransaction\"} Error: transaction underpriced\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:60327:26\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:88030:9\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16678:16\n at replenish (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16522:25)\n at iterateeCallback (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16512:17)\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16694:16\n at resultObj.id (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:88012:9)\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16813:16\n at replenish (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16527:17)\n at iterateeCallback (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16512:17)"
}
}
],
[
{
"op": "replace",
"path": "/status",
"value": "failed",
"note": "txStateManager: setting status to failed"
}
]
],
"nonceDetails": {
"params": {
"highestLocalNonce": 0,
"highestSuggested": 51,
"nextNetworkNonce": 51
},
"local": {
"name": "local",
"nonce": 51,
"details": {
"startPoint": 51,
"highest": 51
}
},
"network": {
"name": "network",
"nonce": 51,
"details": {
"baseCount": 51
}
}
},
"rawTx": "0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7",
"err": {
"message": "Error: [ethjs-rpc] rpc error with payload {\"id\":7801900228852,\"jsonrpc\":\"2.0\",\"params\":[\"0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7\"],\"method\":\"eth_sendRawTransaction\"} Error: transaction underpriced",
"stack": "Error: [ethjs-rpc] rpc error with payload {\"id\":7801900228852,\"jsonrpc\":\"2.0\",\"params\":[\"0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7\"],\"method\":\"eth_sendRawTransaction\"} Error: transaction underpriced\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:60327:26\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:88030:9\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16678:16\n at replenish (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16522:25)\n at iterateeCallback (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16512:17)\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16694:16\n at resultObj.id (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:88012:9)\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16813:16\n at replenish (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16527:17)\n at iterateeCallback (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16512:17)"
}
}
],
"unapprovedMsgs": {},
"unapprovedMsgCount": 0,
"unapprovedPersonalMsgs": {},
"unapprovedPersonalMsgCount": 0,
"unapprovedTypedMessages": {},
"unapprovedTypedMessagesCount": 0,
"keyringTypes": [
"Simple Key Pair",
"HD Key Tree"
],
"keyrings": [
{
"type": "HD Key Tree",
"accounts": [
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
]
}
],
"computedBalances": {},
"currentAccountTab": "history",
"tokens": [
{
"address": "0x0d8775f648430679a709e98d2b0cb6250d2887ef",
"symbol": "BAT",
"decimals": "18"
}
],
"selectedAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"currentCurrency": "usd",
"conversionRate": 418.62,
"conversionDate": 1512615622,
"infuraNetworkStatus": {
"mainnet": "ok",
"ropsten": "ok",
"kovan": "ok",
"rinkeby": "ok"
},
"shapeShiftTxList": [],
"lostAccounts": []
},
"appState": {
"shouldClose": true,
"menuOpen": false,
"currentView": {
"name": "accountDetail",
"context": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
},
"accountDetail": {
"subview": "transactions",
"accountExport": "none",
"privateKey": ""
},
"transForward": false,
"isLoading": false,
"warning": null,
"forgottenPassword": false,
"scrollToBottom": false
},
"identities": {},
"version": "3.12.1",
"platform": {
"arch": "x86-64",
"nacl_arch": "x86-64",
"os": "mac"
},
"browser": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36"
}

@ -4,8 +4,8 @@ metamascara:
ports: ports:
- "9001" - "9001"
environment: environment:
MASCARA_ORIGIN: "https://zero.metamask.io" MASCARA_ORIGIN: "https://wallet.metamask.io"
VIRTUAL_PORT: "9001" VIRTUAL_PORT: "9001"
VIRTUAL_HOST: "zero.metamask.io" VIRTUAL_HOST: "wallet.metamask.io"
LETSENCRYPT_HOST: "zero.metamask.io" LETSENCRYPT_HOST: "wallet.metamask.io"
LETSENCRYPT_EMAIL: "admin@metamask.io" LETSENCRYPT_EMAIL: "admin@metamask.io"

@ -19,10 +19,14 @@ var manifest = require('./app/manifest.json')
var gulpif = require('gulp-if') var gulpif = require('gulp-if')
var replace = require('gulp-replace') var replace = require('gulp-replace')
var mkdirp = require('mkdirp') var mkdirp = require('mkdirp')
var asyncEach = require('async/each')
var exec = require('child_process').exec
var sass = require('gulp-sass') var sass = require('gulp-sass')
var autoprefixer = require('gulp-autoprefixer') var autoprefixer = require('gulp-autoprefixer')
var gulpStylelint = require('gulp-stylelint') var gulpStylelint = require('gulp-stylelint')
var stylefmt = require('gulp-stylefmt') var stylefmt = require('gulp-stylefmt')
var uglify = require('gulp-uglify-es').default
var babel = require('gulp-babel')
var disableDebugTools = gutil.env.disableDebugTools var disableDebugTools = gutil.env.disableDebugTools
@ -159,6 +163,18 @@ gulp.task('copy:watch', function(){
gulp.watch(['./app/{_locales,images}/*', './app/scripts/chromereload.js', './app/*.{html,json}'], gulp.series('copy')) gulp.watch(['./app/{_locales,images}/*', './app/scripts/chromereload.js', './app/*.{html,json}'], gulp.series('copy'))
}) })
// record deps
gulp.task('deps', function (cb) {
exec('npm ls', (err, stdoutOutput, stderrOutput) => {
if (err) return cb(err)
const browsers = ['firefox','chrome','edge','opera']
asyncEach(browsers, (target, done) => {
fs.writeFile(`./dist/${target}/deps.txt`, stdoutOutput, done)
}, cb)
})
})
// lint js // lint js
gulp.task('lint', function () { gulp.task('lint', function () {
@ -232,8 +248,18 @@ var jsDevStrings = jsFiles.map(jsFile => `dev:js:${jsFile}`)
var jsBuildStrings = jsFiles.map(jsFile => `build:js:${jsFile}`) var jsBuildStrings = jsFiles.map(jsFile => `build:js:${jsFile}`)
jsFiles.forEach((jsFile) => { jsFiles.forEach((jsFile) => {
gulp.task(`dev:js:${jsFile}`, bundleTask({ watch: true, label: jsFile, filename: `${jsFile}.js` })) gulp.task(`dev:js:${jsFile}`, bundleTask({
gulp.task(`build:js:${jsFile}`, bundleTask({ watch: false, label: jsFile, filename: `${jsFile}.js` })) watch: true,
label: jsFile,
filename: `${jsFile}.js`,
isBuild: false
}))
gulp.task(`build:js:${jsFile}`, bundleTask({
watch: false,
label: jsFile,
filename: `${jsFile}.js`,
isBuild: true
}))
}) })
// inpage must be built before all other scripts: // inpage must be built before all other scripts:
@ -267,12 +293,18 @@ gulp.task('zip:edge', zipTask('edge'))
gulp.task('zip:opera', zipTask('opera')) gulp.task('zip:opera', zipTask('opera'))
gulp.task('zip', gulp.parallel('zip:chrome', 'zip:firefox', 'zip:edge', 'zip:opera')) gulp.task('zip', gulp.parallel('zip:chrome', 'zip:firefox', 'zip:edge', 'zip:opera'))
// set env var for production
gulp.task('apply-prod-environment', function(done) {
process.env.NODE_ENV = 'production'
done()
});
// high level tasks // high level tasks
gulp.task('dev', gulp.series('build:scss', 'dev:js', 'copy', gulp.parallel('watch:scss', 'copy:watch', 'dev:reload'))) gulp.task('dev', gulp.series('build:scss', 'dev:js', 'copy', gulp.parallel('watch:scss', 'copy:watch', 'dev:reload')))
gulp.task('build', gulp.series('clean', 'build:scss', gulp.parallel('build:js', 'copy'))) gulp.task('build', gulp.series('clean', 'build:scss', gulp.parallel('build:js', 'copy', 'deps')))
gulp.task('dist', gulp.series('build', 'zip')) gulp.task('dist', gulp.series('apply-prod-environment', 'build', 'zip'))
// task generators // task generators
@ -365,7 +397,6 @@ function bundleTask(opts) {
throw err throw err
} }
}) })
// convert bundle stream to gulp vinyl stream // convert bundle stream to gulp vinyl stream
.pipe(source(opts.filename)) .pipe(source(opts.filename))
// inject variables into bundle // inject variables into bundle
@ -375,6 +406,8 @@ function bundleTask(opts) {
// sourcemaps // sourcemaps
// loads map from browserify file // loads map from browserify file
.pipe(gulpif(debug, sourcemaps.init({ loadMaps: true }))) .pipe(gulpif(debug, sourcemaps.init({ loadMaps: true })))
// Minification
.pipe(gulpif(opts.isBuild, uglify()))
// writes .map file // writes .map file
.pipe(gulpif(debug, sourcemaps.write('./'))) .pipe(gulpif(debug, sourcemaps.write('./')))
// write completed bundles // write completed bundles

@ -2,6 +2,7 @@ const path = require('path')
const express = require('express') const express = require('express')
const createBundle = require('./util').createBundle const createBundle = require('./util').createBundle
const serveBundle = require('./util').serveBundle const serveBundle = require('./util').serveBundle
const compression = require('compression')
module.exports = createMetamascaraServer module.exports = createMetamascaraServer
@ -16,6 +17,8 @@ function createMetamascaraServer () {
// serve bundles // serve bundles
const server = express() const server = express()
server.use(compression())
// ui window // ui window
serveBundle(server, '/ui.js', uiBundle) serveBundle(server, '/ui.js', uiBundle)
server.use(express.static(path.join(__dirname, '/../ui/'), { setHeaders: (res) => res.set('X-Frame-Options', 'DENY') })) server.use(express.static(path.join(__dirname, '/../ui/'), { setHeaders: (res) => res.set('X-Frame-Options', 'DENY') }))

@ -23,7 +23,9 @@ function createBundle (entryPoint) {
cache: {}, cache: {},
packageCache: {}, packageCache: {},
plugin: [watchify], plugin: [watchify],
}).transform('babelify') })
.transform('babelify')
.transform('uglifyify', { global: true })
bundler.on('update', bundle) bundler.on('update', bundle)
bundle() bundle()

@ -5,6 +5,8 @@ import { createNewVaultAndKeychain } from '../../../../ui/app/actions'
import LoadingScreen from './loading-screen' import LoadingScreen from './loading-screen'
import Breadcrumbs from './breadcrumbs' import Breadcrumbs from './breadcrumbs'
import { DEFAULT_ROUTE, IMPORT_ACCOUNT_ROUTE } from '../../../../ui/app/routes' import { DEFAULT_ROUTE, IMPORT_ACCOUNT_ROUTE } from '../../../../ui/app/routes'
import EventEmitter from 'events'
import Mascot from '../../../../ui/app/components/mascot'
class CreatePasswordScreen extends Component { class CreatePasswordScreen extends Component {
static propTypes = { static propTypes = {
@ -20,6 +22,11 @@ class CreatePasswordScreen extends Component {
confirmPassword: '', confirmPassword: '',
} }
constructor () {
super()
this.animationEventEmitter = new EventEmitter()
}
componentWillMount () { componentWillMount () {
const { isInitialized, isUnlocked, history } = this.props const { isInitialized, isUnlocked, history } = this.props
if (isInitialized || isUnlocked) { if (isInitialized || isUnlocked) {
@ -56,12 +63,25 @@ class CreatePasswordScreen extends Component {
render () { render () {
const { isLoading } = this.props const { isLoading } = this.props
return ( return isLoading
<div className="first-time-flow"> ? <LoadingScreen loadingMessage="Creating your new account" />
{ : (
isLoading <div>
? <LoadingScreen loadingMessage="Creating your new account" /> <h2 className="alpha-warning">Warning This is Experimental software and is a Developer BETA </h2>
: ( <div className="first-view-main">
<div className="mascara-info">
<Mascot
animationEventEmitter={this.animationEventEmitter}
width="225"
height="225"
/>
<div className="info">
MetaMask is a secure identity vault for Ethereum.
</div>
<div className="info">
It allows you to hold ether & tokens, and interact with decentralized applications.
</div>
</div>
<div className="create-password"> <div className="create-password">
<div className="create-password__title"> <div className="create-password__title">
Create Password Create Password
@ -109,10 +129,9 @@ class CreatePasswordScreen extends Component {
{ */ } { */ }
<Breadcrumbs total={3} currentIndex={0} /> <Breadcrumbs total={3} currentIndex={0} />
</div> </div>
) </div>
} </div>
</div> )
)
} }
} }

@ -1,3 +1,4 @@
.first-time-flow { .first-time-flow {
height: 100vh; height: 100vh;
width: 100vw; width: 100vw;
@ -5,6 +6,36 @@
overflow: auto; overflow: auto;
} }
.alpha-warning {
background: #f7861c;
color: #fff;
line-height: 2em;
padding-left: 2em;
}
.first-view-main {
display: flex;
flex-direction: row-reverse;
justify-content: space-between;
}
.mascara-info {
display: flex;
flex-flow: column;
margin-top: 70px;
margin-right: 10vw;
width: 35vw;
max-width: 550px;
}
.mascara-info :first-child {
align-self: flex-end;
}
.info {
font-size: 19px;
}
.create-password, .create-password,
.unique-image, .unique-image,
.tou, .tou,
@ -540,11 +571,10 @@ button.backup-phrase__confirm-seed-option:hover {
text-transform: uppercase; text-transform: uppercase;
margin: 35px 0 14px; margin: 35px 0 14px;
transition: 200ms ease-in-out; transition: 200ms ease-in-out;
background: #f7861c; background-color: rgba(247, 134, 28, 0.9);
} }
button.first-time-flow__button[disabled] { button.first-time-flow__button[disabled] {
background-color: rgba(247, 134, 28, 0.9);
opacity: .6; opacity: .6;
} }

@ -4,5 +4,3 @@ When you log in to MetaMask, your current account is visible to every new site y
For your privacy, for now, please sign out of MetaMask when you're done using a site. For your privacy, for now, please sign out of MetaMask when you're done using a site.
Also, by default, you will be signed in to a test network. To use real Ether, you must connect to the main network manually in the top left network menu.

File diff suppressed because one or more lines are too long

@ -78,9 +78,10 @@ AccountDetailScreen.prototype.render = function () {
address: selected, address: selected,
}), }),
]), ]),
h('div.flex-column', { h('flex-column', {
style: { style: {
lineHeight: '10px', lineHeight: '10px',
marginLeft: '15px',
width: '100%', width: '100%',
}, },
}, [ }, [
@ -101,7 +102,7 @@ AccountDetailScreen.prototype.render = function () {
{ {
style: { style: {
display: 'flex', display: 'flex',
justifyContent: 'space-between', justifyContent: 'flex-start',
alignItems: 'center', alignItems: 'center',
}, },
}, },
@ -131,6 +132,8 @@ AccountDetailScreen.prototype.render = function () {
AccountDropdowns, AccountDropdowns,
{ {
style: { style: {
marginRight: '8px',
marginLeft: 'auto',
cursor: 'pointer', cursor: 'pointer',
}, },
selected, selected,
@ -144,6 +147,7 @@ AccountDetailScreen.prototype.render = function () {
]), ]),
h('.flex-row', { h('.flex-row', {
style: { style: {
width: '15em',
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'baseline', alignItems: 'baseline',
}, },
@ -157,10 +161,10 @@ AccountDetailScreen.prototype.render = function () {
textOverflow: 'ellipsis', textOverflow: 'ellipsis',
paddingTop: '3px', paddingTop: '3px',
width: '5em', width: '5em',
height: '15px',
fontSize: '13px', fontSize: '13px',
fontFamily: 'Montserrat Light', fontFamily: 'Montserrat Light',
textRendering: 'geometricPrecision', textRendering: 'geometricPrecision',
marginTop: '15px',
marginBottom: '15px', marginBottom: '15px',
color: '#AEAEAE', color: '#AEAEAE',
}, },
@ -188,21 +192,20 @@ AccountDetailScreen.prototype.render = function () {
}, },
}), }),
h('div', {}, [ h('.flex-grow'),
h('button', { h('button', {
onClick: () => props.dispatch(actions.buyEthView(selected)), onClick: () => props.dispatch(actions.buyEthView(selected)),
style: { marginRight: '10px' }, style: { marginRight: '10px' },
}, 'BUY'), }, 'BUY'),
h('button', { h('button', {
onClick: () => props.dispatch(actions.showSendPage()), onClick: () => props.dispatch(actions.showSendPage()),
style: { style: {
marginBottom: '20px', marginBottom: '20px',
}, marginRight: '8px',
}, 'SEND'), },
}, 'SEND'),
]),
]), ]),
]), ]),

@ -34,6 +34,7 @@ const HDCreateVaultComplete = require('./keychains/hd/create-vault-complete')
const HDRestoreVaultScreen = require('./keychains/hd/restore-vault') const HDRestoreVaultScreen = require('./keychains/hd/restore-vault')
const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation') const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation')
const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns
const { BETA_UI_NETWORK_TYPE } = require('../../app/scripts/config').enums
module.exports = connect(mapStateToProps)(App) module.exports = connect(mapStateToProps)(App)
@ -396,7 +397,7 @@ App.prototype.renderDropdown = function () {
h(DropdownMenuItem, { h(DropdownMenuItem, {
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
onClick: () => { this.props.dispatch(actions.lockMetamask()) }, onClick: () => { this.props.dispatch(actions.lockMetamask()) },
}, 'Lock'), }, 'Log Out'),
h(DropdownMenuItem, { h(DropdownMenuItem, {
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
@ -405,7 +406,10 @@ App.prototype.renderDropdown = function () {
h(DropdownMenuItem, { h(DropdownMenuItem, {
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
onClick: () => { this.props.dispatch(actions.setFeatureFlag('betaUI', true)) }, onClick: () => {
this.props.dispatch(actions.setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL'))
.then(() => this.props.dispatch(actions.setNetworkEndpoints(BETA_UI_NETWORK_TYPE)))
},
}, 'Try Beta!'), }, 'Try Beta!'),
]) ])
} }
@ -466,11 +470,6 @@ App.prototype.renderPrimary = function () {
}) })
} }
if (props.seedWords) {
log.debug('rendering seed words')
return h(HDCreateVaultComplete, {key: 'HDCreateVaultComplete'})
}
// show initialize screen // show initialize screen
if (!props.isInitialized || props.forgottenPassword) { if (!props.isInitialized || props.forgottenPassword) {
// show current view // show current view
@ -505,6 +504,12 @@ App.prototype.renderPrimary = function () {
} }
} }
// show seed words screen
if (props.seedWords) {
log.debug('rendering seed words')
return h(HDCreateVaultComplete, {key: 'HDCreateVaultComplete'})
}
// show current view // show current view
switch (props.currentView.name) { switch (props.currentView.name) {

@ -38,6 +38,16 @@ PendingTx.prototype.render = function () {
const txMeta = this.gatherTxMeta() const txMeta = this.gatherTxMeta()
const txParams = txMeta.txParams || {} const txParams = txMeta.txParams || {}
// Allow retry txs
const { lastGasPrice } = txMeta
let forceGasMin
if (lastGasPrice) {
const stripped = ethUtil.stripHexPrefix(lastGasPrice)
const lastGas = new BN(stripped, 16)
const priceBump = lastGas.divn('10')
forceGasMin = lastGas.add(priceBump)
}
// Account Details // Account Details
const address = txParams.from || props.selectedAddress const address = txParams.from || props.selectedAddress
const identity = props.identities[address] || { address: address } const identity = props.identities[address] || { address: address }
@ -199,7 +209,7 @@ PendingTx.prototype.render = function () {
precision: 9, precision: 9,
scale: 9, scale: 9,
suffix: 'GWEI', suffix: 'GWEI',
min: MIN_GAS_PRICE_BN, min: forceGasMin || MIN_GAS_PRICE_BN,
style: { style: {
position: 'relative', position: 'relative',
top: '5px', top: '5px',

@ -4,6 +4,7 @@ const h = require('react-hyperscript')
const connect = require('react-redux').connect const connect = require('react-redux').connect
const actions = require('../../ui/app/actions') const actions = require('../../ui/app/actions')
const NetworkIndicator = require('./components/network') const NetworkIndicator = require('./components/network')
const LoadingIndicator = require('./components/loading')
const txHelper = require('../lib/tx-helper') const txHelper = require('../lib/tx-helper')
const isPopupOrNotification = require('../../app/scripts/lib/is-popup-or-notification') const isPopupOrNotification = require('../../app/scripts/lib/is-popup-or-notification')
@ -60,6 +61,11 @@ ConfirmTxScreen.prototype.render = function () {
h('.flex-column.flex-grow', [ h('.flex-column.flex-grow', [
h(LoadingIndicator, {
isLoading: txData.loadingDefaults,
loadingMessage: 'Estimating transaction cost…',
}),
// subtitle and nav // subtitle and nav
h('.section-title.flex-row.flex-center', [ h('.section-title.flex-row.flex-center', [
!isNotification ? h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', { !isNotification ? h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {

@ -119,7 +119,7 @@ ConfigScreen.prototype.render = function () {
if (err) { if (err) {
state.dispatch(actions.displayWarning('Error in retrieving state logs.')) state.dispatch(actions.displayWarning('Error in retrieving state logs.'))
} else { } else {
exportAsFile('MetaMask State Logs', result) exportAsFile('MetaMask State Logs.json', result)
} }
}) })
}, },

@ -21,6 +21,7 @@ html, body {
background: #F7F7F7; background: #F7F7F7;
margin: 0; margin: 0;
padding: 0; padding: 0;
height: 100%;
} }
html { html {
@ -107,6 +108,10 @@ button:not([disabled]):active, input[type="submit"]:not([disabled]):active {
transform: scale(0.95); transform: scale(0.95);
} }
.grow-on-hover:hover {
transform: scale(1.05);
}
a { a {
text-decoration: none; text-decoration: none;
color: inherit; color: inherit;
@ -436,12 +441,14 @@ input.large-input {
.account-detail-section { .account-detail-section {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
max-height: 465px;
flex-direction: inherit; flex-direction: inherit;
}
.name-label { .account-detail-section .name-label {
margin-left: 15px; margin-left: 15px;
}
} }
.grow-tenx { .grow-tenx {

@ -247,10 +247,26 @@ SendTransactionScreen.prototype.onSubmit = function () {
const recipient = state.recipient || document.querySelector('input[name="address"]').value.replace(/^[.\s]+|[.\s]+$/g, '') const recipient = state.recipient || document.querySelector('input[name="address"]').value.replace(/^[.\s]+|[.\s]+$/g, '')
const nickname = state.nickname || ' ' const nickname = state.nickname || ' '
const input = document.querySelector('input[name="amount"]').value const input = document.querySelector('input[name="amount"]').value
const parts = input.split('')
let message
if (isNaN(input) || input === '') {
message = 'Invalid ether value.'
return this.props.dispatch(actions.displayWarning(message))
}
if (parts[1]) {
var decimal = parts[1]
if (decimal.length > 18) {
message = 'Ether amount is too precise.'
return this.props.dispatch(actions.displayWarning(message))
}
}
const value = util.normalizeEthStringToWei(input) const value = util.normalizeEthStringToWei(input)
const txData = document.querySelector('input[name="txData"]').value const txData = document.querySelector('input[name="txData"]').value
const balance = this.props.balance const balance = this.props.balance
let message
if (value.gt(balance)) { if (value.gt(balance)) {
message = 'Insufficient funds.' message = 'Insufficient funds.'

@ -9,11 +9,11 @@
"ui": "npm run test:flat:build:states && beefy ui-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", "ui": "npm run test:flat:build:states && beefy ui-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./",
"mock": "beefy mock-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", "mock": "beefy mock-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./",
"watch": "mocha watch --recursive \"test/unit/**/*.js\"", "watch": "mocha watch --recursive \"test/unit/**/*.js\"",
"mascara": "METAMASK_DEBUG=true node ./mascara/example/server", "mascara": "gulp build && METAMASK_DEBUG=true node ./mascara/example/server",
"dist": "npm run dist:clear && npm install && gulp dist", "dist": "npm run dist:clear && npm install && gulp dist",
"dist:clear": "rm -rf node_modules/eth-contract-metadata && rm -rf node_modules/eth-phishing-detect", "dist:clear": "rm -rf node_modules/eth-contract-metadata && rm -rf node_modules/eth-phishing-detect",
"test": "npm run lint && npm run test:coverage && npm run test:integration", "test": "npm run lint && npm run test:coverage && npm run test:integration",
"test:unit": "METAMASK_ENV=test mocha --compilers js:babel-core/register --require test/helper.js --recursive \"test/unit/**/*.js\"", "test:unit": "METAMASK_ENV=test mocha --exit --compilers js:babel-core/register --require test/helper.js --recursive \"test/unit/**/*.js\"",
"test:single": "METAMASK_ENV=test mocha --require test/helper.js", "test:single": "METAMASK_ENV=test mocha --require test/helper.js",
"test:integration": "gulp build:scss && npm run test:flat && npm run test:mascara", "test:integration": "gulp build:scss && npm run test:flat && npm run test:mascara",
"test:coverage": "nyc npm run test:unit && npm run test:coveralls-upload", "test:coverage": "nyc npm run test:unit && npm run test:coveralls-upload",
@ -77,14 +77,14 @@
"eslint-plugin-react": "^7.4.0", "eslint-plugin-react": "^7.4.0",
"eth-bin-to-ops": "^1.0.1", "eth-bin-to-ops": "^1.0.1",
"eth-block-tracker": "^2.2.0", "eth-block-tracker": "^2.2.0",
"eth-json-rpc-filters": "^1.2.5",
"eth-json-rpc-infura": "^2.0.5",
"eth-keyring-controller": "^2.1.4",
"eth-contract-metadata": "^1.1.5", "eth-contract-metadata": "^1.1.5",
"eth-hd-keyring": "^1.2.1", "eth-hd-keyring": "^1.2.1",
"eth-json-rpc-filters": "^1.2.4",
"eth-keyring-controller": "^2.1.2",
"eth-phishing-detect": "^1.1.4", "eth-phishing-detect": "^1.1.4",
"eth-query": "^2.1.2", "eth-query": "^2.1.2",
"eth-sig-util": "^1.4.0", "eth-sig-util": "^1.4.2",
"eth-simple-keyring": "^1.2.0",
"eth-token-tracker": "^1.1.4", "eth-token-tracker": "^1.1.4",
"ethereumjs-abi": "^0.6.4", "ethereumjs-abi": "^0.6.4",
"ethereumjs-tx": "^1.3.0", "ethereumjs-tx": "^1.3.0",
@ -129,6 +129,7 @@
"obj-multiplex": "^1.0.0", "obj-multiplex": "^1.0.0",
"obs-store": "^3.0.0", "obs-store": "^3.0.0",
"once": "^1.3.3", "once": "^1.3.3",
"percentile": "^1.2.0",
"ping-pong-stream": "^1.0.0", "ping-pong-stream": "^1.0.0",
"pojo-migrator": "^2.1.0", "pojo-migrator": "^2.1.0",
"polyfill-crypto.getrandomvalues": "^1.0.0", "polyfill-crypto.getrandomvalues": "^1.0.0",
@ -162,15 +163,15 @@
"request-promise": "^4.2.1", "request-promise": "^4.2.1",
"sandwich-expando": "^1.1.3", "sandwich-expando": "^1.1.3",
"semaphore": "^1.0.5", "semaphore": "^1.0.5",
"shallow-copy": "0.0.1",
"semver": "^5.4.1", "semver": "^5.4.1",
"shallow-copy": "0.0.1",
"sw-stream": "^2.0.0", "sw-stream": "^2.0.0",
"textarea-caret": "^3.0.1", "textarea-caret": "^3.0.1",
"through2": "^2.0.3", "through2": "^2.0.3",
"valid-url": "^1.0.9", "valid-url": "^1.0.9",
"vreme": "^3.0.2", "vreme": "^3.0.2",
"web3": "^0.20.1", "web3": "^0.20.1",
"web3-provider-engine": "^13.3.2", "web3-provider-engine": "^13.5.0",
"web3-stream-provider": "^3.0.1", "web3-stream-provider": "^3.0.1",
"xtend": "^4.0.1" "xtend": "^4.0.1"
}, },
@ -200,16 +201,20 @@
"eth-json-rpc-middleware": "^1.2.7", "eth-json-rpc-middleware": "^1.2.7",
"fs-promise": "^2.0.3", "fs-promise": "^2.0.3",
"gulp": "github:gulpjs/gulp#4.0", "gulp": "github:gulpjs/gulp#4.0",
"gulp-if": "^2.0.1", "gulp-babel": "^7.0.0",
"gulp-if": "^2.0.2",
"gulp-json-editor": "^2.2.1", "gulp-json-editor": "^2.2.1",
"gulp-livereload": "^3.8.1", "gulp-livereload": "^3.8.1",
"gulp-replace": "^0.6.1", "gulp-replace": "^0.6.1",
"gulp-sourcemaps": "^2.6.0", "gulp-sourcemaps": "^2.6.0",
"gulp-stylefmt": "^1.1.0", "gulp-stylefmt": "^1.1.0",
"gulp-stylelint": "^4.0.0", "gulp-stylelint": "^4.0.0",
"gulp-uglify": "^3.0.0",
"gulp-uglify-es": "^1.0.0",
"gulp-util": "^3.0.7", "gulp-util": "^3.0.7",
"gulp-watch": "^4.3.5", "gulp-watch": "^4.3.5",
"gulp-zip": "^4.0.0", "gulp-zip": "^4.0.0",
"gulp-eslint": "^4.0.0",
"isomorphic-fetch": "^2.2.1", "isomorphic-fetch": "^2.2.1",
"jsdom": "^11.1.0", "jsdom": "^11.1.0",
"jsdom-global": "^3.0.2", "jsdom-global": "^3.0.2",
@ -239,8 +244,8 @@
"tape": "^4.5.1", "tape": "^4.5.1",
"testem": "^1.10.3", "testem": "^1.10.3",
"uglifyify": "^4.0.2", "uglifyify": "^4.0.2",
"vinyl-buffer": "^1.0.0", "vinyl-buffer": "^1.0.1",
"vinyl-source-stream": "^1.1.0", "vinyl-source-stream": "^2.0.0",
"watchify": "^3.9.0" "watchify": "^3.9.0"
}, },
"engines": { "engines": {

@ -54,6 +54,8 @@ module.exports = function(config) {
// Concurrency level // Concurrency level
// how many browser should be started simultaneous // how many browser should be started simultaneous
concurrency: Infinity concurrency: 1,
nocache: true,
} }
} }

@ -5,7 +5,8 @@ module.exports = {
createEngineForTestData, createEngineForTestData,
providerFromEngine, providerFromEngine,
scaffoldMiddleware, scaffoldMiddleware,
createStubedProvider createEthJsQueryStub,
createStubedProvider,
} }
@ -18,6 +19,18 @@ function providerFromEngine (engine) {
return provider return provider
} }
function createEthJsQueryStub (stubProvider) {
return new Proxy({}, {
get: (obj, method) => {
return (...params) => {
return new Promise((resolve, reject) => {
stubProvider.sendAsync({ method: `eth_${method}`, params }, (err, ress) => resolve(ress.result))
})
}
},
})
}
function createStubedProvider (resultStub) { function createStubedProvider (resultStub) {
const engine = createEngineForTestData() const engine = createEngineForTestData()
engine.push(scaffoldMiddleware(resultStub)) engine.push(scaffoldMiddleware(resultStub))

@ -51,9 +51,8 @@ describe('tx confirmation screen', function () {
actions.cancelTx({value: firstTxId})((action) => { actions.cancelTx({value: firstTxId})((action) => {
result = reducers(initialState, action) result = reducers(initialState, action)
done()
}) })
done()
}) })
it('should transition to the account detail view', function () { it('should transition to the account detail view', function () {

@ -3,6 +3,8 @@ const sinon = require('sinon')
const clone = require('clone') const clone = require('clone')
const MetaMaskController = require('../../app/scripts/metamask-controller') const MetaMaskController = require('../../app/scripts/metamask-controller')
const firstTimeState = require('../../app/scripts/first-time-state') const firstTimeState = require('../../app/scripts/first-time-state')
const BN = require('ethereumjs-util').BN
const GWEI_BN = new BN('1000000000')
describe('MetaMaskController', function () { describe('MetaMaskController', function () {
const noop = () => {} const noop = () => {}
@ -39,17 +41,63 @@ describe('MetaMaskController', function () {
beforeEach(function () { beforeEach(function () {
sinon.spy(metamaskController.keyringController, 'createNewVaultAndKeychain') sinon.spy(metamaskController.keyringController, 'createNewVaultAndKeychain')
sinon.spy(metamaskController.keyringController, 'createNewVaultAndRestore')
}) })
afterEach(function () { afterEach(function () {
metamaskController.keyringController.createNewVaultAndKeychain.restore() metamaskController.keyringController.createNewVaultAndKeychain.restore()
metamaskController.keyringController.createNewVaultAndRestore.restore()
})
describe('#getGasPrice', function () {
it('gives the 50th percentile lowest accepted gas price from recentBlocksController', async function () {
const realRecentBlocksController = metamaskController.recentBlocksController
metamaskController.recentBlocksController = {
store: {
getState: () => {
return {
recentBlocks: [
{ gasPrices: [ '0x3b9aca00', '0x174876e800'] },
{ gasPrices: [ '0x3b9aca00', '0x174876e800'] },
{ gasPrices: [ '0x174876e800', '0x174876e800' ]},
{ gasPrices: [ '0x174876e800', '0x174876e800' ]},
]
}
}
}
}
const gasPrice = metamaskController.getGasPrice()
assert.equal(gasPrice, '0x3b9aca00', 'accurately estimates 50th percentile accepted gas price')
metamaskController.recentBlocksController = realRecentBlocksController
})
it('gives the 1 gwei price if no blocks have been seen.', async function () {
const realRecentBlocksController = metamaskController.recentBlocksController
metamaskController.recentBlocksController = {
store: {
getState: () => {
return {
recentBlocks: []
}
}
}
}
const gasPrice = metamaskController.getGasPrice()
assert.equal(gasPrice, '0x' + GWEI_BN.toString(16), 'defaults to 1 gwei')
metamaskController.recentBlocksController = realRecentBlocksController
})
}) })
describe('#createNewVaultAndKeychain', function () { describe('#createNewVaultAndKeychain', function () {
it('can only create new vault on keyringController once', async function () { it('can only create new vault on keyringController once', async function () {
const selectStub = sinon.stub(metamaskController, 'selectFirstIdentity') const selectStub = sinon.stub(metamaskController, 'selectFirstIdentity')
const password = 'a-fake-password' const password = 'a-fake-password'
const first = await metamaskController.createNewVaultAndKeychain(password) const first = await metamaskController.createNewVaultAndKeychain(password)
@ -60,6 +108,22 @@ describe('MetaMaskController', function () {
selectStub.reset() selectStub.reset()
}) })
}) })
describe('#createNewVaultAndRestore', function () {
it('should be able to call newVaultAndRestore despite a mistake.', async function () {
// const selectStub = sinon.stub(metamaskController, 'selectFirstIdentity')
const password = 'what-what-what'
const wrongSeed = 'debris dizzy just program just float decrease vacant alarm reduce speak stadiu'
const rightSeed = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium'
const first = await metamaskController.createNewVaultAndRestore(password, wrongSeed)
.catch((e) => {
return
})
const second = await metamaskController.createNewVaultAndRestore(password, rightSeed)
assert(metamaskController.keyringController.createNewVaultAndRestore.calledTwice)
})
})
}) })
}) })

@ -339,5 +339,64 @@ describe('PendingTransactionTracker', function () {
assert.equal(pendingTxTracker.publishTransaction.callCount, 1, 'Should call publish transaction') assert.equal(pendingTxTracker.publishTransaction.callCount, 1, 'Should call publish transaction')
}) })
}) })
describe('#_checkIfNonceIsTaken', function () {
beforeEach ( function () {
let confirmedTxList = [{
id: 1,
hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
status: 'confirmed',
txParams: {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
nonce: '0x1',
value: '0xfffff',
},
rawTx: '0xf86c808504a817c800827b0d940c62bb85faa3311a998d3aba8098c1235c564966880de0b6b3a7640000802aa08ff665feb887a25d4099e40e11f0fef93ee9608f404bd3f853dd9e84ed3317a6a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d',
}, {
id: 2,
hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
status: 'confirmed',
txParams: {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
nonce: '0x2',
value: '0xfffff',
},
rawTx: '0xf86c808504a817c800827b0d940c62bb85faa3311a998d3aba8098c1235c564966880de0b6b3a7640000802aa08ff665feb887a25d4099e40e11f0fef93ee9608f404bd3f853dd9e84ed3317a6a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d',
}]
pendingTxTracker.getCompletedTransactions = (address) => {
if (!address) throw new Error('unless behavior has changed #_checkIfNonceIsTaken needs a filtered list of transactions to see if the nonce is taken')
return confirmedTxList
}
})
it('should return false if nonce has not been taken', function (done) {
pendingTxTracker._checkIfNonceIsTaken({
txParams: {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
nonce: '0x3',
value: '0xfffff',
},
})
.then((taken) => {
assert.ok(!taken)
done()
})
.catch(done)
})
it('should return true if nonce has been taken', function (done) {
pendingTxTracker._checkIfNonceIsTaken({
txParams: {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
nonce: '0x2',
value: '0xfffff',
},
}).then((taken) => {
assert.ok(taken)
done()
})
.catch(done)
})
})
}) })

@ -0,0 +1,48 @@
const assert = require('assert')
const PreferencesController = require('../../app/scripts/controllers/preferences')
describe('preferences controller', function () {
let preferencesController
before(() => {
preferencesController = new PreferencesController()
})
describe('addToken', function () {
it('should add that token to its state', async function () {
const address = '0xabcdef1234567'
const symbol = 'ABBR'
const decimals = 5
await preferencesController.addToken(address, symbol, decimals)
const tokens = preferencesController.getTokens()
assert.equal(tokens.length, 1, 'one token added')
const added = tokens[0]
assert.equal(added.address, address, 'set address correctly')
assert.equal(added.symbol, symbol, 'set symbol correctly')
assert.equal(added.decimals, decimals, 'set decimals correctly')
})
it('should allow updating a token value', async function () {
const address = '0xabcdef1234567'
const symbol = 'ABBR'
const decimals = 5
await preferencesController.addToken(address, symbol, decimals)
const newDecimals = 6
await preferencesController.addToken(address, symbol, newDecimals)
const tokens = preferencesController.getTokens()
assert.equal(tokens.length, 1, 'one token added')
const added = tokens[0]
assert.equal(added.address, address, 'set address correctly')
assert.equal(added.symbol, symbol, 'set symbol correctly')
assert.equal(added.decimals, newDecimals, 'updated decimals correctly')
})
})
})

@ -5,7 +5,7 @@ const ObservableStore = require('obs-store')
const sinon = require('sinon') const sinon = require('sinon')
const TransactionController = require('../../app/scripts/controllers/transactions') const TransactionController = require('../../app/scripts/controllers/transactions')
const TxGasUtils = require('../../app/scripts/lib/tx-gas-utils') const TxGasUtils = require('../../app/scripts/lib/tx-gas-utils')
const { createStubedProvider } = require('../stub/provider') const { createStubedProvider, createEthJsQueryStub } = require('../stub/provider')
const noop = () => true const noop = () => true
const currentNetworkId = 42 const currentNetworkId = 42
@ -30,6 +30,8 @@ describe('Transaction Controller', function () {
resolve() resolve()
}), }),
}) })
txController.query = createEthJsQueryStub(provider)
txController.txGasUtil.query = createEthJsQueryStub(provider)
txController.nonceTracker.getNonceLock = () => Promise.resolve({ nextNonce: 0, releaseLock: noop }) txController.nonceTracker.getNonceLock = () => Promise.resolve({ nextNonce: 0, releaseLock: noop })
txController.txProviderUtils = new TxGasUtils(txController.provider) txController.txProviderUtils = new TxGasUtils(txController.provider)
}) })
@ -110,23 +112,16 @@ describe('Transaction Controller', function () {
history: [], history: [],
} }
txController.txStateManager._saveTxList([txMeta]) txController.txStateManager._saveTxList([txMeta])
stub = sinon.stub(txController, 'addUnapprovedTransaction').returns(Promise.resolve(txController.txStateManager.addTx(txMeta))) stub = sinon.stub(txController, 'addUnapprovedTransaction').callsFake(() => {
txController.emit('newUnapprovedTx', txMeta)
return Promise.resolve(txController.txStateManager.addTx(txMeta))
}) })
afterEach(function () { afterEach(function () {
txController.txStateManager._saveTxList([]) txController.txStateManager._saveTxList([])
stub.restore() stub.restore()
}) })
})
it('should emit newUnapprovedTx event and pass txMeta as the first argument', function (done) {
txController.once('newUnapprovedTx', (txMetaFromEmit) => {
assert(txMetaFromEmit, 'txMeta is falsey')
assert.equal(txMetaFromEmit.id, 1, 'the right txMeta was passed')
done()
})
txController.newUnapprovedTransaction(txParams)
.catch(done)
})
it('should resolve when finished and status is submitted and resolve with the hash', function (done) { it('should resolve when finished and status is submitted and resolve with the hash', function (done) {
txController.once('newUnapprovedTx', (txMetaFromEmit) => { txController.once('newUnapprovedTx', (txMetaFromEmit) => {
@ -160,8 +155,17 @@ describe('Transaction Controller', function () {
}) })
describe('#addUnapprovedTransaction', function () { describe('#addUnapprovedTransaction', function () {
let addTxDefaults
beforeEach(() => {
addTxDefaults = txController.addTxDefaults
txController.addTxDefaults = function addTxDefaultsStub () { return Promise.resolve() }
})
afterEach(() => {
txController.addTxDefaults = addTxDefaults
})
it('should add an unapproved transaction and return a valid txMeta', function (done) { it('should add an unapproved transaction and return a valid txMeta', function (done) {
const addTxDefaultsStub = sinon.stub(txController, 'addTxDefaults').callsFake(() => Promise.resolve())
txController.addUnapprovedTransaction({}) txController.addUnapprovedTransaction({})
.then((txMeta) => { .then((txMeta) => {
assert(('id' in txMeta), 'should have a id') assert(('id' in txMeta), 'should have a id')
@ -172,10 +176,20 @@ describe('Transaction Controller', function () {
const memTxMeta = txController.txStateManager.getTx(txMeta.id) const memTxMeta = txController.txStateManager.getTx(txMeta.id)
assert.deepEqual(txMeta, memTxMeta, `txMeta should be stored in txController after adding it\n expected: ${txMeta} \n got: ${memTxMeta}`) assert.deepEqual(txMeta, memTxMeta, `txMeta should be stored in txController after adding it\n expected: ${txMeta} \n got: ${memTxMeta}`)
addTxDefaultsStub.restore()
done() done()
}).catch(done) }).catch(done)
}) })
it('should emit newUnapprovedTx event and pass txMeta as the first argument', function (done) {
providerResultStub.eth_gasPrice = '4a817c800'
txController.once('newUnapprovedTx', (txMetaFromEmit) => {
assert(txMetaFromEmit, 'txMeta is falsey')
done()
})
txController.addUnapprovedTransaction({})
.catch(done)
})
}) })
describe('#addTxDefaults', function () { describe('#addTxDefaults', function () {

@ -0,0 +1,32 @@
const assert = require('assert')
const TxGasUtils = require('../../app/scripts/lib/tx-gas-utils')
const { createStubedProvider } = require('../stub/provider')
describe('Tx Gas Util', function () {
let txGasUtil, provider, providerResultStub
beforeEach(function () {
providerResultStub = {}
provider = createStubedProvider(providerResultStub)
txGasUtil = new TxGasUtils({
provider,
})
})
it('removes recipient for txParams with 0x when contract data is provided', function () {
const zeroRecipientandDataTxParams = {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
to: '0x',
data: 'bytecode',
}
const sanitizedTxParams = txGasUtil.validateRecipient(zeroRecipientandDataTxParams)
assert.deepEqual(sanitizedTxParams, { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', data: 'bytecode' }, 'no recipient with 0x')
})
it('should error when recipient is 0x', function () {
const zeroRecipientTxParams = {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
to: '0x',
}
assert.throws(() => { txGasUtil.validateRecipient(zeroRecipientTxParams) }, Error, 'Invalid recipient address')
})
})

@ -201,6 +201,18 @@ describe('util', function () {
var output = util.normalizeEthStringToWei(input) var output = util.normalizeEthStringToWei(input)
assert.equal(output.toString(10), ethInWei) assert.equal(output.toString(10), ethInWei)
}) })
it('should account for overflow numbers gracefully by dropping extra precision.', function () {
var input = '1.11111111111111111111'
var output = util.normalizeEthStringToWei(input)
assert.equal(output.toString(10), '1111111111111111111')
})
it('should not truncate very exact wei values that do not have extra precision.', function () {
var input = '1.100000000000000001'
var output = util.normalizeEthStringToWei(input)
assert.equal(output.toString(10), '1100000000000000001')
})
}) })
describe('#normalizeNumberToWei', function () { describe('#normalizeNumberToWei', function () {

@ -2,7 +2,6 @@ const inherits = require('util').inherits
const Component = require('react').Component const Component = require('react').Component
const h = require('react-hyperscript') const h = require('react-hyperscript')
const connect = require('react-redux').connect const connect = require('react-redux').connect
const actions = require('../../actions')
import Select from 'react-select' import Select from 'react-select'
// Subviews // Subviews
@ -34,37 +33,14 @@ AccountImportSubview.prototype.render = function () {
const { type } = state const { type } = state
return ( return (
h('div.flex-center', { h('div.new-account-import-form', [
style: {
flexDirection: 'column',
marginTop: '32px',
},
}, [
h('.section-title.flex-row.flex-center', [
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
onClick: (event) => {
props.dispatch(actions.goHome())
},
}),
h('h2.page-subtitle', 'Import Accounts'),
]),
h('div', {
style: {
padding: '10px 0',
width: '260px',
color: 'rgb(174, 174, 174)',
},
}, [
h('h3', { style: { padding: '3px' } }, 'SELECT TYPE'), h('div.new-account-import-form__select-section', [
h('style', ` h('div.new-account-import-form__select-label', 'SELECT TYPE'),
.has-value.Select--single > .Select-control .Select-value .Select-value-label, .Select-value-label {
color: rgb(174,174,174);
}
`),
h(Select, { h(Select, {
className: 'new-account-import-form__select',
name: 'import-type-select', name: 'import-type-select',
clearable: false, clearable: false,
value: type || menuItems[0], value: type || menuItems[0],
@ -75,10 +51,10 @@ AccountImportSubview.prototype.render = function () {
} }
}), }),
onChange: (opt) => { onChange: (opt) => {
props.dispatch(actions.showImportPage())
this.setState({ type: opt.value }) this.setState({ type: opt.value })
}, },
}), }),
]), ]),
this.renderImportView(), this.renderImportView(),

@ -0,0 +1,96 @@
const { Component } = require('react')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const { connect } = require('react-redux')
const actions = require('../../actions')
class NewAccountCreateForm extends Component {
constructor (props) {
super(props)
const { numberOfExistingAccounts = 0 } = props
const newAccountNumber = numberOfExistingAccounts + 1
this.state = {
newAccountName: `Account ${newAccountNumber}`,
}
}
render () {
const { newAccountName } = this.state
return h('div.new-account-create-form', [
h('div.new-account-create-form__input-label', {}, [
'Account Name',
]),
h('div.new-account-create-form__input-wrapper', {}, [
h('input.new-account-create-form__input', {
value: this.state.newAccountName,
placeholder: 'E.g. My new account',
onChange: event => this.setState({ newAccountName: event.target.value }),
}, []),
]),
h('div.new-account-create-form__buttons', {}, [
h('button.new-account-create-form__button-cancel', {
onClick: () => this.props.goHome(),
}, [
'CANCEL',
]),
h('button.new-account-create-form__button-create', {
onClick: () => this.props.createAccount(newAccountName),
}, [
'CREATE',
]),
]),
])
}
}
NewAccountCreateForm.propTypes = {
hideModal: PropTypes.func,
showImportPage: PropTypes.func,
createAccount: PropTypes.func,
goHome: PropTypes.func,
numberOfExistingAccounts: PropTypes.number,
}
const mapStateToProps = state => {
const { metamask: { network, selectedAddress, identities = {} } } = state
const numberOfExistingAccounts = Object.keys(identities).length
return {
network,
address: selectedAddress,
numberOfExistingAccounts,
}
}
const mapDispatchToProps = dispatch => {
return {
toCoinbase: (address) => {
dispatch(actions.buyEth({ network: '1', address, amount: 0 }))
},
hideModal: () => {
dispatch(actions.hideModal())
},
createAccount: (newAccountName) => {
dispatch(actions.addNewAccount())
.then((newAccountAddress) => {
if (newAccountName) {
dispatch(actions.saveAccountLabel(newAccountAddress, newAccountName))
}
dispatch(actions.goHome())
})
},
showImportPage: () => dispatch(actions.showImportPage()),
goHome: () => dispatch(actions.goHome()),
}
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(NewAccountCreateForm)

@ -0,0 +1,81 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const actions = require('../../actions')
const { getCurrentViewContext } = require('../../selectors')
const classnames = require('classnames')
const NewAccountCreateForm = require('./create-form')
const NewAccountImportForm = require('../import')
function mapStateToProps (state) {
return {
displayedForm: getCurrentViewContext(state),
}
}
function mapDispatchToProps (dispatch) {
return {
displayForm: form => dispatch(actions.setNewAccountForm(form)),
showQrView: (selected, identity) => dispatch(actions.showQrView(selected, identity)),
showExportPrivateKeyModal: () => {
dispatch(actions.showModal({ name: 'EXPORT_PRIVATE_KEY' }))
},
hideModal: () => dispatch(actions.hideModal()),
saveAccountLabel: (address, label) => dispatch(actions.saveAccountLabel(address, label)),
}
}
inherits(AccountDetailsModal, Component)
function AccountDetailsModal (props) {
Component.call(this)
this.state = {
displayedForm: props.displayedForm,
}
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountDetailsModal)
AccountDetailsModal.prototype.render = function () {
const { displayedForm, displayForm } = this.props
return h('div.new-account', {}, [
h('div.new-account__header', [
h('div.new-account__title', 'New Account'),
h('div.new-account__tabs', [
h('div.new-account__tabs__tab', {
className: classnames('new-account__tabs__tab', {
'new-account__tabs__selected': displayedForm === 'CREATE',
'new-account__tabs__unselected cursor-pointer': displayedForm !== 'CREATE',
}),
onClick: () => displayForm('CREATE'),
}, 'Create'),
h('div.new-account__tabs__tab', {
className: classnames('new-account__tabs__tab', {
'new-account__tabs__selected': displayedForm === 'IMPORT',
'new-account__tabs__unselected cursor-pointer': displayedForm !== 'IMPORT',
}),
onClick: () => displayForm('IMPORT'),
}, 'Import'),
]),
]),
h('div.new-account__form', [
displayedForm === 'CREATE'
? h(NewAccountCreateForm)
: h(NewAccountImportForm),
]),
])
}

@ -1,5 +1,6 @@
const abi = require('human-standard-token-abi') const abi = require('human-standard-token-abi')
const getBuyEthUrl = require('../../app/scripts/lib/buy-eth-url') const getBuyEthUrl = require('../../app/scripts/lib/buy-eth-url')
const { getTokenAddressFromTokenObject } = require('./util')
const ethUtil = require('ethereumjs-util') const ethUtil = require('ethereumjs-util')
var actions = { var actions = {
@ -50,12 +51,16 @@ var actions = {
SHOW_NEW_VAULT_SEED: 'SHOW_NEW_VAULT_SEED', SHOW_NEW_VAULT_SEED: 'SHOW_NEW_VAULT_SEED',
SHOW_INFO_PAGE: 'SHOW_INFO_PAGE', SHOW_INFO_PAGE: 'SHOW_INFO_PAGE',
SHOW_IMPORT_PAGE: 'SHOW_IMPORT_PAGE', SHOW_IMPORT_PAGE: 'SHOW_IMPORT_PAGE',
SHOW_NEW_ACCOUNT_PAGE: 'SHOW_NEW_ACCOUNT_PAGE',
SET_NEW_ACCOUNT_FORM: 'SET_NEW_ACCOUNT_FORM',
unlockMetamask: unlockMetamask, unlockMetamask: unlockMetamask,
unlockFailed: unlockFailed, unlockFailed: unlockFailed,
showCreateVault: showCreateVault, showCreateVault: showCreateVault,
showRestoreVault: showRestoreVault, showRestoreVault: showRestoreVault,
showInitializeMenu: showInitializeMenu, showInitializeMenu: showInitializeMenu,
showImportPage, showImportPage,
showNewAccountPage,
setNewAccountForm,
createNewVaultAndKeychain: createNewVaultAndKeychain, createNewVaultAndKeychain: createNewVaultAndKeychain,
createNewVaultAndRestore: createNewVaultAndRestore, createNewVaultAndRestore: createNewVaultAndRestore,
createNewVaultInProgress: createNewVaultInProgress, createNewVaultInProgress: createNewVaultInProgress,
@ -125,6 +130,7 @@ var actions = {
sendTx: sendTx, sendTx: sendTx,
signTx: signTx, signTx: signTx,
signTokenTx: signTokenTx, signTokenTx: signTokenTx,
updateTransaction,
updateAndApproveTx, updateAndApproveTx,
cancelTx: cancelTx, cancelTx: cancelTx,
completedTx: completedTx, completedTx: completedTx,
@ -244,6 +250,13 @@ var actions = {
setFeatureFlag, setFeatureFlag,
updateFeatureFlags, updateFeatureFlags,
UPDATE_FEATURE_FLAGS: 'UPDATE_FEATURE_FLAGS', UPDATE_FEATURE_FLAGS: 'UPDATE_FEATURE_FLAGS',
// Network
setNetworkEndpoints,
updateNetworkEndpointType,
UPDATE_NETWORK_ENDPOINT_TYPE: 'UPDATE_NETWORK_ENDPOINT_TYPE',
retryTransaction,
} }
module.exports = actions module.exports = actions
@ -714,6 +727,23 @@ function signTokenTx (tokenAddress, toAddress, amount, txData) {
} }
} }
function updateTransaction (txData) {
log.info('actions: updateTx: ' + JSON.stringify(txData))
return (dispatch) => {
log.debug(`actions calling background.updateTx`)
background.updateTransaction(txData, (err) => {
dispatch(actions.hideLoadingIndication())
dispatch(actions.updateTransactionParams(txData.id, txData.txParams))
if (err) {
dispatch(actions.txError(err))
dispatch(actions.goHome())
return log.error(err.message)
}
dispatch(actions.showConfTxPage({ id: txData.id }))
})
}
}
function updateAndApproveTx (txData) { function updateAndApproveTx (txData) {
log.info('actions: updateAndApproveTx: ' + JSON.stringify(txData)) log.info('actions: updateAndApproveTx: ' + JSON.stringify(txData))
return (dispatch) => { return (dispatch) => {
@ -829,6 +859,7 @@ function cancelTx (txData) {
log.debug(`background.cancelTransaction`) log.debug(`background.cancelTransaction`)
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
background.cancelTransaction(txData.id, () => { background.cancelTransaction(txData.id, () => {
dispatch(actions.clearSend())
dispatch(actions.completedTx(txData.id)) dispatch(actions.completedTx(txData.id))
resolve(txData) resolve(txData)
}) })
@ -880,6 +911,20 @@ function showImportPage () {
} }
} }
function showNewAccountPage (formToSelect) {
return {
type: actions.SHOW_NEW_ACCOUNT_PAGE,
formToSelect,
}
}
function setNewAccountForm (formToSelect) {
return {
type: actions.SET_NEW_ACCOUNT_FORM,
formToSelect,
}
}
function createNewVaultInProgress () { function createNewVaultInProgress () {
return { return {
type: actions.CREATE_NEW_VAULT_IN_PROGRESS, type: actions.CREATE_NEW_VAULT_IN_PROGRESS,
@ -976,9 +1021,13 @@ function lockMetamask () {
}) })
.then(newState => { .then(newState => {
dispatch(actions.updateMetamaskState(newState)) dispatch(actions.updateMetamaskState(newState))
dispatch(actions.hideLoadingIndication())
dispatch({ type: actions.LOCK_METAMASK })
})
.catch(() => {
dispatch(actions.hideLoadingIndication())
dispatch({ type: actions.LOCK_METAMASK }) dispatch({ type: actions.LOCK_METAMASK })
}) })
.catch(() => dispatch({ type: actions.LOCK_METAMASK }))
} }
} }
@ -1123,10 +1172,12 @@ function removeToken (address) {
function addTokens (tokens) { function addTokens (tokens) {
return dispatch => { return dispatch => {
if (Array.isArray(tokens)) { if (Array.isArray(tokens)) {
dispatch(actions.setSelectedToken(getTokenAddressFromTokenObject(tokens[0])))
return Promise.all(tokens.map(({ address, symbol, decimals }) => ( return Promise.all(tokens.map(({ address, symbol, decimals }) => (
dispatch(addToken(address, symbol, decimals)) dispatch(addToken(address, symbol, decimals))
))) )))
} else { } else {
dispatch(actions.setSelectedToken(getTokenAddressFromTokenObject(tokens)))
return Promise.all( return Promise.all(
Object Object
.entries(tokens) .entries(tokens)
@ -1196,6 +1247,19 @@ function markAccountsFound () {
return callBackgroundThenUpdate(background.markAccountsFound) return callBackgroundThenUpdate(background.markAccountsFound)
} }
function retryTransaction (txId) {
log.debug(`background.retryTransaction`)
return (dispatch) => {
background.retryTransaction(txId, (err, newState) => {
if (err) {
return dispatch(actions.displayWarning(err.message))
}
dispatch(actions.updateMetamaskState(newState))
dispatch(actions.viewPendingTx(txId))
})
}
}
// //
// config // config
// //
@ -1472,7 +1536,6 @@ function pairUpdate (coin) {
function shapeShiftSubview (network) { function shapeShiftSubview (network) {
var pair = 'btc_eth' var pair = 'btc_eth'
return (dispatch) => { return (dispatch) => {
dispatch(actions.showSubLoadingIndication()) dispatch(actions.showSubLoadingIndication())
shapeShiftRequest('marketinfo', {pair}, (mktResponse) => { shapeShiftRequest('marketinfo', {pair}, (mktResponse) => {
@ -1498,7 +1561,7 @@ function coinShiftRquest (data, marketData) {
dispatch(actions.hideLoadingIndication()) dispatch(actions.hideLoadingIndication())
if (response.error) return dispatch(actions.displayWarning(response.error)) if (response.error) return dispatch(actions.displayWarning(response.error))
var message = ` var message = `
Deposit your ${response.depositType} to the address bellow:` Deposit your ${response.depositType} to the address below:`
log.debug(`background.createShapeShiftTx`) log.debug(`background.createShapeShiftTx`)
background.createShapeShiftTx(response.deposit, response.depositType) background.createShapeShiftTx(response.deposit, response.depositType)
dispatch(actions.showQrView(response.deposit, [message].concat(marketData))) dispatch(actions.showQrView(response.deposit, [message].concat(marketData)))
@ -1534,7 +1597,7 @@ function reshowQrCode (data, coin) {
if (mktResponse.error) return dispatch(actions.displayWarning(mktResponse.error)) if (mktResponse.error) return dispatch(actions.displayWarning(mktResponse.error))
var message = [ var message = [
`Deposit your ${coin} to the address bellow:`, `Deposit your ${coin} to the address below:`,
`Deposit Limit: ${mktResponse.limit}`, `Deposit Limit: ${mktResponse.limit}`,
`Deposit Minimum:${mktResponse.minimum}`, `Deposit Minimum:${mktResponse.minimum}`,
] ]
@ -1600,10 +1663,7 @@ function updateTokenExchangeRate (token = '') {
} }
} }
function setFeatureFlag (feature, activated) { function setFeatureFlag (feature, activated, notificationType) {
const notificationType = activated
? 'BETA_UI_NOTIFICATION_MODAL'
: 'OLD_UI_NOTIFICATION_MODAL'
return (dispatch) => { return (dispatch) => {
dispatch(actions.showLoadingIndication()) dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -1611,10 +1671,10 @@ function setFeatureFlag (feature, activated) {
dispatch(actions.hideLoadingIndication()) dispatch(actions.hideLoadingIndication())
if (err) { if (err) {
dispatch(actions.displayWarning(err.message)) dispatch(actions.displayWarning(err.message))
reject(err) return reject(err)
} }
dispatch(actions.updateFeatureFlags(updatedFeatureFlags)) dispatch(actions.updateFeatureFlags(updatedFeatureFlags))
dispatch(actions.showModal({ name: notificationType })) notificationType && dispatch(actions.showModal({ name: notificationType }))
resolve(updatedFeatureFlags) resolve(updatedFeatureFlags)
}) })
}) })
@ -1698,3 +1758,27 @@ function setUseBlockie (val) {
}) })
} }
} }
function setNetworkEndpoints (networkEndpointType) {
return dispatch => {
log.debug('background.setNetworkEndpoints')
return new Promise((resolve, reject) => {
background.setNetworkEndpoints(networkEndpointType, err => {
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(actions.updateNetworkEndpointType(networkEndpointType))
resolve(networkEndpointType)
})
})
}
}
function updateNetworkEndpointType (networkEndpointType) {
return {
type: actions.UPDATE_NETWORK_ENDPOINT_TYPE,
value: networkEndpointType,
}
}

@ -5,6 +5,8 @@ const { Switch, Redirect, withRouter } = require('react-router-dom')
const { compose } = require('recompose') const { compose } = require('recompose')
const h = require('react-hyperscript') const h = require('react-hyperscript')
const actions = require('./actions') const actions = require('./actions')
const classnames = require('classnames')
// mascara // mascara
const MascaraCreatePassword = require('../../mascara/src/app/first-time/create-password-screen').default const MascaraCreatePassword = require('../../mascara/src/app/first-time/create-password-screen').default
const MascaraBuyEtherScreen = require('../../mascara/src/app/first-time/buy-ether-screen').default const MascaraBuyEtherScreen = require('../../mascara/src/app/first-time/buy-ether-screen').default
@ -234,22 +236,22 @@ class App extends Component {
showNetworkDropdown, showNetworkDropdown,
hideNetworkDropdown, hideNetworkDropdown,
currentView, currentView,
isMascara,
isOnboarding,
history,
} = this.props } = this.props
if (window.METAMASK_UI_TYPE === 'notification') { if (window.METAMASK_UI_TYPE === 'notification') {
return null return null
} }
const props = this.props
const {isMascara, isOnboarding} = props
// Do not render header if user is in mascara onboarding // Do not render header if user is in mascara onboarding
if (isMascara && isOnboarding) { if (isMascara && isOnboarding) {
return null return null
} }
// Do not render header if user is in mascara buy ether // Do not render header if user is in mascara buy ether
if (isMascara && currentView.name === 'buyEth') { if (isMascara && props.currentView.name === 'buyEth') {
return null return null
} }
@ -260,7 +262,9 @@ class App extends Component {
}, [ }, [
h('.app-header.flex-row.flex-space-between', { h('.app-header.flex-row.flex-space-between', {
style: {}, className: classnames({
'app-header--initialized': !isOnboarding,
}),
}, [ }, [
h('div.app-header-contents', {}, [ h('div.app-header-contents', {}, [
h('div.left-menu-wrapper', { h('div.left-menu-wrapper', {
@ -268,19 +272,13 @@ class App extends Component {
}, [ }, [
// mini logo // mini logo
h('img.metafox-icon', { h('img.metafox-icon', {
height: 29, height: 42,
width: 29, width: 42,
src: '/images/icon-128.png', src: '/images/metamask-fox.svg',
}), }),
// metamask name // metamask name
h('h1', { h('h1', 'MetaMask'),
style: {
position: 'relative',
paddingLeft: '9px',
color: '#5B5D67',
},
}, 'MetaMask'),
]), ]),
@ -313,6 +311,7 @@ class App extends Component {
]), ]),
]), ]),
]), ]),
]) ])
) )
} }

@ -33,15 +33,28 @@ function mapDispatchToProps (dispatch) {
toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()), toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()),
showAccountDetail: address => { showAccountDetail: address => {
dispatch(actions.showAccountDetail(address)) dispatch(actions.showAccountDetail(address))
dispatch(actions.hideSidebar())
dispatch(actions.toggleAccountMenu()) dispatch(actions.toggleAccountMenu())
}, },
lockMetamask: () => { lockMetamask: () => {
dispatch(actions.lockMetamask()) dispatch(actions.lockMetamask())
dispatch(actions.displayWarning(null)) dispatch(actions.hideWarning())
dispatch(actions.hideSidebar())
dispatch(actions.toggleAccountMenu()) dispatch(actions.toggleAccountMenu())
}, },
showNewAccountModal: () => { showConfigPage: () => {
dispatch(actions.showModal({ name: 'NEW_ACCOUNT' })) dispatch(actions.showConfigPage())
dispatch(actions.hideSidebar())
dispatch(actions.toggleAccountMenu())
},
showNewAccountPage: (formToSelect) => {
dispatch(actions.showNewAccountPage(formToSelect))
dispatch(actions.hideSidebar())
dispatch(actions.toggleAccountMenu())
},
showInfoPage: () => {
dispatch(actions.showInfoPage())
dispatch(actions.hideSidebar())
dispatch(actions.toggleAccountMenu()) dispatch(actions.toggleAccountMenu())
}, },
} }
@ -51,7 +64,7 @@ AccountMenu.prototype.render = function () {
const { const {
isAccountMenuOpen, isAccountMenuOpen,
toggleAccountMenu, toggleAccountMenu,
showNewAccountModal, showNewAccountPage,
lockMetamask, lockMetamask,
history, history,
} = this.props } = this.props
@ -73,15 +86,12 @@ AccountMenu.prototype.render = function () {
h('div.account-menu__accounts', this.renderAccounts()), h('div.account-menu__accounts', this.renderAccounts()),
h(Divider), h(Divider),
h(Item, { h(Item, {
onClick: showNewAccountModal, onClick: () => showNewAccountPage('CREATE'),
icon: h('img', { src: 'images/plus-btn-white.svg' }), icon: h('img', { src: 'images/plus-btn-white.svg' }),
text: 'Create Account', text: 'Create Account',
}), }),
h(Item, { h(Item, {
onClick: () => { onClick: () => showNewAccountPage('IMPORT'),
toggleAccountMenu()
history.push(IMPORT_ACCOUNT_ROUTE)
},
icon: h('img', { src: 'images/import-account.svg' }), icon: h('img', { src: 'images/import-account.svg' }),
text: 'Import Account', text: 'Import Account',
}), }),

@ -40,7 +40,7 @@ BalanceComponent.prototype.render = function () {
// style: {}, // style: {},
// }), // }),
h(Identicon, { h(Identicon, {
diameter: 45, diameter: 50,
address: token && token.address, address: token && token.address,
network, network,
}), }),
@ -94,7 +94,8 @@ BalanceComponent.prototype.renderFiatValue = function (formattedBalance) {
} }
BalanceComponent.prototype.renderFiatAmount = function (fiatDisplayNumber, fiatSuffix, fiatPrefix) { BalanceComponent.prototype.renderFiatAmount = function (fiatDisplayNumber, fiatSuffix, fiatPrefix) {
if (fiatDisplayNumber === 'N/A') return null const shouldNotRenderFiat = fiatDisplayNumber === 'N/A' || Number(fiatDisplayNumber) === 0
if (shouldNotRenderFiat) return null
return h('div.fiat-amount', { return h('div.fiat-amount', {
style: {}, style: {},

@ -40,7 +40,7 @@ CoinbaseForm.prototype.render = function () {
}, 'Continue to Coinbase'), }, 'Continue to Coinbase'),
h('button.btn-red', { h('button.btn-red', {
onClick: () => props.dispatch(actions.backTobuyView(props.accounts.address)), onClick: () => props.dispatch(actions.goHome()),
}, 'Cancel'), }, 'Cancel'),
]), ]),
]) ])

@ -50,10 +50,18 @@ function sanitizeValue (value) {
CurrencyInput.prototype.handleChange = function (newValue) { CurrencyInput.prototype.handleChange = function (newValue) {
const { onInputChange } = this.props const { onInputChange } = this.props
const { value } = this.state
this.setState({ value: sanitizeValue(newValue) }) let parsedValue = newValue
const newValueLastIndex = newValue.length - 1
onInputChange(sanitizeValue(newValue)) if (value === '0' && newValue[newValueLastIndex] === '0') {
parsedValue = parsedValue.slice(0, newValueLastIndex)
}
const sanitizedValue = sanitizeValue(parsedValue)
this.setState({ value: sanitizedValue })
onInputChange(sanitizedValue)
} }
// If state.value === props.value plus a decimal point, or at least one // If state.value === props.value plus a decimal point, or at least one

@ -199,7 +199,7 @@ class AccountDropdowns extends Component {
{}, {},
menuItemStyles, menuItemStyles,
), ),
onClick: () => actions.showNewAccountModal(), onClick: () => actions.showNewAccountPageCreateForm(),
}, },
[ [
h( h(
@ -228,7 +228,7 @@ class AccountDropdowns extends Component {
actions.hideSidebar() actions.hideSidebar()
} }
}, },
onClick: () => actions.showImportPage(), onClick: () => actions.showNewAccountPageImportForm(),
style: Object.assign( style: Object.assign(
{}, {},
menuItemStyles, menuItemStyles,
@ -457,9 +457,7 @@ const mapDispatchToProps = (dispatch) => {
identity, identity,
})) }))
}, },
showNewAccountModal: () => { showNewAccountPageCreateForm: () => dispatch(actions.showNewAccountPage({ form: 'CREATE' })),
dispatch(actions.showModal({ name: 'NEW_ACCOUNT' }))
},
showExportPrivateKeyModal: () => { showExportPrivateKeyModal: () => {
dispatch(actions.showModal({ name: 'EXPORT_PRIVATE_KEY' })) dispatch(actions.showModal({ name: 'EXPORT_PRIVATE_KEY' }))
}, },
@ -467,7 +465,7 @@ const mapDispatchToProps = (dispatch) => {
dispatch(actions.showAddTokenPage()) dispatch(actions.showAddTokenPage())
}, },
addNewAccount: () => dispatch(actions.addNewAccount()), addNewAccount: () => dispatch(actions.addNewAccount()),
showImportPage: () => dispatch(actions.showImportPage()), showNewAccountPageImportForm: () => dispatch(actions.showNewAccountPage({ form: 'IMPORT' })),
showQrView: (selected, identity) => dispatch(actions.showQrView(selected, identity)), showQrView: (selected, identity) => dispatch(actions.showQrView(selected, identity)),
}, },
} }

@ -7,13 +7,13 @@ const debounce = require('debounce')
module.exports = Mascot module.exports = Mascot
inherits(Mascot, Component) inherits(Mascot, Component)
function Mascot () { function Mascot ({width = '200', height = '200'}) {
Component.call(this) Component.call(this)
this.logo = metamaskLogo({ this.logo = metamaskLogo({
followMouse: true, followMouse: true,
pxNotRatio: true, pxNotRatio: true,
width: 200, width,
height: 200, height,
}) })
this.refollowMouse = debounce(this.logo.setFollowMouse.bind(this.logo, true), 1000) this.refollowMouse = debounce(this.logo.setFollowMouse.bind(this.logo, true), 1000)

@ -62,12 +62,12 @@ AccountDetailsModal.prototype.render = function () {
h('div.account-modal-divider'), h('div.account-modal-divider'),
h('button.btn-clear', { h('button.btn-clear.account-modal__button', {
onClick: () => global.platform.openWindow({ url: genAccountLink(address, network) }), onClick: () => global.platform.openWindow({ url: genAccountLink(address, network) }),
}, 'View account on Etherscan'), }, 'View account on Etherscan'),
// Holding on redesign for Export Private Key functionality // Holding on redesign for Export Private Key functionality
h('button.btn-clear', { h('button.btn-clear.account-modal__button', {
onClick: () => showExportPrivateKeyModal(), onClick: () => showExportPrivateKeyModal(),
}, 'Export private key'), }, 'Export private key'),

@ -69,7 +69,7 @@ BuyOptions.prototype.render = function () {
// h('div.buy-modal-content-option', {}, [ // h('div.buy-modal-content-option', {}, [
// h('div.buy-modal-content-option-title', {}, 'Shapeshift'), // h('div.buy-modal-content-option-title', {}, 'Shapeshift'),
// h('div.buy-modal-content-option-subtitle', {}, 'Trade any digital asset for any other'), // h('div.buy-modal-content-option-subtitle', {}, 'Trade any digital asset for any other'),
// ]), // ]),,
this.renderModalContentOption( this.renderModalContentOption(
'Direct Deposit', 'Direct Deposit',

@ -0,0 +1,184 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const actions = require('../../actions')
const networkNames = require('../../../../app/scripts/config.js').networkNames
const ShapeshiftForm = require('../shapeshift-form')
const DIRECT_DEPOSIT_ROW_TITLE = 'Directly Deposit Ether'
const DIRECT_DEPOSIT_ROW_TEXT = `If you already have some Ether, the quickest way to get Ether in
your new wallet by direct deposit.`
const COINBASE_ROW_TITLE = 'Buy on Coinbase'
const COINBASE_ROW_TEXT = `Coinbase is the world’s most popular way to buy and sell bitcoin,
ethereum, and litecoin.`
const SHAPESHIFT_ROW_TITLE = 'Deposit with ShapeShift'
const SHAPESHIFT_ROW_TEXT = `If you own other cryptocurrencies, you can trade and deposit Ether
directly into your MetaMask wallet. No Account Needed.`
const FAUCET_ROW_TITLE = 'Test Faucet'
const facuetRowText = networkName => `Get Ether from a faucet for the ${networkName}`
function mapStateToProps (state) {
return {
network: state.metamask.network,
address: state.metamask.selectedAddress,
}
}
function mapDispatchToProps (dispatch) {
return {
toCoinbase: (address) => {
dispatch(actions.buyEth({ network: '1', address, amount: 0 }))
},
hideModal: () => {
dispatch(actions.hideModal())
},
showAccountDetailModal: () => {
dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' }))
},
toFaucet: network => dispatch(actions.buyEth({ network })),
}
}
inherits(DepositEtherModal, Component)
function DepositEtherModal () {
Component.call(this)
this.state = {
buyingWithShapeshift: false,
}
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(DepositEtherModal)
DepositEtherModal.prototype.renderRow = function ({
logo,
title,
text,
buttonLabel,
onButtonClick,
hide,
className,
hideButton,
hideTitle,
onBackClick,
showBackButton,
}) {
if (hide) {
return null
}
return h('div', {
className: className || 'deposit-ether-modal__buy-row',
}, [
onBackClick && showBackButton && h('div.deposit-ether-modal__buy-row__back', {
onClick: onBackClick,
}, [
h('i.fa.fa-arrow-left.cursor-pointer'),
]),
h('div.deposit-ether-modal__buy-row__logo', [logo]),
h('div.deposit-ether-modal__buy-row__description', [
!hideTitle && h('div.deposit-ether-modal__buy-row__description__title', [title]),
h('div.deposit-ether-modal__buy-row__description__text', [text]),
]),
!hideButton && h('div.deposit-ether-modal__buy-row__button', [
h('button.deposit-ether-modal__deposit-button', {
onClick: onButtonClick,
}, [buttonLabel]),
]),
])
}
DepositEtherModal.prototype.render = function () {
const { network, toCoinbase, address, toFaucet } = this.props
const { buyingWithShapeshift } = this.state
const isTestNetwork = ['3', '4', '42'].find(n => n === network)
const networkName = networkNames[network]
return h('div.deposit-ether-modal', {}, [
h('div.deposit-ether-modal__header', [
h('div.deposit-ether-modal__header__title', ['Deposit Ether']),
h('div.deposit-ether-modal__header__description', [
'To interact with decentralized applications using MetaMask, you’ll need Ether in your wallet.',
]),
h('div.deposit-ether-modal__header__close', {
onClick: () => {
this.setState({ buyingWithShapeshift: false })
this.props.hideModal()
},
}),
]),
h('div.deposit-ether-modal__buy-rows', [
this.renderRow({
logo: h('img.deposit-ether-modal__buy-row__eth-logo', { src: '../../../images/eth_logo.svg' }),
title: DIRECT_DEPOSIT_ROW_TITLE,
text: DIRECT_DEPOSIT_ROW_TEXT,
buttonLabel: 'View Account',
onButtonClick: () => this.goToAccountDetailsModal(),
hide: buyingWithShapeshift,
}),
this.renderRow({
logo: h('i.fa.fa-tint.fa-2x'),
title: FAUCET_ROW_TITLE,
text: facuetRowText(networkName),
buttonLabel: 'Get Ether',
onButtonClick: () => toFaucet(network),
hide: !isTestNetwork || buyingWithShapeshift,
}),
this.renderRow({
logo: h('img.deposit-ether-modal__buy-row__coinbase-logo', {
src: '../../../images/coinbase logo.png',
}),
title: COINBASE_ROW_TITLE,
text: COINBASE_ROW_TEXT,
buttonLabel: 'Continue to Coinbase',
onButtonClick: () => toCoinbase(address),
hide: isTestNetwork || buyingWithShapeshift,
}),
this.renderRow({
logo: h('img.deposit-ether-modal__buy-row__shapeshift-logo', {
src: '../../../images/shapeshift logo.png',
}),
title: SHAPESHIFT_ROW_TITLE,
text: SHAPESHIFT_ROW_TEXT,
buttonLabel: 'Buy with Shapeshift',
onButtonClick: () => this.setState({ buyingWithShapeshift: true }),
hide: isTestNetwork,
hideButton: buyingWithShapeshift,
hideTitle: buyingWithShapeshift,
onBackClick: () => this.setState({ buyingWithShapeshift: false }),
showBackButton: this.state.buyingWithShapeshift,
className: buyingWithShapeshift && 'deposit-ether-modal__buy-row__shapeshift-buy',
}),
buyingWithShapeshift && h(ShapeshiftForm),
]),
])
}
DepositEtherModal.prototype.goToAccountDetailsModal = function () {
this.props.hideModal()
this.props.showAccountDetailModal()
}

@ -79,11 +79,15 @@ ExportPrivateKeyModal.prototype.renderButton = function (className, onClick, lab
ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, password, address, hideModal) { ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, password, address, hideModal) {
return h('div.export-private-key-buttons', {}, [ return h('div.export-private-key-buttons', {}, [
!privateKey && this.renderButton('btn-clear btn-cancel', () => hideModal(), 'Cancel'), !privateKey && this.renderButton(
'btn-cancel export-private-key__button export-private-key__button--cancel',
() => hideModal(),
'Cancel'
),
(privateKey (privateKey
? this.renderButton('btn-clear', () => hideModal(), 'Done') ? this.renderButton('btn-clear export-private-key__button', () => hideModal(), 'Done')
: this.renderButton('btn-clear', () => this.exportAccountAndGetPrivateKey(this.state.password, address), 'Show') : this.renderButton('btn-clear export-private-key__button', () => this.exportAccountAndGetPrivateKey(this.state.password, address), 'Confirm')
), ),
]) ])

@ -58,12 +58,12 @@ HideTokenConfirmationModal.prototype.render = function () {
]), ]),
h('div.hide-token-confirmation__buttons', {}, [ h('div.hide-token-confirmation__buttons', {}, [
h('button.btn-clear', { h('button.btn-cancel.hide-token-confirmation__button', {
onClick: () => hideModal(), onClick: () => hideModal(),
}, [ }, [
'CANCEL', 'CANCEL',
]), ]),
h('button.btn-clear', { h('button.btn-clear.hide-token-confirmation__button', {
onClick: () => hideToken(address), onClick: () => hideToken(address),
}, [ }, [
'HIDE', 'HIDE',

@ -9,6 +9,7 @@ const isPopupOrNotification = require('../../../../app/scripts/lib/is-popup-or-n
// Modal Components // Modal Components
const BuyOptions = require('./buy-options-modal') const BuyOptions = require('./buy-options-modal')
const DepositEtherModal = require('./deposit-ether-modal')
const AccountDetailsModal = require('./account-details-modal') const AccountDetailsModal = require('./account-details-modal')
const EditAccountNameModal = require('./edit-account-name-modal') const EditAccountNameModal = require('./edit-account-name-modal')
const ExportPrivateKeyModal = require('./export-private-key-modal') const ExportPrivateKeyModal = require('./export-private-key-modal')
@ -73,6 +74,37 @@ const MODALS = {
}, },
}, },
DEPOSIT_ETHER: {
contents: [
h(DepositEtherModal, {}, []),
],
mobileModalStyle: {
width: '100%',
height: '100%',
transform: 'none',
left: '0',
right: '0',
margin: '0 auto',
boxShadow: '0 0 7px 0 rgba(0,0,0,0.08)',
top: '0',
display: 'flex',
},
laptopModalStyle: {
width: '900px',
maxWidth: '900px',
top: 'calc(10% + 10px)',
left: '0',
right: '0',
margin: '0 auto',
boxShadow: '0 0 6px 0 rgba(0,0,0,0.3)',
borderRadius: '8px',
transform: 'none',
},
contentStyle: {
borderRadius: '8px',
},
},
EDIT_ACCOUNT_NAME: { EDIT_ACCOUNT_NAME: {
contents: [ contents: [
h(EditAccountNameModal, {}, []), h(EditAccountNameModal, {}, []),

@ -39,7 +39,6 @@ Network.prototype.render = function () {
}, },
src: 'images/loading.svg', src: 'images/loading.svg',
}), }),
h('i.fa.fa-caret-down.network-caret'),
]) ])
} else if (providerName === 'mainnet') { } else if (providerName === 'mainnet') {
hoverText = 'Main Ethereum Network' hoverText = 'Main Ethereum Network'
@ -85,12 +84,8 @@ Network.prototype.render = function () {
backgroundColor: '#038789', // $blue-lagoon backgroundColor: '#038789', // $blue-lagoon
nonSelectBackgroundColor: '#15afb2', nonSelectBackgroundColor: '#15afb2',
}), }),
h('.network-name', { h('.network-name', 'Main Network'),
style: { h('i.fa.fa-chevron-down.fa-lg.network-caret'),
color: '#039396',
}},
'Main Network'),
h('i.fa.fa-caret-down.fa-lg.network-caret'),
]) ])
case 'ropsten-test-network': case 'ropsten-test-network':
return h('.network-indicator', [ return h('.network-indicator', [
@ -98,12 +93,8 @@ Network.prototype.render = function () {
backgroundColor: '#e91550', // $crimson backgroundColor: '#e91550', // $crimson
nonSelectBackgroundColor: '#ec2c50', nonSelectBackgroundColor: '#ec2c50',
}), }),
h('.network-name', { h('.network-name', 'Ropsten Test Net'),
style: { h('i.fa.fa-chevron-down.fa-lg.network-caret'),
color: '#ff6666',
}},
'Ropsten Test Net'),
h('i.fa.fa-caret-down.fa-lg.network-caret'),
]) ])
case 'kovan-test-network': case 'kovan-test-network':
return h('.network-indicator', [ return h('.network-indicator', [
@ -111,12 +102,8 @@ Network.prototype.render = function () {
backgroundColor: '#690496', // $purple backgroundColor: '#690496', // $purple
nonSelectBackgroundColor: '#b039f3', nonSelectBackgroundColor: '#b039f3',
}), }),
h('.network-name', { h('.network-name', 'Kovan Test Net'),
style: { h('i.fa.fa-chevron-down.fa-lg.network-caret'),
color: '#690496',
}},
'Kovan Test Net'),
h('i.fa.fa-caret-down.fa-lg.network-caret'),
]) ])
case 'rinkeby-test-network': case 'rinkeby-test-network':
return h('.network-indicator', [ return h('.network-indicator', [
@ -124,12 +111,8 @@ Network.prototype.render = function () {
backgroundColor: '#ebb33f', // $tulip-tree backgroundColor: '#ebb33f', // $tulip-tree
nonSelectBackgroundColor: '#ecb23e', nonSelectBackgroundColor: '#ecb23e',
}), }),
h('.network-name', { h('.network-name', 'Rinkeby Test Net'),
style: { h('i.fa.fa-chevron-down.fa-lg.network-caret'),
color: '#e7a218',
}},
'Rinkeby Test Net'),
h('i.fa.fa-caret-down.fa-lg.network-caret'),
]) ])
default: default:
return h('.network-indicator', [ return h('.network-indicator', [
@ -140,12 +123,8 @@ Network.prototype.render = function () {
}, },
}), }),
h('.network-name', { h('.network-name', 'Private Network'),
style: { h('i.fa.fa-chevron-down.fa-lg.network-caret'),
color: '#AEAEAE',
}},
'Private Network'),
h('i.fa.fa-caret-down.fa-lg.network-caret'),
]) ])
} }
})(), })(),

@ -3,6 +3,7 @@ const Component = require('react').Component
const classnames = require('classnames') const classnames = require('classnames')
const h = require('react-hyperscript') const h = require('react-hyperscript')
const connect = require('react-redux').connect const connect = require('react-redux').connect
const R = require('ramda')
const Fuse = require('fuse.js') const Fuse = require('fuse.js')
const contractMap = require('eth-contract-metadata') const contractMap = require('eth-contract-metadata')
const TokenBalance = require('../../components/token-balance') const TokenBalance = require('../../components/token-balance')
@ -17,7 +18,10 @@ const fuse = new Fuse(contractList, {
distance: 100, distance: 100,
maxPatternLength: 32, maxPatternLength: 32,
minMatchCharLength: 1, minMatchCharLength: 1,
keys: ['address', 'name', 'symbol'], keys: [
{ name: 'name', weight: 0.5 },
{ name: 'symbol', weight: 0.5 },
],
}) })
// const actions = require('./actions') // const actions = require('./actions')
const actions = require('../../actions') const actions = require('../../actions')
@ -219,9 +223,11 @@ AddTokenScreen.prototype.renderCustomForm = function () {
AddTokenScreen.prototype.renderTokenList = function () { AddTokenScreen.prototype.renderTokenList = function () {
const { searchQuery = '', selectedTokens } = this.state const { searchQuery = '', selectedTokens } = this.state
const results = searchQuery const fuseSearchResult = fuse.search(searchQuery)
? fuse.search(searchQuery) || [] const addressSearchResult = contractList.filter(token => {
: contractList return token.address.toLowerCase() === searchQuery.toLowerCase()
})
const results = [...addressSearchResult, ...fuseSearchResult]
return Array(6).fill(undefined) return Array(6).fill(undefined)
.map((_, i) => { .map((_, i) => {
@ -297,12 +303,12 @@ AddTokenScreen.prototype.renderConfirmation = function () {
]), ]),
]), ]),
h('div.add-token__buttons', [ h('div.add-token__buttons', [
h('button.btn-secondary', { h('button.btn-cancel.add-token__button', {
onClick: () => addTokens(tokens).then(() => history.push(DEFAULT_ROUTE)),
}, 'Add Tokens'),
h('button.btn-tertiary', {
onClick: () => this.setState({ isShowingConfirmation: false }), onClick: () => this.setState({ isShowingConfirmation: false }),
}, 'Back'), }, 'Back'),
h('button.btn-clear.add-token__button', {
onClick: () => addTokens(tokens).then(() => history.push(DEFAULT_ROUTE)),
}, 'Add Tokens'),
]), ]),
]) ])
) )
@ -347,12 +353,12 @@ AddTokenScreen.prototype.render = function () {
]), ]),
]), ]),
h('div.add-token__buttons', [ h('div.add-token__buttons', [
h('button.btn-secondary', { h('button.btn-cancel.add-token__button', {
onClick: this.onNext,
}, 'Next'),
h('button.btn-tertiary', {
onClick: () => history.goBack(), onClick: () => history.goBack(),
}, 'Cancel'), }, 'Cancel'),
h('button.btn-clear.add-token__button', {
onClick: this.onNext,
}, 'Next'),
]), ]),
]) ])
) )

@ -24,14 +24,7 @@ JsonImportSubview.prototype.render = function () {
const { error } = this.props const { error } = this.props
return ( return (
h('div', { h('div.new-account-import-form__json', [
style: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: '5px 15px 0px 15px',
},
}, [
h('p', 'Used by a variety of different clients'), h('p', 'Used by a variety of different clients'),
h('a.warning', { href: HELP_LINK, target: '_blank' }, 'File import not working? Click here!'), h('a.warning', { href: HELP_LINK, target: '_blank' }, 'File import not working? Click here!'),
@ -40,28 +33,35 @@ JsonImportSubview.prototype.render = function () {
readAs: 'text', readAs: 'text',
onLoad: this.onLoad.bind(this), onLoad: this.onLoad.bind(this),
style: { style: {
margin: '20px 0px 12px 20px', margin: '20px 0px 12px 34%',
fontSize: '15px', fontSize: '15px',
display: 'flex',
justifyContent: 'center',
}, },
}), }),
h('input.large-input.letter-spacey', { h('input.new-account-import-form__input-password', {
type: 'password', type: 'password',
placeholder: 'Enter password', placeholder: 'Enter password',
id: 'json-password-box', id: 'json-password-box',
onKeyPress: this.createKeyringOnEnter.bind(this), onKeyPress: this.createKeyringOnEnter.bind(this),
style: {
width: 260,
marginTop: 12,
},
}), }),
h('button.primary', { h('div.new-account-create-form__buttons', {}, [
onClick: this.createNewKeychain.bind(this),
style: { h('button.new-account-create-form__button-cancel', {
margin: 12, onClick: () => this.props.goHome(),
}, }, [
}, 'Import'), 'CANCEL',
]),
h('button.new-account-create-form__button-create', {
onClick: () => this.createNewKeychain.bind(this),
}, [
'IMPORT',
]),
]),
error ? h('span.error', error) : null, error ? h('span.error', error) : null,
]) ])

@ -4,7 +4,7 @@ const h = require('react-hyperscript')
const connect = require('react-redux').connect const connect = require('react-redux').connect
const actions = require('../../../actions') const actions = require('../../../actions')
module.exports = connect(mapStateToProps)(PrivateKeyImportView) module.exports = connect(mapStateToProps, mapDispatchToProps)(PrivateKeyImportView)
function mapStateToProps (state) { function mapStateToProps (state) {
return { return {
@ -12,45 +12,49 @@ function mapStateToProps (state) {
} }
} }
function mapDispatchToProps (dispatch) {
return {
goHome: () => dispatch(actions.goHome()),
importNewAccount: (strategy, [ privateKey ]) => {
dispatch(actions.importNewAccount(strategy, [ privateKey ]))
},
displayWarning: () => dispatch(actions.displayWarning(null)),
}
}
inherits(PrivateKeyImportView, Component) inherits(PrivateKeyImportView, Component)
function PrivateKeyImportView () { function PrivateKeyImportView () {
Component.call(this) Component.call(this)
} }
PrivateKeyImportView.prototype.componentWillUnmount = function () {
this.props.dispatch(actions.displayWarning(null))
}
PrivateKeyImportView.prototype.render = function () { PrivateKeyImportView.prototype.render = function () {
const { error } = this.props const { error, goHome } = this.props
return ( return (
h('div', { h('div.new-account-import-form__private-key', [
style: { h('span.new-account-create-form__instruction', 'Paste your private key string here:'),
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: '5px 15px 0px 15px',
},
}, [
h('span', 'Paste your private key string here'),
h('input.large-input.letter-spacey', { h('input.new-account-import-form__input-password', {
type: 'password', type: 'password',
id: 'private-key-box', id: 'private-key-box',
onKeyPress: this.createKeyringOnEnter.bind(this), onKeyPress: () => this.createKeyringOnEnter(),
style: {
width: 260,
marginTop: 12,
},
}), }),
h('button.primary', { h('div.new-account-create-form__buttons', {}, [
onClick: this.createNewKeychain.bind(this),
style: { h('button.new-account-create-form__button-cancel', {
margin: 12, onClick: () => goHome(),
}, }, [
}, 'Import'), 'CANCEL',
]),
h('button.new-account-create-form__button-create', {
onClick: () => this.createNewKeychain(),
}, [
'IMPORT',
]),
]),
error ? h('span.error', error) : null, error ? h('span.error', error) : null,
]) ])
@ -67,5 +71,6 @@ PrivateKeyImportView.prototype.createKeyringOnEnter = function (event) {
PrivateKeyImportView.prototype.createNewKeychain = function () { PrivateKeyImportView.prototype.createNewKeychain = function () {
const input = document.getElementById('private-key-box') const input = document.getElementById('private-key-box')
const privateKey = input.value const privateKey = input.value
this.props.dispatch(actions.importNewAccount('Private Key', [ privateKey ]))
this.props.importNewAccount('Private Key', [ privateKey ])
} }

@ -52,7 +52,10 @@ class RestoreVaultPage extends PersistentForm {
// submit // submit
this.props.createNewVaultAndRestore(password, seed) this.props.createNewVaultAndRestore(password, seed)
.then(() => history.push(DEFAULT_ROUTE)) .then(() => history.push(DEFAULT_ROUTE))
.catch(({ message }) => this.setState({ error: message })) .catch(({ message }) => {
this.setState({ error: message })
log.error(message)
})
} }
render () { render () {

@ -11,6 +11,7 @@ const { exportAsFile } = require('../../../util')
const SimpleDropdown = require('../../dropdowns/simple-dropdown') const SimpleDropdown = require('../../dropdowns/simple-dropdown')
const ToggleButton = require('react-toggle-button') const ToggleButton = require('react-toggle-button')
const { REVEAL_SEED_ROUTE } = require('../../../routes') const { REVEAL_SEED_ROUTE } = require('../../../routes')
const { OLD_UI_NETWORK_TYPE } = require('../../app/scripts/config').enums
const getInfuraCurrencyOptions = () => { const getInfuraCurrencyOptions = () => {
const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => { const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => {
@ -230,18 +231,18 @@ class Settings extends Component {
} }
render () { render () {
const { warning } = this.props const { warning, isMascara } = this.props
return ( return (
h('div.settings__content', [ h('div.settings__content', [
warning && h('div.settings__error', warning), warning && h('div.settings__error', warning),
this.renderBlockieOptIn(),
this.renderCurrentConversion(), this.renderCurrentConversion(),
// this.renderCurrentProvider(), // this.renderCurrentProvider(),
this.renderNewRpcUrl(), this.renderNewRpcUrl(),
this.renderStateLogs(), this.renderStateLogs(),
this.renderSeedWords(), this.renderSeedWords(),
this.renderOldUI(), !isMascara && this.renderOldUI(),
this.renderBlockieOptIn(),
]) ])
) )
} }
@ -257,12 +258,14 @@ Settings.propTypes = {
setFeatureFlagToBeta: PropTypes.func, setFeatureFlagToBeta: PropTypes.func,
warning: PropTypes.string, warning: PropTypes.string,
history: PropTypes.object, history: PropTypes.object,
isMascara: PropTypes.bool,
} }
const mapStateToProps = state => { const mapStateToProps = state => {
return { return {
metamask: state.metamask, metamask: state.metamask,
warning: state.appState.warning, warning: state.appState.warning,
isMascara: state.metamask.isMascara,
} }
} }
@ -273,7 +276,10 @@ const mapDispatchToProps = dispatch => {
displayWarning: warning => dispatch(actions.displayWarning(warning)), displayWarning: warning => dispatch(actions.displayWarning(warning)),
revealSeedConfirmation: () => dispatch(actions.revealSeedConfirmation()), revealSeedConfirmation: () => dispatch(actions.revealSeedConfirmation()),
setUseBlockie: value => dispatch(actions.setUseBlockie(value)), setUseBlockie: value => dispatch(actions.setUseBlockie(value)),
setFeatureFlagToBeta: () => dispatch(actions.setFeatureFlag('betaUI', false)), setFeatureFlagToBeta: () => {
return dispatch(actions.setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL'))
.then(() => dispatch(actions.setNetworkEndpoints(OLD_UI_NETWORK_TYPE)))
},
} }
} }

@ -231,8 +231,8 @@ ConfirmSendEther.prototype.render = function () {
// Main Send token Card // Main Send token Card
h('div.confirm-screen-wrapper.flex-column.flex-grow', [ h('div.confirm-screen-wrapper.flex-column.flex-grow', [
h('h3.flex-center.confirm-screen-header', [ h('h3.flex-center.confirm-screen-header', [
h('button.confirm-screen-back-button', { h('button.btn-clear.confirm-screen-back-button', {
onClick: () => this.editTransaction(txMeta), onClick: () => editTransaction(txMeta),
}, 'EDIT'), }, 'EDIT'),
h('div.confirm-screen-title', 'Confirm Transaction'), h('div.confirm-screen-title', 'Confirm Transaction'),
h('div.confirm-screen-header-tip'), h('div.confirm-screen-header-tip'),
@ -433,7 +433,9 @@ ConfirmSendEther.prototype.onSubmit = function (event) {
ConfirmSendEther.prototype.cancel = function (event, txMeta) { ConfirmSendEther.prototype.cancel = function (event, txMeta) {
event.preventDefault() event.preventDefault()
this.props.cancelTransaction(txMeta) const { cancelTransaction } = this.props
cancelTransaction(txMeta)
.then(() => this.props.history.push(DEFAULT_ROUTE)) .then(() => this.props.history.push(DEFAULT_ROUTE))
} }
@ -458,26 +460,6 @@ ConfirmSendEther.prototype.gatherTxMeta = function () {
const state = this.state const state = this.state
const txData = clone(state.txData) || clone(props.txData) const txData = clone(state.txData) || clone(props.txData)
if (props.send.editingTransactionId) {
const {
send: {
memo,
amount: value,
gasLimit: gas,
gasPrice,
},
} = props
const { txParams: { from, to } } = txData
txData.txParams = {
from: ethUtil.addHexPrefix(from),
to: ethUtil.addHexPrefix(to),
memo: memo && ethUtil.addHexPrefix(memo),
value: ethUtil.addHexPrefix(value),
gas: ethUtil.addHexPrefix(gas),
gasPrice: ethUtil.addHexPrefix(gasPrice),
}
}
// log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`) // log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`)
return txData return txData
} }

@ -4,7 +4,6 @@ const { withRouter } = require('react-router-dom')
const { compose } = require('recompose') const { compose } = require('recompose')
const h = require('react-hyperscript') const h = require('react-hyperscript')
const inherits = require('util').inherits const inherits = require('util').inherits
const ethAbi = require('ethereumjs-abi')
const tokenAbi = require('human-standard-token-abi') const tokenAbi = require('human-standard-token-abi')
const abiDecoder = require('abi-decoder') const abiDecoder = require('abi-decoder')
abiDecoder.addABI(tokenAbi) abiDecoder.addABI(tokenAbi)
@ -305,6 +304,7 @@ ConfirmSendToken.prototype.renderTotalPlusGas = function () {
} }
ConfirmSendToken.prototype.render = function () { ConfirmSendToken.prototype.render = function () {
const { editTransaction } = this.props
const txMeta = this.gatherTxMeta() const txMeta = this.gatherTxMeta()
const { const {
from: { from: {
@ -326,8 +326,8 @@ ConfirmSendToken.prototype.render = function () {
// Main Send token Card // Main Send token Card
h('div.confirm-screen-wrapper.flex-column.flex-grow', [ h('div.confirm-screen-wrapper.flex-column.flex-grow', [
h('h3.flex-center.confirm-screen-header', [ h('h3.flex-center.confirm-screen-header', [
h('button.confirm-screen-back-button', { h('button.btn-clear.confirm-screen-back-button', {
onClick: () => this.editTransaction(txMeta), onClick: () => editTransaction(txMeta),
}, 'EDIT'), }, 'EDIT'),
h('div.confirm-screen-title', 'Confirm Transaction'), h('div.confirm-screen-title', 'Confirm Transaction'),
h('div.confirm-screen-header-tip'), h('div.confirm-screen-header-tip'),
@ -426,7 +426,9 @@ ConfirmSendToken.prototype.onSubmit = function (event) {
ConfirmSendToken.prototype.cancel = function (event, txMeta) { ConfirmSendToken.prototype.cancel = function (event, txMeta) {
event.preventDefault() event.preventDefault()
this.props.cancelTransaction(txMeta) const { cancelTransaction } = this.props
cancelTransaction(txMeta)
.then(() => this.props.history.push(DEFAULT_ROUTE)) .then(() => this.props.history.push(DEFAULT_ROUTE))
} }
@ -451,39 +453,6 @@ ConfirmSendToken.prototype.gatherTxMeta = function () {
const state = this.state const state = this.state
const txData = clone(state.txData) || clone(props.txData) const txData = clone(state.txData) || clone(props.txData)
if (props.send.editingTransactionId) {
const {
send: {
memo,
amount,
gasLimit: gas,
gasPrice,
to,
},
} = props
const { txParams: { from, to: tokenAddress } } = txData
const tokenParams = {
from: ethUtil.addHexPrefix(from),
value: '0',
gas: ethUtil.addHexPrefix(gas),
gasPrice: ethUtil.addHexPrefix(gasPrice),
}
const data = '0xa9059cbb' + Array.prototype.map.call(
ethAbi.rawEncode(['address', 'uint256'], [to, ethUtil.addHexPrefix(amount)]),
x => ('00' + x.toString(16)).slice(-2)
).join('')
txData.txParams = {
...tokenParams,
to: ethUtil.addHexPrefix(tokenAddress),
memo: memo && ethUtil.addHexPrefix(memo),
data,
}
}
// log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`) // log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`)
return txData return txData
} }

@ -32,8 +32,9 @@ GasFeeDisplay.prototype.render = function () {
}) })
: h('div.currency-display', 'Loading...'), : h('div.currency-display', 'Loading...'),
h('div.send-v2__sliders-icon-container', { h('button.send-v2__sliders-icon-container', {
onClick, onClick,
disabled: !gasTotal,
}, [ }, [
h('i.fa.fa-sliders.send-v2__sliders-icon'), h('i.fa.fa-sliders.send-v2__sliders-icon'),
]), ]),

@ -20,6 +20,8 @@ const MIN_GAS_TOTAL = multiplyCurrencies(MIN_GAS_LIMIT_HEX, MIN_GAS_PRICE_HEX, {
multiplierBase: 16, multiplierBase: 16,
}) })
const TOKEN_TRANSFER_FUNCTION_SIGNATURE = '0xa9059cbb'
module.exports = { module.exports = {
MIN_GAS_PRICE_GWEI, MIN_GAS_PRICE_GWEI,
MIN_GAS_PRICE_HEX, MIN_GAS_PRICE_HEX,
@ -27,4 +29,5 @@ module.exports = {
MIN_GAS_LIMIT_HEX, MIN_GAS_LIMIT_HEX,
MIN_GAS_LIMIT_DEC, MIN_GAS_LIMIT_DEC,
MIN_GAS_TOTAL, MIN_GAS_TOTAL,
TOKEN_TRANSFER_FUNCTION_SIGNATURE,
} }

@ -55,6 +55,8 @@ function mapStateToProps (state) {
data, data,
amountConversionRate: selectedToken ? tokenToFiatRate : conversionRate, amountConversionRate: selectedToken ? tokenToFiatRate : conversionRate,
tokenContract: getSelectedTokenContract(state), tokenContract: getSelectedTokenContract(state),
unapprovedTxs: state.metamask.unapprovedTxs,
network: state.metamask.network,
} }
} }
@ -69,6 +71,7 @@ function mapDispatchToProps (dispatch) {
), ),
signTx: txParams => dispatch(actions.signTx(txParams)), signTx: txParams => dispatch(actions.signTx(txParams)),
updateAndApproveTx: txParams => dispatch(actions.updateAndApproveTx(txParams)), updateAndApproveTx: txParams => dispatch(actions.updateAndApproveTx(txParams)),
updateTx: txData => dispatch(actions.updateTransaction(txData)),
setSelectedAddress: address => dispatch(actions.setSelectedAddress(address)), setSelectedAddress: address => dispatch(actions.setSelectedAddress(address)),
addToAddressBook: address => dispatch(actions.addToAddressBook(address)), addToAddressBook: address => dispatch(actions.addToAddressBook(address)),
updateGasTotal: newTotal => dispatch(actions.updateGasTotal(newTotal)), updateGasTotal: newTotal => dispatch(actions.updateGasTotal(newTotal)),
@ -82,7 +85,6 @@ function mapDispatchToProps (dispatch) {
updateSendErrors: newError => dispatch(actions.updateSendErrors(newError)), updateSendErrors: newError => dispatch(actions.updateSendErrors(newError)),
goHome: () => dispatch(actions.goHome()), goHome: () => dispatch(actions.goHome()),
clearSend: () => dispatch(actions.clearSend()), clearSend: () => dispatch(actions.clearSend()),
backToConfirmScreen: editingTransactionId => dispatch(actions.showConfTxPage({ id: editingTransactionId })),
setMaxModeTo: bool => dispatch(actions.setMaxModeTo(bool)), setMaxModeTo: bool => dispatch(actions.setMaxModeTo(bool)),
} }
} }

@ -1,308 +1,242 @@
const PersistentForm = require('../../lib/persistent-form')
const h = require('react-hyperscript') const h = require('react-hyperscript')
const inherits = require('util').inherits const inherits = require('util').inherits
const Component = require('react').Component
const connect = require('react-redux').connect const connect = require('react-redux').connect
const actions = require('../actions') const classnames = require('classnames')
const Qr = require('./qr-code') const { qrcode } = require('qrcode-npm')
const isValidAddress = require('../util').isValidAddress const { shapeShiftSubview, pairUpdate, buyWithShapeShift } = require('../actions')
module.exports = connect(mapStateToProps)(ShapeshiftForm) const { isValidAddress } = require('../util')
const SimpleDropdown = require('./dropdowns/simple-dropdown')
function mapStateToProps (state) { function mapStateToProps (state) {
const {
coinOptions,
tokenExchangeRates,
selectedAddress,
} = state.metamask
return {
coinOptions,
tokenExchangeRates,
selectedAddress,
}
}
function mapDispatchToProps (dispatch) {
return { return {
warning: state.appState.warning, shapeShiftSubview: () => dispatch(shapeShiftSubview()),
isSubLoading: state.appState.isSubLoading, pairUpdate: coin => dispatch(pairUpdate(coin)),
qrRequested: state.appState.qrRequested, buyWithShapeShift: data => dispatch(buyWithShapeShift(data)),
} }
} }
inherits(ShapeshiftForm, PersistentForm) module.exports = connect(mapStateToProps, mapDispatchToProps)(ShapeshiftForm)
inherits(ShapeshiftForm, Component)
function ShapeshiftForm () { function ShapeshiftForm () {
PersistentForm.call(this) Component.call(this)
this.persistentFormParentId = 'shapeshift-buy-form'
this.state = {
depositCoin: 'btc',
refundAddress: '',
showQrCode: false,
depositAddress: '',
errorMessage: '',
isLoading: false,
bought: false,
}
} }
ShapeshiftForm.prototype.render = function () { ShapeshiftForm.prototype.componentWillMount = function () {
return this.props.qrRequested ? h(Qr, {key: 'qr'}) : this.renderMain() this.props.shapeShiftSubview()
} }
ShapeshiftForm.prototype.renderMain = function () { ShapeshiftForm.prototype.onCoinChange = function (e) {
const marketinfo = this.props.buyView.formView.marketinfo const coin = e.target.value
const coinOptions = this.props.buyView.formView.coinOptions this.setState({
var coin = marketinfo.pair.split('_')[0].toUpperCase() depositCoin: coin,
errorMessage: '',
return h('.flex-column', { })
style: { this.props.pairUpdate(coin)
position: 'relative', }
padding: '25px',
paddingTop: '5px',
width: '90%',
minHeight: '215px',
alignItems: 'center',
overflowY: 'auto',
},
}, [
h('.flex-row', {
style: {
justifyContent: 'center',
alignItems: 'baseline',
height: '42px',
},
}, [
h('img', {
src: coinOptions[coin].image,
width: '25px',
height: '25px',
style: {
marginRight: '5px',
},
}),
h('.input-container', {
position: 'relative',
}, [
h('input#fromCoin.buy-inputs.ex-coins', {
type: 'text',
list: 'coinList',
autoFocus: true,
dataset: {
persistentFormId: 'input-coin',
},
style: {
boxSizing: 'border-box',
},
onChange: this.handleLiveInput.bind(this),
defaultValue: 'BTC',
}),
this.renderCoinList(), ShapeshiftForm.prototype.onBuyWithShapeShift = function () {
this.setState({
isLoading: true,
showQrCode: true,
})
h('i.fa.fa-pencil-square-o.edit-text', { const {
style: { buyWithShapeShift,
fontSize: '12px', selectedAddress: withdrawal,
color: '#F7861C', } = this.props
position: 'absolute', const {
}, refundAddress: returnAddress,
}), depositCoin,
]), } = this.state
const pair = `${depositCoin}_eth`
const data = {
withdrawal,
pair,
returnAddress,
// Public api key
'apiKey': '803d1f5df2ed1b1476e4b9e6bcd089e34d8874595dda6a23b67d93c56ea9cc2445e98a6748b219b2b6ad654d9f075f1f1db139abfa93158c04e825db122c14b6',
}
h('.icon-control', { if (isValidAddress(withdrawal)) {
style: { buyWithShapeShift(data)
position: 'relative', .then(d => this.setState({
}, showQrCode: true,
}, [ depositAddress: d.deposit,
// Not visible on the screen, can't see it on master. isLoading: false,
}))
// h('i.fa.fa-refresh.fa-4.orange', { .catch(() => this.setState({
// style: { showQrCode: false,
// bottom: '5px', errorMessage: 'Invalid Request',
// left: '5px', isLoading: false,
// color: '#F7861C', }))
// }, }
// onClick: this.updateCoin.bind(this), }
// }),
h('i.fa.fa-chevron-right.fa-4.orange', {
style: {
position: 'absolute',
bottom: '35%',
left: '0%',
color: '#F7861C',
},
onClick: this.updateCoin.bind(this),
}),
]),
h('#toCoin.ex-coins', marketinfo.pair.split('_')[1].toUpperCase()), ShapeshiftForm.prototype.renderMetadata = function (label, value) {
return h('div', {className: 'shapeshift-form__metadata-wrapper'}, [
h('img', { h('div.shapeshift-form__metadata-label', {}, [
src: coinOptions[marketinfo.pair.split('_')[1].toUpperCase()].image, h('span', `${label}:`),
width: '25px',
height: '25px',
style: {
marginLeft: '5px',
},
}),
]), ]),
h('.flex-column', { h('div.shapeshift-form__metadata-value', {}, [
style: { h('span', value),
marginTop: '1%',
alignItems: 'flex-start',
},
}, [
this.props.warning ?
this.props.warning &&
h('span.error.flex-center', {
style: {
textAlign: 'center',
width: '229px',
height: '82px',
},
}, this.props.warning)
: this.renderInfo(),
this.renderRefundAddressForCoin(coin),
]), ]),
]) ])
} }
ShapeshiftForm.prototype.renderRefundAddressForCoin = function (coin) { ShapeshiftForm.prototype.renderMarketInfo = function () {
return h(this.activeToggle('.input-container'), { const { depositCoin } = this.state
style: { const coinPair = `${depositCoin}_eth`
marginTop: '1%', const { tokenExchangeRates } = this.props
}, const {
}, [ limit,
rate,
h('div', `${coin} Address:`), minimum,
} = tokenExchangeRates[coinPair] || {}
h('input#fromCoinAddress.buy-inputs', {
type: 'text', return h('div.shapeshift-form__metadata', {}, [
placeholder: `Your ${coin} Refund Address`,
dataset: { this.renderMetadata('Status', limit ? 'Available' : 'Unavailable'),
persistentFormId: 'refund-address', this.renderMetadata('Limit', limit),
this.renderMetadata('Exchange Rate', rate),
}, this.renderMetadata('Minimum', minimum),
style: {
boxSizing: 'border-box',
width: '227px',
height: '30px',
padding: ' 5px ',
},
}),
h('i.fa.fa-pencil-square-o.edit-text', {
style: {
fontSize: '12px',
color: '#F7861C',
position: 'absolute',
},
}),
h('div.flex-row', {
style: {
justifyContent: 'flex-start',
},
}, [
h('button', {
onClick: this.shift.bind(this),
style: {
marginTop: '1%',
},
},
'Submit'),
]),
]) ])
} }
ShapeshiftForm.prototype.shift = function () { ShapeshiftForm.prototype.renderQrCode = function () {
var props = this.props const { depositAddress, isLoading } = this.state
var withdrawal = this.props.buyView.buyAddress const qrImage = qrcode(4, 'M')
var returnAddress = document.getElementById('fromCoinAddress').value qrImage.addData(depositAddress)
var pair = this.props.buyView.formView.marketinfo.pair qrImage.make()
var data = {
'withdrawal': withdrawal,
'pair': pair,
'returnAddress': returnAddress,
// Public api key
'apiKey': '803d1f5df2ed1b1476e4b9e6bcd089e34d8874595dda6a23b67d93c56ea9cc2445e98a6748b219b2b6ad654d9f075f1f1db139abfa93158c04e825db122c14b6',
}
var message = [
`Deposit Limit: ${props.buyView.formView.marketinfo.limit}`,
`Deposit Minimum:${props.buyView.formView.marketinfo.minimum}`,
]
if (isValidAddress(withdrawal)) {
this.props.dispatch(actions.coinShiftRquest(data, message))
}
}
ShapeshiftForm.prototype.renderCoinList = function () { return h('div.shapeshift-form', {}, [
var list = Object.keys(this.props.buyView.formView.coinOptions).map((item) => {
return h('option', {
value: item,
}, item)
})
return h('datalist#coinList', { h('div.shapeshift-form__deposit-instruction', [
onClick: (event) => { 'Deposit your BTC to the address below:',
event.preventDefault() ]),
},
}, list)
}
ShapeshiftForm.prototype.updateCoin = function (event) { h('div', depositAddress),
event.preventDefault()
const props = this.props
var coinOptions = this.props.buyView.formView.coinOptions
var coin = document.getElementById('fromCoin').value
if (!coinOptions[coin.toUpperCase()] || coin.toUpperCase() === 'ETH') {
var message = 'Not a valid coin'
return props.dispatch(actions.displayWarning(message))
} else {
return props.dispatch(actions.pairUpdate(coin))
}
}
ShapeshiftForm.prototype.handleLiveInput = function () { h('div.shapeshift-form__qr-code', [
const props = this.props isLoading
var coinOptions = this.props.buyView.formView.coinOptions ? h('img', {
var coin = document.getElementById('fromCoin').value src: 'images/loading.svg',
style: { width: '60px'},
})
: h('div', {
dangerouslySetInnerHTML: { __html: qrImage.createTableTag(4) },
}),
]),
if (!coinOptions[coin.toUpperCase()] || coin.toUpperCase() === 'ETH') { this.renderMarketInfo(),
return null
} else {
return props.dispatch(actions.pairUpdate(coin))
}
}
ShapeshiftForm.prototype.renderInfo = function () {
const marketinfo = this.props.buyView.formView.marketinfo
const coinOptions = this.props.buyView.formView.coinOptions
var coin = marketinfo.pair.split('_')[0].toUpperCase()
return h('span', {
style: {
},
}, [
h('h3.flex-row.text-transform-uppercase', {
style: {
color: '#868686',
paddingTop: '4px',
justifyContent: 'space-around',
textAlign: 'center',
fontSize: '17px',
},
}, `Market Info for ${marketinfo.pair.replace('_', ' to ').toUpperCase()}:`),
h('.marketinfo', ['Status : ', `${coinOptions[coin].status}`]),
h('.marketinfo', ['Exchange Rate: ', `${marketinfo.rate}`]),
h('.marketinfo', ['Limit: ', `${marketinfo.limit}`]),
h('.marketinfo', ['Minimum : ', `${marketinfo.minimum}`]),
]) ])
} }
ShapeshiftForm.prototype.activeToggle = function (elementType) {
if (!this.props.buyView.formView.response || this.props.warning) return elementType
return `${elementType}.inactive`
}
ShapeshiftForm.prototype.renderLoading = function () { ShapeshiftForm.prototype.render = function () {
return h('span', { const { coinOptions, btnClass } = this.props
style: { const { depositCoin, errorMessage, showQrCode, depositAddress } = this.state
position: 'absolute', const coinPair = `${depositCoin}_eth`
left: '70px', const { tokenExchangeRates } = this.props
bottom: '194px', const token = tokenExchangeRates[coinPair]
background: 'transparent',
width: '229px', return h('div.shapeshift-form-wrapper', [
height: '82px', showQrCode
display: 'flex', ? this.renderQrCode()
justifyContent: 'center', : h('div.shapeshift-form', [
}, h('div.shapeshift-form__selectors', [
}, [
h('img', { h('div.shapeshift-form__selector', [
style: {
width: '60px', h('div.shapeshift-form__selector-label', 'Deposit'),
},
src: 'images/loading.svg', h(SimpleDropdown, {
}), selectedOption: this.state.depositCoin,
]) onSelect: this.onCoinChange,
options: Object.entries(coinOptions).map(([coin]) => ({
value: coin.toLowerCase(),
displayValue: coin,
})),
}),
]),
h('div.icon.shapeshift-form__caret', {
style: { backgroundImage: 'url(images/caret-right.svg)'},
}),
h('div.shapeshift-form__selector', [
h('div.shapeshift-form__selector-label', [
'Receive',
]),
h('div.shapeshift-form__selector-input', ['ETH']),
]),
]),
h('div', {
className: classnames('shapeshift-form__address-input-wrapper', {
'shapeshift-form__address-input-wrapper--error': errorMessage,
}),
}, [
h('div.shapeshift-form__address-input-label', [
'Your Refund Address',
]),
h('input.shapeshift-form__address-input', {
type: 'text',
onChange: e => this.setState({
refundAddress: e.target.value,
errorMessage: '',
}),
}),
h('divshapeshift-form__address-input-error-message', [errorMessage]),
]),
this.renderMarketInfo(),
]),
!depositAddress && h('button.shapeshift-form__shapeshift-buy-btn', {
className: btnClass,
disabled: !token,
onClick: () => this.onBuyWithShapeShift(),
}, ['Buy']),
])
} }

@ -16,6 +16,7 @@ module.exports = connect(mapStateToProps)(ShiftListItem)
function mapStateToProps (state) { function mapStateToProps (state) {
return { return {
selectedAddress: state.metamask.selectedAddress,
conversionRate: state.metamask.conversionRate, conversionRate: state.metamask.conversionRate,
currentCurrency: state.metamask.currentCurrency, currentCurrency: state.metamask.currentCurrency,
} }
@ -28,36 +29,39 @@ function ShiftListItem () {
} }
ShiftListItem.prototype.render = function () { ShiftListItem.prototype.render = function () {
const { selectedAddress, receivingAddress } = this.props
return ( return (
h('div.tx-list-item.tx-list-clickable', { selectedAddress === receivingAddress
style: { ? h('div.tx-list-item.tx-list-clickable', {
paddingTop: '20px',
paddingBottom: '20px',
justifyContent: 'space-around',
alignItems: 'center',
},
}, [
h('div', {
style: { style: {
width: '0px', paddingTop: '20px',
position: 'relative', paddingBottom: '20px',
bottom: '19px', justifyContent: 'space-around',
alignItems: 'center',
}, },
}, [ }, [
h('img', { h('div', {
src: 'https://info.shapeshift.io/sites/default/files/logo.png',
style: { style: {
height: '35px', width: '0px',
width: '132px', position: 'relative',
position: 'absolute', bottom: '19px',
clip: 'rect(0px,23px,34px,0px)',
}, },
}), }, [
]), h('img', {
src: 'https://info.shapeshift.io/sites/default/files/logo.png',
style: {
height: '35px',
width: '132px',
position: 'absolute',
clip: 'rect(0px,23px,34px,0px)',
},
}),
]),
this.renderInfo(), this.renderInfo(),
this.renderUtilComponents(), this.renderUtilComponents(),
]) ])
: null
) )
} }

@ -86,7 +86,9 @@ TokenCell.prototype.render = function () {
numberOfDecimals: 2, numberOfDecimals: 2,
conversionRate: currentTokenToFiatRate, conversionRate: currentTokenToFiatRate,
}) })
formattedFiat = `${currentTokenInFiat} ${currentCurrency.toUpperCase()}` formattedFiat = currentTokenInFiat.toString() === '0'
? ''
: `${currentTokenInFiat} ${currentCurrency.toUpperCase()}`
} }
const showFiat = Boolean(currentTokenInFiat) && currentCurrency.toUpperCase() !== symbol const showFiat = Boolean(currentTokenInFiat) && currentCurrency.toUpperCase() !== symbol
@ -104,7 +106,7 @@ TokenCell.prototype.render = function () {
h(Identicon, { h(Identicon, {
className: 'token-list-item__identicon', className: 'token-list-item__identicon',
diameter: 45, diameter: 50,
address, address,
network, network,
}), }),

@ -1,6 +1,7 @@
const Component = require('react').Component const Component = require('react').Component
const h = require('react-hyperscript') const h = require('react-hyperscript')
const inherits = require('util').inherits const inherits = require('util').inherits
const connect = require('react-redux').connect
const EthBalance = require('./eth-balance') const EthBalance = require('./eth-balance')
const addressSummary = require('../util').addressSummary const addressSummary = require('../util').addressSummary
@ -9,18 +10,33 @@ const CopyButton = require('./copyButton')
const vreme = new (require('vreme'))() const vreme = new (require('vreme'))()
const Tooltip = require('./tooltip') const Tooltip = require('./tooltip')
const numberToBN = require('number-to-bn') const numberToBN = require('number-to-bn')
const actions = require('../actions')
const TransactionIcon = require('./transaction-list-item-icon') const TransactionIcon = require('./transaction-list-item-icon')
const ShiftListItem = require('./shift-list-item') const ShiftListItem = require('./shift-list-item')
module.exports = TransactionListItem
const mapDispatchToProps = dispatch => {
return {
retryTransaction: transactionId => dispatch(actions.retryTransaction(transactionId)),
}
}
module.exports = connect(null, mapDispatchToProps)(TransactionListItem)
inherits(TransactionListItem, Component) inherits(TransactionListItem, Component)
function TransactionListItem () { function TransactionListItem () {
Component.call(this) Component.call(this)
} }
TransactionListItem.prototype.showRetryButton = function () {
const { transaction = {} } = this.props
const { status, time } = transaction
return status === 'submitted' && Date.now() - time > 30000
}
TransactionListItem.prototype.render = function () { TransactionListItem.prototype.render = function () {
const { transaction, network, conversionRate, currentCurrency } = this.props const { transaction, network, conversionRate, currentCurrency } = this.props
const { status } = transaction
if (transaction.key === 'shapeshift') { if (transaction.key === 'shapeshift') {
if (network === '1') return h(ShiftListItem, transaction) if (network === '1') return h(ShiftListItem, transaction)
} }
@ -32,7 +48,7 @@ TransactionListItem.prototype.render = function () {
var isMsg = ('msgParams' in transaction) var isMsg = ('msgParams' in transaction)
var isTx = ('txParams' in transaction) var isTx = ('txParams' in transaction)
var isPending = transaction.status === 'unapproved' var isPending = status === 'unapproved'
let txParams let txParams
if (isTx) { if (isTx) {
txParams = transaction.txParams txParams = transaction.txParams
@ -44,7 +60,7 @@ TransactionListItem.prototype.render = function () {
const isClickable = ('hash' in transaction && isLinkable) || isPending const isClickable = ('hash' in transaction && isLinkable) || isPending
return ( return (
h(`.transaction-list-item.flex-row.flex-space-between${isClickable ? '.pointer' : ''}`, { h('.transaction-list-item.flex-column', {
onClick: (event) => { onClick: (event) => {
if (isPending) { if (isPending) {
this.props.showTx(transaction.id) this.props.showTx(transaction.id)
@ -56,51 +72,92 @@ TransactionListItem.prototype.render = function () {
}, },
style: { style: {
padding: '20px 0', padding: '20px 0',
alignItems: 'center',
}, },
}, [ }, [
h(`.flex-row.flex-space-between${isClickable ? '.pointer' : ''}`, {
h('.identicon-wrapper.flex-column.flex-center.select-none', [ style: {
h(TransactionIcon, { txParams, transaction, isTx, isMsg }), width: '100%',
},
}, [
h('.identicon-wrapper.flex-column.flex-center.select-none', [
h(TransactionIcon, { txParams, transaction, isTx, isMsg }),
]),
h(Tooltip, {
title: 'Transaction Number',
position: 'right',
}, [
h('span', {
style: {
display: 'flex',
cursor: 'normal',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
padding: '10px',
},
}, nonce),
]),
h('.flex-column', {style: {width: '200px', overflow: 'hidden'}}, [
domainField(txParams),
h('div', date),
recipientField(txParams, transaction, isTx, isMsg),
]),
// Places a copy button if tx is successful, else places a placeholder empty div.
transaction.hash ? h(CopyButton, { value: transaction.hash }) : h('div', {style: { display: 'flex', alignItems: 'center', width: '26px' }}),
isTx ? h(EthBalance, {
value: txParams.value,
conversionRate,
currentCurrency,
width: '55px',
shorten: true,
showFiat: false,
style: {fontSize: '15px'},
}) : h('.flex-column'),
]), ]),
h(Tooltip, { this.showRetryButton() && h('.transition-list-item__retry.grow-on-hover', {
title: 'Transaction Number', onClick: event => {
position: 'right', event.stopPropagation()
this.resubmit()
},
style: {
height: '22px',
borderRadius: '22px',
color: '#F9881B',
padding: '0 20px',
backgroundColor: '#FFE3C9',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
fontSize: '8px',
cursor: 'pointer',
},
}, [ }, [
h('span', { h('div', {
style: { style: {
display: 'flex', paddingRight: '2px',
cursor: 'normal',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
padding: '10px',
}, },
}, nonce), }, 'Taking too long?'),
]), h('div', {
style: {
h('.flex-column', {style: {width: '200px', overflow: 'hidden'}}, [ textDecoration: 'underline',
domainField(txParams), },
h('div', date), }, 'Retry with a higher gas price here'),
recipientField(txParams, transaction, isTx, isMsg),
]), ]),
// Places a copy button if tx is successful, else places a placeholder empty div.
transaction.hash ? h(CopyButton, { value: transaction.hash }) : h('div', {style: { display: 'flex', alignItems: 'center', width: '26px' }}),
isTx ? h(EthBalance, {
value: txParams.value,
conversionRate,
currentCurrency,
width: '55px',
shorten: true,
showFiat: false,
style: {fontSize: '15px'},
}) : h('.flex-column'),
]) ])
) )
} }
TransactionListItem.prototype.resubmit = function () {
const { transaction } = this.props
this.props.retryTransaction(transaction.id)
}
function domainField (txParams) { function domainField (txParams) {
return h('div', { return h('div', {
style: { style: {

@ -170,6 +170,7 @@ TxListItem.prototype.getSendTokenTotal = async function () {
TxListItem.prototype.render = function () { TxListItem.prototype.render = function () {
const { const {
transactionStatus, transactionStatus,
transactionAmount,
onClick, onClick,
transActionId, transActionId,
dateString, dateString,
@ -177,6 +178,7 @@ TxListItem.prototype.render = function () {
className, className,
} = this.props } = this.props
const { total, fiatTotal } = this.state const { total, fiatTotal } = this.state
const showFiatTotal = transactionAmount !== '0x0' && fiatTotal
return h(`div${className || ''}`, { return h(`div${className || ''}`, {
key: transActionId, key: transActionId,
@ -232,13 +234,9 @@ TxListItem.prototype.render = function () {
style: {}, style: {},
}, [ }, [
h('span', { h('span.tx-list-value', total),
className: classnames('tx-list-value', {
'tx-list-value--confirmed': transactionStatus === 'confirmed',
}),
}, total),
fiatTotal && h('span.tx-list-fiat-value', fiatTotal), showFiatTotal && h('span.tx-list-fiat-value', fiatTotal),
]), ]),
]), ]),

@ -42,23 +42,22 @@ TxList.prototype.componentWillMount = function () {
} }
TxList.prototype.render = function () { TxList.prototype.render = function () {
return h('div.flex-column.tx-list-container', {}, [ return h('div.flex-column', [
h('div.flex-row.tx-list-header-wrapper', [ h('div.flex-row.tx-list-header-wrapper', [
h('div.flex-row.tx-list-header', [ h('div.flex-row.tx-list-header', [
h('div', 'transactions'), h('div', 'transactions'),
]), ]),
]), ]),
h('div.flex-column.tx-list-container', {}, [
this.renderTransaction(), this.renderTransaction(),
]),
]) ])
} }
TxList.prototype.renderTransaction = function () { TxList.prototype.renderTransaction = function () {
const { txsToRender, conversionRate } = this.props const { txsToRender, conversionRate } = this.props
return txsToRender.length return txsToRender.length
? txsToRender.map((transaction, i) => this.renderTransactionListItem(transaction, conversionRate)) ? txsToRender.map((transaction, i) => this.renderTransactionListItem(transaction, conversionRate, i))
: [h( : [h(
'div.tx-list-item.tx-list-item--empty', 'div.tx-list-item.tx-list-item--empty',
{ key: 'tx-list-none' }, { key: 'tx-list-none' },
@ -67,12 +66,16 @@ TxList.prototype.renderTransaction = function () {
} }
// TODO: Consider moving TxListItem into a separate component // TODO: Consider moving TxListItem into a separate component
TxList.prototype.renderTransactionListItem = function (transaction, conversionRate) { TxList.prototype.renderTransactionListItem = function (transaction, conversionRate, index) {
// console.log({transaction}) // console.log({transaction})
// refer to transaction-list.js:line 58 // refer to transaction-list.js:line 58
if (transaction.key === 'shapeshift') { if (transaction.key === 'shapeshift') {
return h(ShiftListItem, transaction) return h('div', {
key: `shapeshift${index}`,
}, [
h(ShiftListItem, transaction),
])
} }
const props = { const props = {

@ -74,18 +74,14 @@ TxView.prototype.renderButtons = function () {
return !selectedToken return !selectedToken
? ( ? (
h('div.flex-row.flex-center.hero-balance-buttons', [ h('div.flex-row.flex-center.hero-balance-buttons', [
h('button.btn-clear', { h('button.btn-clear.hero-balance-button', {
style: {
textAlign: 'center',
},
onClick: () => showModal({ onClick: () => showModal({
name: 'BUY', name: 'DEPOSIT_ETHER',
}), }),
}, 'DEPOSIT'), }, 'DEPOSIT'),
h('button.btn-clear', { h('button.btn-clear.hero-balance-button', {
style: { style: {
textAlign: 'center',
marginLeft: '0.8em', marginLeft: '0.8em',
}, },
onClick: () => history.push(SEND_ROUTE), onClick: () => history.push(SEND_ROUTE),
@ -94,11 +90,7 @@ TxView.prototype.renderButtons = function () {
) )
: ( : (
h('div.flex-row.flex-center.hero-balance-buttons', [ h('div.flex-row.flex-center.hero-balance-buttons', [
h('button.btn-clear', { h('button.btn-clear.hero-balance-button', {
style: {
textAlign: 'center',
marginLeft: '0.8em',
},
onClick: () => history.push(SEND_ROUTE), onClick: () => history.push(SEND_ROUTE),
}, 'SEND'), }, 'SEND'),
]) ])
@ -114,7 +106,7 @@ TxView.prototype.render = function () {
h('div.flex-row.phone-visible', { h('div.flex-row.phone-visible', {
style: { style: {
margin: '1em 0.9em', margin: '1.5em 1.2em 0',
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center', alignItems: 'center',
}, },
@ -150,7 +142,7 @@ TxView.prototype.render = function () {
!isMascara && h('div.open-in-browser', { !isMascara && h('div.open-in-browser', {
onClick: () => global.platform.openExtensionInBrowser(), onClick: () => global.platform.openExtensionInBrowser(),
}, [h('img', { src: 'images/open.svg' })]), }, [h('img', { src: 'images/popout.svg' })]),
]), ]),

@ -136,7 +136,7 @@ WalletView.prototype.render = function () {
selectedIdentity.name, selectedIdentity.name,
]), ]),
h('button.wallet-view__details-button', 'DETAILS'), h('button.btn-clear.wallet-view__details-button', 'DETAILS'),
]), ]),
]), ]),
@ -157,7 +157,7 @@ WalletView.prototype.render = function () {
h(TokenList), h(TokenList),
h('button.wallet-view__add-token-button', { h('button.btn-clear.wallet-view__add-token-button', {
onClick: () => history.push(ADD_TOKEN_ROUTE), onClick: () => history.push(ADD_TOKEN_ROUTE),
}, 'Add Token'), }, 'Add Token'),
]) ])

@ -94,6 +94,7 @@
padding: 12px 0; padding: 12px 0;
font-weight: 600; font-weight: 600;
cursor: pointer; cursor: pointer;
position: relative;
&:hover { &:hover {
background-color: rgba(0, 0, 0, .05); background-color: rgba(0, 0, 0, .05);
@ -164,9 +165,18 @@
&__buttons { &__buttons {
display: flex; display: flex;
flex-flow: column nowrap; flex-flow: row nowrap;
margin: 30px 0 51px; margin: 30px 0 51px;
flex: 0 0 auto; flex: 0 0 auto;
align-items: center;
justify-content: center;
}
&__button {
flex: 1 0 141px;
margin: 0 12px;
padding: 10px 22px;
height: 54px;
} }
&__token-icons-container { &__token-icons-container {
@ -324,18 +334,10 @@
} }
&__buttons { &__buttons {
flex-flow: row nowrap;
width: 100%;
align-items: center;
justify-content: center;
padding: 12px 0; padding: 12px 0;
margin: 0; margin: 0;
border-top: 1px solid $gallery; border-top: 1px solid $gallery;
width: 100%;
button {
flex: 1 0 auto;
margin: 0 12px;
}
} }
} }
} }

@ -6,9 +6,43 @@
background-color: #02c9b1; // TODO: reusable color in colors.css background-color: #02c9b1; // TODO: reusable color in colors.css
} }
button.btn-clear { .btn-clear {
background: $white; background: $white;
border: 1px solid; text-align: center;
padding: .8rem 1rem;
color: $curious-blue;
border: 2px solid $spindle;
border-radius: 4px;
font-size: .85rem;
font-weight: 400;
transition: border-color .3s ease;
&:hover {
border-color: $curious-blue;
}
&--disabled,
&[disabled] {
cursor: auto;
opacity: .5;
pointer-events: none;
}
}
.btn-cancel {
background: $white;
text-align: center;
padding: .9rem 1rem;
color: $scorpion;
border: 2px solid $dusty-gray;
border-radius: 4px;
font-size: .85rem;
font-weight: 400;
transition: border-color .3s ease;
&:hover {
border-color: $scorpion;
}
} }
// No longer used in flat design, remove when modal buttons done // No longer used in flat design, remove when modal buttons done

@ -2,13 +2,15 @@
position: relative; position: relative;
align-items: center; align-items: center;
font-family: Roboto; font-family: Roboto;
flex: 0 0 auto; flex: 1 0 auto;
flex-flow: column nowrap; flex-flow: column nowrap;
box-shadow: 0 2px 4px 0 rgba($black, .08); box-shadow: 0 2px 4px 0 rgba($black, .08);
border-radius: 8px; border-radius: 8px;
display: flex;
@media screen and (max-width: 575px) { @media screen and (max-width: 575px) {
width: 100%; width: 100%;
box-shadow: initial;
} }
@media screen and (min-width: 576px) { @media screen and (min-width: 576px) {
@ -102,15 +104,10 @@
.confirm-screen-back-button { .confirm-screen-back-button {
background: transparent; background: transparent;
border: 1px solid $curious-blue;
left: 24px; left: 24px;
position: absolute; position: absolute;
text-align: center; padding: 6px 12px;
color: $curious-blue; font-size: .7rem;
padding: 6px 13px 7px 12px;
border-radius: 2px;
height: 30px;
width: 54px;
@media screen and (max-width: $break-small) { @media screen and (max-width: $break-small) {
margin-right: 12px; margin-right: 12px;
@ -277,8 +274,8 @@ section .confirm-screen-account-number,
} }
.confirm-screen-confirm-button { .confirm-screen-confirm-button {
height: 62px; height: 50px;
border-radius: 2px; border-radius: 4px;
background-color: #02c9b1; background-color: #02c9b1;
font-size: 16px; font-size: 16px;
color: $white; color: $white;
@ -290,11 +287,11 @@ section .confirm-screen-account-number,
box-shadow: none; box-shadow: none;
flex: 1 0 auto; flex: 1 0 auto;
font-weight: 300; font-weight: 300;
margin: 0 8px; margin: 0 5px;
} }
.btn-light.confirm-screen-cancel-button { .btn-light.confirm-screen-cancel-button {
height: 62px; height: 50px;
background: none; background: none;
border: none; border: none;
opacity: 1; opacity: 1;
@ -303,12 +300,11 @@ section .confirm-screen-account-number,
padding-top: 15px; padding-top: 15px;
padding-bottom: 15px; padding-bottom: 15px;
font-size: 16px; font-size: 16px;
line-height: 32px;
box-shadow: none; box-shadow: none;
cursor: pointer; cursor: pointer;
flex: 1 0 auto; flex: 1 0 auto;
font-weight: 300; font-weight: 300;
margin: 0 8px; margin: 0 5px;
} }
#pending-tx-form { #pending-tx-form {
@ -317,7 +313,7 @@ section .confirm-screen-account-number,
display: flex; display: flex;
flex-flow: row nowrap; flex-flow: row nowrap;
background-color: $white; background-color: $white;
padding: 12px 18px; padding: 12px;
border-bottom-left-radius: 8px; border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px; border-bottom-right-radius: 8px;
width: 100%; width: 100%;

@ -17,7 +17,16 @@
@media screen and (min-width: 576px) { @media screen and (min-width: 576px) {
height: 75px; height: 75px;
justify-content: center; justify-content: center;
}
.metafox-icon {
cursor: pointer;
}
}
.app-header--initialized {
@media screen and (min-width: 576px) {
&::after { &::after {
content: ''; content: '';
position: absolute; position: absolute;
@ -27,10 +36,6 @@
bottom: -32px; bottom: -32px;
} }
} }
.metafox-icon {
cursor: pointer;
}
} }
.app-header-contents { .app-header-contents {
@ -53,7 +58,7 @@
} }
@media screen and (min-width: 1281px) { @media screen and (min-width: 1281px) {
width: 65vw; width: 62vw;
} }
} }
@ -61,8 +66,10 @@
font-family: Roboto; font-family: Roboto;
text-transform: uppercase; text-transform: uppercase;
font-weight: 400; font-weight: 400;
color: #22232c; // $shark font-size: 1.1rem;
line-height: 29px; position: relative;
padding-left: 15px;
color: #5b5d67;
@media screen and (max-width: 575px) { @media screen and (max-width: 575px) {
display: none; display: none;

@ -16,7 +16,8 @@
flex-direction: row; flex-direction: row;
justify-content: flex-start; justify-content: flex-start;
align-items: center; align-items: center;
margin: 2.8em 2.37em .8em; margin: 2.3em 2.37em .8em;
flex: 0 0 auto;
} }
.balance-container { .balance-container {
@ -37,13 +38,16 @@
} }
.balance-display { .balance-display {
.token-amount {
color: $black;
}
@media screen and (max-width: $break-small) { @media screen and (max-width: $break-small) {
text-align: center; text-align: center;
.token-amount { .token-amount {
font-size: 175%; font-size: 1.75rem;
margin-top: 12.5%; margin-top: 1rem;
} }
.fiat-amount { .fiat-amount {
@ -54,12 +58,12 @@
} }
@media screen and (min-width: $break-large) { @media screen and (min-width: $break-large) {
margin-left: 3%; margin-left: .8em;
justify-content: flex-start; justify-content: flex-start;
align-items: flex-start; align-items: flex-start;
.token-amount { .token-amount {
font-size: 135%; font-size: 1.5rem;
} }
.fiat-amount { .fiat-amount {
@ -69,13 +73,6 @@
} }
} }
.balance-icon {
border-radius: 25px;
width: 45px;
height: 45px;
border: 1px solid $alto;
}
.hero-balance-buttons { .hero-balance-buttons {
@media screen and (max-width: $break-small) { @media screen and (max-width: $break-small) {
@ -89,26 +86,9 @@
flex-grow: 2; flex-grow: 2;
justify-content: flex-end; justify-content: flex-end;
} }
button.btn-clear {
background: $white;
border: 1px solid;
border-radius: 2px;
font-size: 12px;
@media screen and (max-width: $break-small) {
border-color: $curious-blue;
color: $curious-blue;
height: 36px;
}
@media screen and (min-width: $break-large) {
border-color: $curious-blue;
color: $curious-blue;
padding: 0;
width: 85px;
height: 34px;
}
}
} }
} }
.hero-balance-button {
width: 6rem;
}

@ -53,3 +53,5 @@
@import './editable-label.scss'; @import './editable-label.scss';
@import './pages/index.scss'; @import './pages/index.scss';
@import './new-account.scss';

@ -258,19 +258,10 @@
width: 286px; width: 286px;
} }
.btn-clear { .account-modal__button {
min-height: 28px;
font-size: 14px;
border-color: $curious-blue;
color: $curious-blue;
border-radius: 2px;
flex-basis: 100%;
width: 75%;
margin-top: 17px; margin-top: 17px;
padding: 10px 22px; padding: 10px 22px;
height: 44px;
width: 235px; width: 235px;
font-family: Roboto;
} }
} }
@ -346,17 +337,17 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: center; justify-content: center;
}
.btn-clear { .export-private-key__button {
width: 141px; margin-top: 17px;
height: 54px; padding: 10px 22px;
} width: 141px;
height: 54px;
}
.btn-cancel { .export-private-key__button--cancel {
margin-right: 15px; margin-right: 15px;
border-color: $dusty-gray;
color: $scorpion;
}
} }
.private-key-password-display-wrapper { .private-key-password-display-wrapper {
@ -495,10 +486,9 @@
.hide-token-confirmation { .hide-token-confirmation {
min-height: 250.72px; min-height: 250.72px;
width: 374.49px;
border-radius: 4px; border-radius: 4px;
background-color: #FFFFFF; background-color: $white;
box-shadow: 0 1px 7px 0 rgba(0,0,0,0.5); box-shadow: 0 1px 7px 0 rgba(0, 0, 0, .5);
&__container { &__container {
padding: 24px 27px 21px; padding: 24px 27px 21px;
@ -508,7 +498,7 @@
} }
&__identicon { &__identicon {
margin-bottom: 10px margin-bottom: 10px;
} }
&__symbol { &__symbol {
@ -547,21 +537,264 @@
justify-content: center; justify-content: center;
margin-top: 15px; margin-top: 15px;
width: 100%; width: 100%;
}
&__button {
width: 141px;
margin: 0 5px;
}
}
//Notification Modal
button { .notification-modal-wrapper {
height: 44px; display: flex;
width: 113px; flex-direction: column;
border: 1px solid $scorpion; justify-content: flex-start;
border-radius: 2px; align-items: center;
color: $tundora; position: relative;
font-family: Roboto; border: 1px solid $alto;
font-size: 14px; box-shadow: 0 0 2px 2px $alto;
line-height: 20px; font-family: Roboto;
text-align: center; }
margin-left: 4px;
margin-right: 4px; .notification-modal-header {
background: $wild-sand;
width: 100%;
display: flex;
justify-content: center;
padding: 30px;
font-size: 22px;
color: $nile-blue;
height: 79px;
}
.notification-modal-message {
padding: 20px;
}
.notification-modal-message {
width: 100%;
display: flex;
justify-content: center;
font-size: 17px;
color: $nile-blue;
}
// Deposit Ether Modal
.deposit-ether-modal {
border-radius: 8px;
font-family: Roboto;
display: flex;
flex-flow: column;
height: 100%;
&__header {
width: 100%;
border-radius: 8px 8px 0 0;
background-color: $mid-gray;
display: flex;
position: relative;
padding: 25px;
flex-flow: column;
align-items: flex-start;
&__title {
color: $white;
font-size: 24px;
line-height: 32px;
}
&__description {
color: $white;
font-size: 16px;
line-height: 22px;
margin-top: 10px;
}
&__close::after {
content: '\00D7';
font-size: 2em;
color: $white;
position: absolute;
top: 20.8px;
right: 28px;
cursor: pointer;
} }
} }
&__buy-rows {
width: 100%;
padding: 33px;
padding-top: 0px;
display: flex;
flex-flow: column nowrap;
flex: 1;
overflow-y: auto;
@media screen and (max-width: 575px) {
height: 0;
}
}
&__buy-row {
border-bottom: 1px solid $alto;
display: flex;
justify-content: space-between;
align-items: center;
flex: 1;
padding-bottom: 25px;
padding-top: 25px;
@media screen and (max-width: 575px) {
min-height: 360px;
flex-flow: column;
justify-content: center;
padding-top: 45px;
}
&__back {
position: absolute;
top: 10px;
left: 0px;
}
&__shapeshift-buy {
padding-top: 25px;
position: relative;
@media screen and (max-width: 575px) {
display: flex;
justify-content: space-between;
align-items: center;
flex: 1;
padding-bottom: 25px;
flex-flow: column;
justify-content: center;
padding-top: 20px;
min-height: 240px;
border: none;
}
}
&__logo {
display: flex;
justify-content: center;
flex: 0.3 1 auto;
@media screen and (min-width: 575px) {
min-width: 215px;
}
}
&__coinbase-logo {
height: 40px;
width: 180px;
}
&__shapeshift-logo {
height: 60px;
width: 174px;
}
&__eth-logo {
border-radius: 50%;
width: 68px;
height: 68px;
border: 3px solid $tundora;
z-index: 25;
padding: 4px;
background-color: #fff;
}
&__right {
display: flex;
}
&__description {
color: $cape-cod;
flex: 0.5 1 auto;
@media screen and (min-width: 575px) {
min-width: 315px;
}
&__title {
font-size: 20px;
line-height: 30px;
}
&__text {
font-size: 14px;
line-height: 22px;
margin-top: 7px;
}
}
&__button {
display: flex;
justify-content: flex-end;
@media screen and (min-width: 575px) {
min-width: 300px;
}
}
}
&__buy-row:last-of-type {
border-bottom: 0px;
}
&__deposit-button, .shapeshift-form__shapeshift-buy-btn {
height: 54px;
width: 257px;
border: 1px solid $curious-blue;
border-radius: 4px;
display: flex;
justify-content: center;
font-size: 16px;
color: $curious-blue;
background-color: $white;
}
.shapeshift-form-wrapper {
display: flex;
flex-flow: column;
justify-content: center;
align-items: center;
margin-top: 28px;
flex: 1 0 auto;
.shapeshift-form {
width: auto;
&__caret {
width: auto;
flex: 1;
}
}
}
.shapeshift-form__shapeshift-buy-btn {
margin-top: 10px;
}
.simple-dropdown {
color: #5B5D67;
font-size: 16px;
font-weight: 300;
line-height: 21px;
border: 1px solid #D8D8D8;
background-color: #FFFFFF;
text-align: center;
width: 100%;
height: 45px;
line-height: 44px;
font-family: Montserrat Light;
}
.simple-dropdown__selected {
text-align: center;
}
} }
//Notification Modal //Notification Modal
@ -582,6 +815,7 @@
width: 100%; width: 100%;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center;
padding: 30px; padding: 30px;
font-size: 22px; font-size: 22px;
color: $nile-blue; color: $nile-blue;

@ -8,41 +8,25 @@
} }
.network-component.pointer { .network-component.pointer {
border: 1px solid $shark; border: 2px solid $silver;
border-radius: 82px; border-radius: 82px;
padding: 6px; padding: 3px;
flex: 0 0 auto; flex: 0 0 auto;
&.ethereum-network { &.ethereum-network .menu-icon-circle div {
border-color: rgb(3, 135, 137); background-color: rgba(3, 135, 137, .7) !important;
.menu-icon-circle div {
background-color: rgba(3, 135, 137, .7) !important;
}
} }
&.ropsten-test-network { &.ropsten-test-network .menu-icon-circle div {
border-color: rgb(233, 21, 80); background-color: rgba(233, 21, 80, .7) !important;
.menu-icon-circle div {
background-color: rgba(233, 21, 80, .7) !important;
}
} }
&.kovan-test-network { &.kovan-test-network .menu-icon-circle div {
border-color: rgb(105, 4, 150); background-color: rgba(105, 4, 150, .7) !important;
.menu-icon-circle div {
background-color: rgba(105, 4, 150, .7) !important;
}
} }
&.rinkeby-test-network { &.rinkeby-test-network .menu-icon-circle div {
border-color: rgb(235, 179, 63); background-color: rgba(235, 179, 63, .7) !important;
.menu-icon-circle div {
background-color: rgba(235, 179, 63, .7) !important;
}
} }
} }
@ -66,11 +50,12 @@
} }
.network-name { .network-name {
line-height: 15px;
padding: 0 4px; padding: 0 4px;
font-family: Roboto; font-family: Roboto;
font-size: 12px; font-size: 12px;
flex: 1 0 auto; flex: 1 0 auto;
color: $tundora;
font-weight: 500;
} }
.network-droppo { .network-droppo {
@ -167,3 +152,6 @@
line-height: 18px; line-height: 18px;
} }
.network-caret {
margin: 0 8px 2px;
}

@ -0,0 +1,192 @@
.new-account {
width: 376px;
background-color: #FFFFFF;
box-shadow: 0 0 7px 0 rgba(0,0,0,0.08);
z-index: 25;
padding-bottom: 31px;
&__header {
display: flex;
flex-flow: column;
border-bottom: 1px solid $geyser;
}
&__title {
color: $tundora;
font-family: Roboto;
font-size: 32px;
font-weight: 500;
line-height: 43px;
margin-top: 22px;
margin-left: 29px;
}
&__tabs {
margin-left: 22px;
display: flex;
margin-top: 10px;
&__tab {
height: 54px;
width: 75px;
padding: 15px 10px;
color: $dusty-gray;
font-family: Roboto;
font-size: 18px;
line-height: 24px;
text-align: center;
}
&__tab:first-of-type {
margin-right: 20px;
}
&__unselected:hover {
color: $black;
border-bottom: none;
}
&__selected {
color: $curious-blue;
border-bottom: 3px solid $curious-blue;
}
}
}
.new-account-import-form {
&__select-section {
display: flex;
justify-content: space-evenly;
align-items: center;
margin-top: 29px;
}
&__select-label {
color: $scorpion;
font-family: Roboto;
font-size: 16px;
line-height: 21px;
}
&__select {
height: 54px;
width: 210px;
border: 1px solid #D2D8DD;
border-radius: 4px;
background-color: #FFFFFF;
display: flex;
align-items: center;
.Select-control,
.Select-control:hover {
height: 100%;
border: none;
box-shadow: none;
.Select-value {
display: flex;
align-items: center;
}
}
}
&__instruction {
color: $scorpion;
font-family: Roboto;
font-size: 16px;
line-height: 21px;
align-self: flex-start;
margin-left: 30px;
}
&__private-key {
display: flex;
flex-flow: column;
align-items: center;
margin-top: 34px;
}
&__input-password {
height: 54px;
width: 315px;
border: 1px solid $geyser;
border-radius: 4px;
background-color: $white;
margin-top: 16px;
color: $scorpion;
font-family: Roboto;
font-size: 16px;
padding: 0px 20px;
}
&__json {
display: flex;
flex-flow: column;
align-items: center;
margin-top: 29px;
}
}
.new-account-create-form {
display: flex;
flex-flow: column;
align-items: center;
&__input-label {
color: $scorpion;
font-family: Roboto;
font-size: 16px;
line-height: 21px;
margin-top: 29px;
align-self: flex-start;
margin-left: 30px;
}
&__input {
height: 54px;
width: 315.84px;
border: 1px solid $geyser;
border-radius: 4px;
background-color: $white;
color: $scorpion;
font-family: Roboto;
font-size: 16px;
line-height: 21px;
margin-top: 15px;
padding: 0px 20px;
}
&__buttons {
margin-top: 39px;
display: flex;
width: 100%;
justify-content: space-evenly;
}
&__button-cancel,
&__button-create {
height: 55px;
width: 150px;
border-radius: 2px;
background-color: #FFFFFF;
}
&__button-cancel {
border: 1px solid $dusty-gray;
color: $dusty-gray;
font-family: Roboto;
font-size: 16px;
line-height: 21px;
text-align: center;
}
&__button-create {
border: 1px solid $curious-blue;
color: $curious-blue;
font-family: Roboto;
font-size: 16px;
line-height: 21px;
text-align: center;
}
}

@ -4,7 +4,7 @@
// Component Colors // Component Colors
$tx-view-bg: $white; $tx-view-bg: $white;
$wallet-view-bg: $wild-sand; $wallet-view-bg: $alabaster;
// Main container // Main container
.main-container { .main-container {
@ -40,6 +40,8 @@ $wallet-view-bg: $wild-sand;
.open-in-browser { .open-in-browser {
cursor: pointer; cursor: pointer;
display: flex;
justify-content: center;
} }
// wallet view and sidebar // wallet view and sidebar
@ -47,7 +49,7 @@ $wallet-view-bg: $wild-sand;
.wallet-view { .wallet-view {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 33.5 1 33.5%; flex: 32 1 32%;
width: 0; width: 0;
background: $wallet-view-bg; background: $wallet-view-bg;
z-index: 200; z-index: 200;
@ -69,22 +71,18 @@ $wallet-view-bg: $wild-sand;
} }
&__keyring-label { &__keyring-label {
height: 40px; height: 50px;
color: $dusty-gray; color: $dusty-gray;
font-family: Roboto; font-family: Roboto;
font-size: 10px; font-size: 10px;
line-height: 40px;
text-align: right; text-align: right;
padding: 0 20px; padding: 17px 20px 0;
box-sizing: border-box;
} }
&__details-button { &__details-button {
color: $curious-blue;
font-size: 10px; font-size: 10px;
line-height: 13px; border-radius: 17px;
text-align: center;
border: 1px solid $curious-blue;
border-radius: 10.5px;
background-color: transparent; background-color: transparent;
margin: 0 auto; margin: 0 auto;
padding: 4px 12px; padding: 4px 12px;
@ -121,16 +119,14 @@ $wallet-view-bg: $wild-sand;
&__add-token-button { &__add-token-button {
flex: 0 0 auto; flex: 0 0 auto;
color: $dusty-gray;
font-size: 14px;
line-height: 19px;
text-align: center;
margin: 36px auto; margin: 36px auto;
border: 1px solid $dusty-gray;
border-radius: 2px;
font-weight: 300;
background: none; background: none;
padding: 9px 30px; padding: .7rem 2rem;
transition: border-color .3s ease;
&:hover {
border-color: $curious-blue;
}
} }
} }
@ -159,7 +155,7 @@ $wallet-view-bg: $wild-sand;
background: rgb(250, 250, 250); background: rgb(250, 250, 250);
z-index: $sidebar-z-index; z-index: $sidebar-z-index;
position: fixed; position: fixed;
top: 56px; top: 66px;
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
@ -199,7 +195,7 @@ $wallet-view-bg: $wild-sand;
.main-container { .main-container {
// margin-top: 6.9vh; // margin-top: 6.9vh;
width: 85%; width: 85vw;
height: 90vh; height: 90vh;
box-shadow: 0 0 7px 0 rgba(0, 0, 0, .08); box-shadow: 0 0 7px 0 rgba(0, 0, 0, .08);
} }
@ -208,7 +204,7 @@ $wallet-view-bg: $wild-sand;
@media screen and (min-width: 769px) { @media screen and (min-width: 769px) {
.main-container { .main-container {
// margin-top: 6.9vh; // margin-top: 6.9vh;
width: 80%; width: 80vw;
height: 82vh; height: 82vh;
box-shadow: 0 0 7px 0 rgba(0, 0, 0, .08); box-shadow: 0 0 7px 0 rgba(0, 0, 0, .08);
} }
@ -217,7 +213,7 @@ $wallet-view-bg: $wild-sand;
@media screen and (min-width: 1281px) { @media screen and (min-width: 1281px) {
.main-container { .main-container {
// margin-top: 6.9vh; // margin-top: 6.9vh;
width: 65%; width: 62vw;
height: 82vh; height: 82vh;
box-shadow: 0 0 7px 0 rgba(0, 0, 0, .08); box-shadow: 0 0 7px 0 rgba(0, 0, 0, .08);
} }
@ -239,14 +235,6 @@ $wallet-view-bg: $wild-sand;
overflow-y: auto; overflow-y: auto;
background-color: $white; background-color: $white;
} }
button.btn-clear {
width: 93px;
height: 50px;
font-size: .7em;
background: $white;
border: 1px solid;
}
} }
// wallet view // wallet view
@ -254,9 +242,9 @@ $wallet-view-bg: $wild-sand;
font-size: 24px; font-size: 24px;
font-weight: 300; font-weight: 300;
line-height: 20px; line-height: 20px;
color: $scorpion; color: $black;
margin-top: 8px; margin-top: 8px;
margin-bottom: 24px; margin-bottom: .9rem;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;

@ -526,8 +526,9 @@
} }
&__form { &__form {
margin: 13px 0; padding: 13px 0;
width: 100%; width: 100%;
overflow-y: auto;
@media screen and (max-width: $break-small) { @media screen and (max-width: $break-small) {
padding: 13px 0; padding: 13px 0;
@ -651,11 +652,11 @@
border: 1px solid $curious-blue; border: 1px solid $curious-blue;
border-radius: 4px; border-radius: 4px;
background-color: $white; background-color: $white;
padding: 5px;
position: absolute; position: absolute;
right: 15px; right: 15px;
top: 14px; top: 14px;
cursor: pointer; cursor: pointer;
font-size: 1em;
} }
&__sliders-icon { &__sliders-icon {
@ -677,40 +678,15 @@
border-top: 1px solid $alto; border-top: 1px solid $alto;
background: $white; background: $white;
padding: 0 12px; padding: 0 12px;
flex-shrink: 0;
} }
&__next-btn, &__next-btn,
&__cancel-btn, &__cancel-btn {
&__next-btn__disabled {
width: 163px; width: 163px;
text-align: center;
height: 55px;
border-radius: 2px;
background-color: $white;
font-family: Roboto;
font-size: 16px;
font-weight: 300;
line-height: 21px;
border: 1px solid;
margin: 0 4px; margin: 0 4px;
} }
&__next-btn,
&__next-btn__disabled {
color: $curious-blue;
border-color: $curious-blue;
}
&__next-btn__disabled {
opacity: .5;
cursor: auto;
}
&__cancel-btn {
color: $dusty-gray;
border-color: $dusty-gray;
}
&__customize-gas { &__customize-gas {
border: 1px solid #D8D8D8; border: 1px solid #D8D8D8;
border-radius: 4px; border-radius: 4px;

@ -12,7 +12,7 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
position: relative; position: relative;
&__token-balance { &__token-balance {
font-size: 130%; font-size: 1.5rem;
@media #{$wallet-balance-breakpoint-range} { @media #{$wallet-balance-breakpoint-range} {
font-size: 105%; font-size: 105%;
@ -34,7 +34,8 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
} }
&--active { &--active {
background-color: rgba($wallet-balance-bg, 1); background-color: $manatee;
color: $white;
} }
&__identicon { &__identicon {
@ -62,11 +63,11 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
height: 55px; height: 55px;
width: 191px; width: 191px;
border-radius: 4px; border-radius: 4px;
background-color: rgba(0,0,0,0.82); background-color: rgba(0, 0, 0, .82);
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.5); box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .5);
position: fixed; position: absolute;
margin-top: 20px; top: 60px;
margin-left: 105px; right: 25px;
z-index: 2000; z-index: 2000;
&__close-area { &__close-area {

@ -6,6 +6,10 @@
} }
} }
.tx-list-header-wrapper {
flex: 0 0 auto;
}
.tx-list-header { .tx-list-header {
text-transform: capitalize; text-transform: capitalize;
} }
@ -32,13 +36,9 @@
} }
@media screen and (min-width: $break-large) { @media screen and (min-width: $break-large) {
.tx-list-header-wrapper {
flex: 0 0 55px;
}
.tx-list-header { .tx-list-header {
font-size: 16px; font-size: 16px;
margin: 1.5em 2.37em; margin: 1.1em 2.37em .8em;
} }
.tx-list-container::-webkit-scrollbar { .tx-list-container::-webkit-scrollbar {
@ -73,7 +73,7 @@
} }
@media screen and (min-width: $break-large) { @media screen and (min-width: $break-large) {
padding-bottom: 12px; padding-bottom: 8px;
} }
} }
@ -91,21 +91,13 @@
} }
.tx-list-date-wrapper { .tx-list-date-wrapper {
margin-top: 6px;
flex: 1 1 auto; flex: 1 1 auto;
@media screen and (max-width: $break-small) {
margin-top: 6px;
}
@media screen and (min-width: $break-large) {
margin-top: 12px;
}
} }
.tx-list-content-wrapper { .tx-list-content-wrapper {
align-items: stretch; align-items: stretch;
margin-bottom: 4px; margin-bottom: 4px;
margin-top: 2px;
flex: 1 0 auto; flex: 1 0 auto;
width: 100%; width: 100%;
display: flex; display: flex;
@ -115,7 +107,7 @@
font-size: 12px; font-size: 12px;
.tx-list-status { .tx-list-status {
font-size: 14px !important; font-size: 12px !important;
} }
.tx-list-account { .tx-list-account {
@ -129,7 +121,7 @@
.tx-list-fiat-value { .tx-list-fiat-value {
font-size: 12px; font-size: 12px;
line-height: 16px; line-height: 22px;
} }
} }
} }
@ -210,7 +202,7 @@
} }
@media screen and (min-width: $break-large) { @media screen and (min-width: $break-large) {
margin: 0 2.37em; padding: 0 2.37em;
} }
&:last-of-type { &:last-of-type {
@ -259,6 +251,8 @@
} }
.tx-list-fiat-value { .tx-list-fiat-value {
font-size: 12px;
line-height: initial;
text-align: right; text-align: right;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;

@ -8,7 +8,8 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
background: rgba($wallet-balance-bg, 0); background: rgba($wallet-balance-bg, 0);
&--active { &--active {
background: rgba($wallet-balance-bg, 1); background: $manatee;
color: $white;
} }
} }
@ -41,7 +42,7 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
align-items: flex-start; align-items: flex-start;
.token-amount { .token-amount {
font-size: 135%; font-size: 1.5rem;
} }
.fiat-amount { .fiat-amount {
@ -61,11 +62,13 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
} }
} }
} }
}
.balance-icon { .balance-icon {
border-radius: 25px; border-radius: 25px;
width: 45px; width: 50px;
height: 45px; height: 50px;
border: 1px solid $alto; border: 1px solid $alto;
} padding: 5px;
background: $white;
} }

@ -51,14 +51,14 @@
@font-face { @font-face {
font-family: 'DIN NEXT'; font-family: 'DIN NEXT';
src: url('/fonts/DIN NEXT/DIN NEXT W01 Regular.otf') format('opentype'); src: url('/fonts/DIN Next/DIN Next W01 Regular.otf') format('opentype');
font-weight: 400; font-weight: 400;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: 'DIN NEXT Light'; font-family: 'DIN NEXT Light';
src: url('/fonts/DIN NEXT/DIN NEXT W10 Light.otf') format('opentype'); src: url('/fonts/DIN Next/DIN Next W10 Light.otf') format('opentype');
font-weight: 400; font-weight: 400;
font-style: normal; font-style: normal;
} }

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save