diff options
author | Dan Finlay <542863+danfinlay@users.noreply.github.com> | 2019-08-07 05:53:50 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-08-07 05:53:50 +0800 |
commit | db08881d4527e8a037f401ef22b849e52152864f (patch) | |
tree | 6032d7a4ae67371889eece1d8490c26d5a119dd5 /app | |
parent | 4139019d0f4dd83f56da400ca7e0e6d1976d1716 (diff) | |
parent | 86ad9564a064fd6158dab6a3c9e5b10614ef6e68 (diff) | |
download | tangerine-wallet-browser-7.0.0.tar tangerine-wallet-browser-7.0.0.tar.gz tangerine-wallet-browser-7.0.0.tar.bz2 tangerine-wallet-browser-7.0.0.tar.lz tangerine-wallet-browser-7.0.0.tar.xz tangerine-wallet-browser-7.0.0.tar.zst tangerine-wallet-browser-7.0.0.zip |
Merge pull request #6969 from MetaMask/developv7.0.0
Master Version Bump
Diffstat (limited to 'app')
56 files changed, 782 insertions, 441 deletions
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index cc087867a..430d1b50c 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1,4 +1,16 @@ { + "shareAddress": { + "message": "Share Address" + }, + "shareAddressToConnect": { + "message": "Share your address to connect to $1?" + }, + "shareAddressInfo": { + "message": "Sharing your address with $1 will allow you to interact with this dapp. This permission is to protect your privacy by default." + }, + "privacyModeDefault": { + "message": "Privacy Mode is now enabled by default" + }, "privacyMode": { "message": "Privacy Mode" }, @@ -80,12 +92,21 @@ "activityLog": { "message": "activity log" }, + "add": { + "message": "Add" + }, "address": { "message": "Address" }, "addNetwork": { "message": "Add Network" }, + "addRecipient": { + "message": "Add Recipient" + }, + "addressBook": { + "message": "Address Book" + }, "advanced": { "message": "Advanced" }, @@ -98,6 +119,18 @@ "addCustomToken": { "message": "Add custom token" }, + "addToAddressBook": { + "message": "Add to address book" + }, + "addToAddressBookModalPlaceholder": { + "message": "e.g. John D." + }, + "addAlias": { + "message": "Add alias" + }, + "addEthAddress": { + "message": "Add an Ethereum address" + }, "addToken": { "message": "Add Token" }, @@ -172,6 +205,18 @@ "back": { "message": "Back" }, + "backToAll": { + "message": "Back to All" + }, + "backupApprovalNotice": { + "message": "Backup your Secret Recovery code to keep your wallet and funds secure." + }, + "backupApprovalInfo": { + "message": "This secret code is required to recover your wallet in case you lose your device, forget your password, have to re-install MetaMask, or want to access your wallet on another device." + }, + "backupNow": { + "message": "Backup now" + }, "balance": { "message": "Balance" }, @@ -237,9 +282,15 @@ "bytes": { "message": "Bytes" }, + "off": { + "message": "Off" + }, "ok": { "message": "Ok" }, + "on": { + "message": "On" + }, "optionalBlockExplorerUrl": { "message": "Block Explorer URL (optional)" }, @@ -348,6 +399,12 @@ "connectToTrezor": { "message": "Connect to Trezor" }, + "contactList": { + "message": "Contact List" + }, + "contactListDescription": { + "message": "Add, edit, remove, and manage your contacts" + }, "continue": { "message": "Continue" }, @@ -457,6 +514,9 @@ "delete": { "message": "Delete" }, + "deleteAccount": { + "message": "Delete Account" + }, "denExplainer": { "message": "Your DEN is your password-encrypted storage within MetaMask." }, @@ -496,6 +556,9 @@ "directDepositEtherExplainer": { "message": "If you already have some Ether, the quickest way to get Ether in your new wallet by direct deposit." }, + "dismiss": { + "message": "Dismiss" + }, "done": { "message": "Done" }, @@ -523,6 +586,9 @@ "editAccountName": { "message": "Edit Account Name" }, + "editContact":{ + "message": "Edit Contact" + }, "editingTransaction": { "message": "Make changes to your transaction" }, @@ -556,9 +622,24 @@ "endOfFlowMessage8": { "message": "MetaMask cannot recover your seedphrase. Learn more." }, + "endOfFlowMessage9": { + "message": "Learn more." + }, + "endOfFlowMessage10": { + "message": "All Done" + }, "ensNameNotFound": { "message": "ENS name not found" }, + "ensRegistrationError": { + "message": "Error in ENS name registration" + }, + "ensNotFoundOnCurrentNetwork": { + "message": "ENS name not found on the current network. Try switching to Main Ethereum Network." + }, + "enterAnAlias": { + "message": "Enter an alias" + }, "enterPassword": { "message": "Enter password" }, @@ -571,6 +652,9 @@ "eth": { "message": "ETH" }, + "ethereumPublicAddress": { + "message": "Ethereum Public Address" + }, "etherscanView": { "message": "View account on Etherscan" }, @@ -881,6 +965,9 @@ "loadingTokens": { "message": "Loading Tokens..." }, + "loadMore": { + "message": "Load More" + }, "localhost": { "message": "Localhost 8545" }, @@ -902,6 +989,9 @@ "memorizePhrase": { "message": "Memorize this phrase." }, + "memo": { + "message": "memo" + }, "menu": { "message": "Menu" }, @@ -935,6 +1025,12 @@ "myAccounts": { "message": "My Accounts" }, + "myWalletAccounts": { + "message": "My Wallet Accounts" + }, + "myWalletAccountsDescription": { + "message": "All of your MetaMask created accounts will automatically be added to this section." + }, "mustSelectOne": { "message": "Must select at least 1 token." }, @@ -967,10 +1063,16 @@ "newAccount": { "message": "New Account" }, + "newAccountDetectedDialogMessage": { + "message": "New address detected! Click here to add to your address book." + }, "newAccountNumberName": { "message": "Account $1", "description": "Default name of next account to be created on create account screen" }, + "newContact": { + "message": "New Contact" + }, "newContract": { "message": "New Contract" }, @@ -1170,7 +1272,7 @@ "message": "Queue" }, "readdToken": { - "message": "You can add this token back in the future by going go to “Add token” in your accounts options menu." + "message": "You can add this token back in the future by going to “Add token” in your accounts options menu." }, "readMore": { "message": "Read more here." @@ -1181,9 +1283,15 @@ "receive": { "message": "Receive" }, + "recents": { + "message": "Recents" + }, "recipientAddress": { "message": "Recipient Address" }, + "recipientAddressPlaceholder": { + "message": "Search, public address (0x), or ENS" + }, "refundAddress": { "message": "Your Refund Address" }, @@ -1208,6 +1316,15 @@ "resetAccountDescription": { "message": "Resetting your account will clear your transaction history." }, + "deleteNetwork": { + "message": "Delete Network?" + }, + "deleteNetworkDescription": { + "message": "Are you sure you want to delete this network?" + }, + "remindMeLater": { + "message": "Remind me later" + }, "restoreFromSeed": { "message": "Restore account?" }, @@ -1457,7 +1574,7 @@ "message": "there can only be a space between words" }, "speedUp": { - "message": "speed up" + "message": "Speed Up" }, "speedUpTitle": { "message": "Speed Up Transaction" @@ -1652,6 +1769,9 @@ "transfer": { "message": "Transfer" }, + "transferBetweenAccounts": { + "message": "Transfer between my accounts" + }, "transferFrom": { "message": "Transfer From" }, @@ -1732,6 +1852,9 @@ "useOldUI": { "message": "Use old UI" }, + "userName":{ + "message": "Username" + }, "validFileImport": { "message": "You must select a valid file to import." }, @@ -1744,6 +1867,9 @@ "viewinExplorer": { "message": "View in Explorer" }, + "viewContact": { + "message": "View Contact" + }, "viewOnCustomBlockExplorer": { "message": "View at $1" }, @@ -1802,6 +1928,6 @@ "message": "MetaMask will never ask for your seed phrase!" }, "zeroGasPriceOnSpeedUpError": { - "message":"Zero gas price on speed up" + "message": "Zero gas price on speed up" } } diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index 06531b1dc..bf466fdc2 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -1235,7 +1235,7 @@ }, "pending": { "message": "pendiente" - }, + }, "popularTokens": { "message": "Tokens Corrientes" }, @@ -1504,7 +1504,7 @@ "message": "Tu frase semilla privada" }, "zeroGasPriceOnSpeedUpError": { - "message":"No hubo precio de gas al agilizar" + "message": "No hubo precio de gas al agilizar" }, "currencyConversion": { "message": "Cambio de Monedas" diff --git a/app/_locales/ph/messages.json b/app/_locales/ph/messages.json index c8cde27f6..9763bd273 100644 --- a/app/_locales/ph/messages.json +++ b/app/_locales/ph/messages.json @@ -318,7 +318,7 @@ "message": "i-click ito", "description": "tulad ng -i-click dito- para sa mas maraming impormasyon (kasama ng troubleTokenBalances)" }, - "hide": { + "hide": { "message": "Itago" }, "hideToken": { diff --git a/app/_locales/sl/messages.json b/app/_locales/sl/messages.json index d04ba1ee7..563bae1b1 100644 --- a/app/_locales/sl/messages.json +++ b/app/_locales/sl/messages.json @@ -1145,7 +1145,7 @@ }, "seedPhraseAlert": { "message": "Opozorilo o seed phrase" - }, + }, "select": { "message": "Izberi" }, @@ -1549,6 +1549,6 @@ "message": "Vaš zasebni seed phrase" }, "zeroGasPriceOnSpeedUpError": { - "message":"Ničelni gas price na pospešitvi" + "message": "Ničelni gas price na pospešitvi" } } diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index aee6474b6..622e0d818 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -655,5 +655,5 @@ }, "yourSigRequested": { "message": "Chữ ký của bạn đang được yêu cầu" - } + } } diff --git a/app/_locales/zh_TW/messages.json b/app/_locales/zh_TW/messages.json index 18e30188e..2559fc0ad 100644 --- a/app/_locales/zh_TW/messages.json +++ b/app/_locales/zh_TW/messages.json @@ -651,7 +651,7 @@ "description": "status showing that an account has been fully loaded into the keyring" }, "importUsingSeed": { - "message": "利用助憶詞匯入帳戶" , + "message": "利用助憶詞匯入帳戶", "description": "登入頁面下方" }, "importWithSeedPhrase": { @@ -1584,6 +1584,6 @@ "message": "您每一次在交易時,都會看到這個圖案。" }, "zeroGasPriceOnSpeedUpError": { - "message":"加速的 Gas 價格為 0" + "message": "加速的 Gas 價格為 0" } } diff --git a/app/home.html b/app/home.html index 051133cf8..ac17ed307 100644 --- a/app/home.html +++ b/app/home.html @@ -4,6 +4,7 @@ <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1 user-scalable=no"> <title>MetaMask</title> + <link rel="stylesheet" type="text/css" href="./index.css"> </head> <body> <div id="app-content"></div> diff --git a/app/images/check-green-solid.svg b/app/images/check-green-solid.svg new file mode 100644 index 000000000..3e58e8dcc --- /dev/null +++ b/app/images/check-green-solid.svg @@ -0,0 +1,4 @@ +<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M0.833374 10C0.833374 4.9374 4.93743 0.833344 10 0.833344C15.0626 0.833344 19.1667 4.9374 19.1667 10C19.1667 15.0626 15.0626 19.1667 10 19.1667C4.93743 19.1667 0.833374 15.0626 0.833374 10Z" fill="#28A745"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M14.4256 6.70245C14.7511 7.02789 14.7511 7.55553 14.4256 7.88097L9.25303 13.2976C8.9276 13.6231 8.39996 13.6231 8.07452 13.2976L5.57452 10.7976C5.24909 10.4722 5.24909 9.94456 5.57452 9.61912C5.89996 9.29368 6.4276 9.29368 6.75303 9.61912L8.66378 11.5299L13.2471 6.70245C13.5725 6.37702 14.1002 6.37702 14.4256 6.70245Z" fill="white"/> +</svg> diff --git a/app/images/close-gray.svg b/app/images/close-gray.svg new file mode 100755 index 000000000..fca1c4740 --- /dev/null +++ b/app/images/close-gray.svg @@ -0,0 +1,4 @@ +<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg"> +<rect x="0.14917" y="1.09723" width="1.34076" height="15.4188" rx="0.670381" transform="rotate(-45 0.14917 1.09723)" fill="#A1A5B3"/> +<rect x="0.94812" y="11.8508" width="1.34076" height="15.4188" rx="0.670381" transform="rotate(-135 0.94812 11.8508)" fill="#A1A5B3"/> +</svg> diff --git a/app/images/coinswitch_logo.png b/app/images/coinswitch_logo.png Binary files differindex 445ecf02e..aa8f525be 100644 --- a/app/images/coinswitch_logo.png +++ b/app/images/coinswitch_logo.png diff --git a/app/images/ethereum-metamask-chrome.png b/app/images/ethereum-metamask-chrome.png Binary files differindex 0b886babb..ffd0ff146 100644 --- a/app/images/ethereum-metamask-chrome.png +++ b/app/images/ethereum-metamask-chrome.png diff --git a/app/images/icon-64.png b/app/images/icon-64.png Binary files differindex b3019ad65..9a1995874 100644 --- a/app/images/icon-64.png +++ b/app/images/icon-64.png diff --git a/app/images/icons/connect.svg b/app/images/icons/connect.svg new file mode 100644 index 000000000..24543e8d8 --- /dev/null +++ b/app/images/icons/connect.svg @@ -0,0 +1,7 @@ +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path d="M8.00002 9.57037C8.93767 9.57037 9.69778 8.81026 9.69778 7.8726C9.69778 6.93495 8.93767 6.17484 8.00002 6.17484C7.06236 6.17484 6.30225 6.93495 6.30225 7.8726C6.30225 8.81026 7.06236 9.57037 8.00002 9.57037Z" fill="white"/> + <path d="M11.0582 11.6586C10.872 11.6586 10.6857 11.5876 10.5437 11.4455C10.2595 11.1614 10.2595 10.7007 10.5437 10.4165C11.2232 9.73704 11.5975 8.83356 11.5975 7.87259C11.5975 6.91161 11.2232 6.00813 10.5437 5.32865C10.2595 5.04448 10.2595 4.58381 10.5437 4.29964C10.8278 4.01554 11.2886 4.01554 11.5727 4.29964C12.527 5.25398 13.0527 6.52293 13.0527 7.87259C13.0527 9.22224 12.527 10.4912 11.5727 11.4455C11.4306 11.5876 11.2444 11.6586 11.0582 11.6586Z" fill="white"/> + <path d="M4.94175 11.6586C4.75553 11.6586 4.56929 11.5876 4.42724 11.4455C3.4729 10.4912 2.94727 9.22224 2.94727 7.87259C2.94727 6.52293 3.4729 5.25398 4.42724 4.29964C4.71135 4.01554 5.17215 4.01554 5.45626 4.29964C5.74043 4.58381 5.74043 5.04448 5.45626 5.32865C4.77672 6.00813 4.4025 6.91161 4.4025 7.87259C4.4025 8.83356 4.77672 9.73704 5.45626 10.4165C5.74043 10.7007 5.74043 11.1614 5.45626 11.4455C5.3142 11.5876 5.12798 11.6586 4.94175 11.6586Z" fill="white"/> + <path d="M13.1451 13.7453C12.9589 13.7453 12.7727 13.6742 12.6306 13.5322C12.3464 13.248 12.3464 12.7873 12.6306 12.5031C15.1839 9.94985 15.1839 5.79538 12.6306 3.24209C12.3464 2.95792 12.3464 2.49725 12.6306 2.21308C12.9147 1.92897 13.3755 1.92897 13.6596 2.21308C16.7803 5.33374 16.7803 10.4115 13.6596 13.5322C13.5176 13.6742 13.3313 13.7453 13.1451 13.7453Z" fill="white"/> + <path d="M2.855 13.7453C2.66878 13.7453 2.48255 13.6742 2.3405 13.5322C-0.780166 10.4115 -0.780166 5.33374 2.3405 2.21308C2.62461 1.92897 3.08541 1.92897 3.36951 2.21308C3.65368 2.49725 3.65368 2.95792 3.36951 3.24209C0.816221 5.79538 0.816221 9.94985 3.36951 12.5031C3.65368 12.7873 3.65368 13.248 3.36951 13.5322C3.22745 13.6742 3.04123 13.7453 2.855 13.7453Z" fill="white"/> +</svg> diff --git a/app/images/icons/info.svg b/app/images/icons/info.svg new file mode 100644 index 000000000..138811bae --- /dev/null +++ b/app/images/icons/info.svg @@ -0,0 +1,5 @@ +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path fill-rule="evenodd" clip-rule="evenodd" d="M7.99984 2.00001C4.68613 2.00001 1.99984 4.6863 1.99984 8C1.99984 11.3137 4.68613 14 7.99984 14C11.3135 14 13.9998 11.3137 13.9998 8C13.9998 4.6863 11.3135 2.00001 7.99984 2.00001ZM0.666504 8C0.666504 3.94992 3.94975 0.666672 7.99984 0.666672C12.0499 0.666672 15.3332 3.94992 15.3332 8C15.3332 12.0501 12.0499 15.3333 7.99984 15.3333C3.94975 15.3333 0.666504 12.0501 0.666504 8Z" fill="#6A737D"/> + <path fill-rule="evenodd" clip-rule="evenodd" d="M7.99984 7.33334C8.36803 7.33334 8.6665 7.63181 8.6665 8V10.6667C8.6665 11.0349 8.36803 11.3333 7.99984 11.3333C7.63165 11.3333 7.33317 11.0349 7.33317 10.6667V8C7.33317 7.63181 7.63165 7.33334 7.99984 7.33334Z" fill="#6A737D"/> + <path d="M8.6665 5.33334C8.6665 5.70153 8.36803 6 7.99984 6C7.63165 6 7.33317 5.70153 7.33317 5.33334C7.33317 4.96515 7.63165 4.66667 7.99984 4.66667C8.36803 4.66667 8.6665 4.96515 8.6665 5.33334Z" fill="#6A737D"/> +</svg> diff --git a/app/images/key-32.png b/app/images/key-32.png Binary files differindex ca11cacd9..ac9ddcae3 100644 --- a/app/images/key-32.png +++ b/app/images/key-32.png diff --git a/app/images/logo.png b/app/images/logo.png Binary files differindex db6ba7d6c..3013d583b 100644 --- a/app/images/logo.png +++ b/app/images/logo.png diff --git a/app/images/meta-shield.svg b/app/images/meta-shield.svg new file mode 100644 index 000000000..346934dbc --- /dev/null +++ b/app/images/meta-shield.svg @@ -0,0 +1,3 @@ +<svg width="20" height="24" viewBox="0 0 20 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M0 9.71495V3.57158L10 0.872803L20 3.57158V9.71495C20 16.0442 15.7999 21.4041 10 23.2357C4.19995 21.4041 0 16.0442 0 9.71495ZM15.3879 10.7277L15.6232 10.898L15.0819 11.5366L15.9057 14.1001L15.1431 16.7158L12.4738 15.9779L11.956 16.4036L10.9014 17.1367H9.09833L8.04379 16.4036L7.52596 15.9779L4.85671 16.7158L4.09874 14.1001L4.91784 11.5366L4.37644 10.898L4.61188 10.7277L4.23524 10.3825L4.52242 10.1553L4.14578 9.8669L4.4 9.6777L3.99989 7.74783L4.59302 5.95527L8.41104 7.38838H11.5887L15.4067 5.95527L15.9999 7.74783L15.6045 9.6777L15.854 9.8669L15.4773 10.1553L15.7644 10.3825L15.3879 10.7277ZM7.38499 13.1652L8.47065 12.6641L8.92346 13.6225L7.38499 13.1652ZM10.7685 13.6225L11.2197 12.6641L12.307 13.1652L10.7685 13.6225ZM9.24343 14.9001H10.4439L10.6519 15.0662L10.7689 16.178L10.6607 16.0674H9.02677L8.92279 16.178L9.03541 15.0662L9.24343 14.9001Z" fill="white"/> +</svg> diff --git a/app/images/pw128x128.png b/app/images/pw128x128.png Binary files differindex a0eb1b730..c1ef2b5b2 100644 --- a/app/images/pw128x128.png +++ b/app/images/pw128x128.png diff --git a/app/images/qr-blue.svg b/app/images/qr-blue.svg new file mode 100644 index 000000000..54434295a --- /dev/null +++ b/app/images/qr-blue.svg @@ -0,0 +1,7 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path fill-rule="evenodd" clip-rule="evenodd" d="M16.32 8H8.63997L8.63997 16H16.32V8ZM8.63997 6C7.57958 6 6.71997 6.89543 6.71997 8V16C6.71997 17.1046 7.57958 18 8.63997 18H16.32C17.3804 18 18.24 17.1046 18.24 16V8C18.24 6.89543 17.3804 6 16.32 6H8.63997Z" fill="#037DD6"/> + <path fill-rule="evenodd" clip-rule="evenodd" d="M16.32 1C16.32 0.447715 16.7498 0 17.28 0H21.12C22.7106 0 24 1.34315 24 3V7C24 7.55228 23.5702 8 23.04 8C22.5098 8 22.08 7.55228 22.08 7V3C22.08 2.44772 21.6502 2 21.12 2H17.28C16.7498 2 16.32 1.55228 16.32 1Z" fill="#037DD6"/> + <path fill-rule="evenodd" clip-rule="evenodd" d="M23.04 16C23.5702 16 24 16.4477 24 17L24 21C24 22.6569 22.7106 24 21.12 24L17.28 24C16.7498 24 16.32 23.5523 16.32 23C16.32 22.4477 16.7498 22 17.28 22L21.12 22C21.6502 22 22.08 21.5523 22.08 21L22.08 17C22.08 16.4477 22.5098 16 23.04 16Z" fill="#037DD6"/> + <path fill-rule="evenodd" clip-rule="evenodd" d="M7.67999 23C7.67999 23.5523 7.25019 24 6.71999 24L2.87999 24C1.28941 24 -6.563e-06 22.6569 -6.42394e-06 21L-6.08824e-06 17C-6.04189e-06 16.4477 0.429801 16 0.959994 16C1.49019 16 1.91999 16.4477 1.91999 17L1.91999 21C1.91999 21.5523 2.3498 22 2.87999 22L6.71999 22C7.25019 22 7.67999 22.4477 7.67999 23Z" fill="#037DD6"/> + <path fill-rule="evenodd" clip-rule="evenodd" d="M0.96 8C0.429807 8 5.87108e-08 7.55228 1.31134e-07 7L6.55671e-07 3C8.72941e-07 1.34315 1.28942 1.69087e-07 2.88 3.77666e-07L6.72 8.81222e-07C7.25019 9.50748e-07 7.68 0.447716 7.68 1C7.68 1.55229 7.25019 2 6.72 2L2.88 2C2.34981 2 1.92 2.44772 1.92 3L1.92 7C1.92 7.55229 1.49019 8 0.96 8Z" fill="#037DD6"/> +</svg> diff --git a/app/images/search-black.svg b/app/images/search-black.svg new file mode 100644 index 000000000..7b7db5124 --- /dev/null +++ b/app/images/search-black.svg @@ -0,0 +1,4 @@ +<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path fill-rule="evenodd" clip-rule="evenodd" d="M9.16667 3.33341C5.94501 3.33341 3.33334 5.94509 3.33334 9.16675C3.33334 12.3884 5.94501 15.0001 9.16667 15.0001C12.3883 15.0001 15 12.3884 15 9.16675C15 5.94509 12.3883 3.33341 9.16667 3.33341ZM1.66667 9.16675C1.66667 5.02461 5.02454 1.66675 9.16667 1.66675C13.3088 1.66675 16.6667 5.02461 16.6667 9.16675C16.6667 13.3089 13.3088 16.6667 9.16667 16.6667C5.02454 16.6667 1.66667 13.3089 1.66667 9.16675Z" fill="black"/> + <path fill-rule="evenodd" clip-rule="evenodd" d="M13.2857 13.2858C13.6112 12.9604 14.1388 12.9604 14.4642 13.2858L18.0892 16.9108C18.4147 17.2363 18.4147 17.7639 18.0892 18.0893C17.7638 18.4148 17.2362 18.4148 16.9107 18.0893L13.2857 14.4643C12.9603 14.1389 12.9603 13.6113 13.2857 13.2858Z" fill="black"/> +</svg> diff --git a/app/images/shapeshift logo.png b/app/images/shapeshift logo.png Binary files differindex ac8faba5b..a8b47d31e 100644 --- a/app/images/shapeshift logo.png +++ b/app/images/shapeshift logo.png diff --git a/app/manifest.json b/app/manifest.json index d994e7518..1ca2215eb 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "6.7.3", + "version": "7.0.0", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", @@ -21,7 +21,8 @@ }, "applications": { "gecko": { - "id": "webextension@metamask.io" + "id": "webextension@metamask.io", + "strict_min_version": "60.0" } }, "default_locale": "en", @@ -85,4 +86,4 @@ "*" ] } -}
\ No newline at end of file +} diff --git a/app/notification.html b/app/notification.html index 82bf95ada..eba290c15 100644 --- a/app/notification.html +++ b/app/notification.html @@ -28,6 +28,7 @@ margin-top: 1rem; } </style> + <link rel="stylesheet" type="text/css" href="./index.css"> </head> <body class="notification" style="height:600px;"> <div id="app-content"> diff --git a/app/popup.html b/app/popup.html index 1ba0fda94..3a6709eaf 100644 --- a/app/popup.html +++ b/app/popup.html @@ -4,6 +4,7 @@ <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1 user-scalable=no"> <title>MetaMask</title> + <link rel="stylesheet" type="text/css" href="./index.css"> </head> <body style="width:357px; height:600px;"> <div id="app-content"></div> diff --git a/app/scripts/background.js b/app/scripts/background.js index cca0d4709..8e65bd5a4 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -116,7 +116,6 @@ setupMetamaskMeshMetrics() * @property {boolean} useBlockie - Indicates preferred user identicon format. True for blockie, false for Jazzicon. * @property {Object} featureFlags - An object for optional feature flags. * @property {string} networkEndpointType - TODO: Document - * @property {boolean} isRevealingSeedWords - True if seed words are currently being recovered, and should be shown to user. * @property {boolean} welcomeScreen - True if welcome screen should be shown. * @property {string} currentLocale - A locale string matching the user's preferred display language. * @property {Object} provider - The current selected network provider. @@ -253,7 +252,6 @@ function setupController (initState, initLangCode) { const controller = new MetamaskController({ // User confirmation callbacks: showUnconfirmedMessage: triggerUi, - unlockAccountMessage: triggerUi, showUnapprovedTx: triggerUi, openPopup: openPopup, closePopup: notificationManager.closePopup.bind(notificationManager), diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 0c55ae39f..7415c5fe9 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -10,7 +10,7 @@ const extension = require('extensionizer') const PortStream = require('extension-port-stream') const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js')).toString() -const inpageSuffix = '//# sourceURL=' + extension.extension.getURL('inpage.js') + '\n' +const inpageSuffix = '//# sourceURL=' + extension.runtime.getURL('inpage.js') + '\n' const inpageBundle = inpageContent + inpageSuffix // Eventually this streaming injection could be replaced with: @@ -114,6 +114,7 @@ function forwardTrafficBetweenMuxers (channelName, muxA, muxB) { async function setupPublicApi (outStream) { const api = { + forceReloadSite: (cb) => cb(null, forceReloadSite()), getSiteMetadata: (cb) => cb(null, getSiteMetadata()), } const dnode = Dnode(api) @@ -306,3 +307,10 @@ async function domIsReady () { // wait for load await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve, { once: true })) } + +/** + * Reloads the site + */ +function forceReloadSite () { + window.location.reload() +} diff --git a/app/scripts/controllers/computed-balances.js b/app/scripts/controllers/computed-balances.js index e04ce2ef7..caa061df4 100644 --- a/app/scripts/controllers/computed-balances.js +++ b/app/scripts/controllers/computed-balances.js @@ -65,7 +65,7 @@ class ComputedbalancesController { syncAllAccountsFromStore (store) { const upstream = Object.keys(store.accounts) const balances = Object.keys(this.balances) - .map(address => this.balances[address]) + .map(address => this.balances[address]) // Follow new addresses for (const address in balances) { diff --git a/app/scripts/controllers/detect-tokens.js b/app/scripts/controllers/detect-tokens.js index e6940c613..e6e993073 100644 --- a/app/scripts/controllers/detect-tokens.js +++ b/app/scripts/controllers/detect-tokens.js @@ -47,14 +47,14 @@ class DetectTokensController { } tokensToDetect.forEach((tokenAddress, index) => { const balance = result[index] - if (!balance.isZero()) { + if (balance && !balance.isZero()) { this._preferences.addToken(tokenAddress, contracts[tokenAddress].symbol, contracts[tokenAddress].decimals) } }) }) } - /** + /** * Find if selectedAddress has tokens with contract in contractAddress. * * @param {string} contractAddress Hex address of the token contract to explore. diff --git a/app/scripts/controllers/network/contract-addresses.js b/app/scripts/controllers/network/contract-addresses.js index 5cd7da1d0..f9385accd 100644 --- a/app/scripts/controllers/network/contract-addresses.js +++ b/app/scripts/controllers/network/contract-addresses.js @@ -4,8 +4,8 @@ const SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN = '0xb8e671734ce5c8d7dfbbea5574fa4cf3 const SINGLE_CALL_BALANCES_ADDRESS_KOVAN = '0xb1d3fbb2f83aecd196f474c16ca5d9cffa0d0ffc' module.exports = { - SINGLE_CALL_BALANCES_ADDRESS, - SINGLE_CALL_BALANCES_ADDRESS_RINKEBY, - SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN, - SINGLE_CALL_BALANCES_ADDRESS_KOVAN, + SINGLE_CALL_BALANCES_ADDRESS, + SINGLE_CALL_BALANCES_ADDRESS_RINKEBY, + SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN, + SINGLE_CALL_BALANCES_ADDRESS_KOVAN, } diff --git a/app/scripts/controllers/onboarding.js b/app/scripts/controllers/onboarding.js new file mode 100644 index 000000000..18fec4993 --- /dev/null +++ b/app/scripts/controllers/onboarding.js @@ -0,0 +1,43 @@ +const ObservableStore = require('obs-store') +const extend = require('xtend') + +/** + * @typedef {Object} InitState + * @property {Boolean} seedPhraseBackedUp Indicates whether the user has completed the seed phrase backup challenge + */ + +/** + * @typedef {Object} OnboardingOptions + * @property {InitState} initState The initial controller state + */ + +/** + * Controller responsible for maintaining + * a cache of account balances in local storage + */ +class OnboardingController { + /** + * Creates a new controller instance + * + * @param {OnboardingOptions} [opts] Controller configuration parameters + */ + constructor (opts = {}) { + const initState = extend({ + seedPhraseBackedUp: null, + }, opts.initState) + this.store = new ObservableStore(initState) + } + + setSeedPhraseBackedUp (newSeedPhraseBackUpState) { + this.store.updateState({ + seedPhraseBackedUp: newSeedPhraseBackUpState, + }) + } + + getSeedPhraseBackedUp () { + return this.store.getState().seedPhraseBackedUp + } + +} + +module.exports = OnboardingController diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index acf952bb1..d480834f5 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -49,13 +49,12 @@ class PreferencesController { currentLocale: opts.initLangCode, identities: {}, lostIdentities: {}, - seedWords: null, forgottenPassword: false, preferences: { useNativeCurrencyAsPrimaryCurrency: true, }, completedOnboarding: false, - completedUiMigration: true, + migratedPrivacyMode: false, metaMetricsId: null, metaMetricsSendCount: 0, }, opts.initState) @@ -70,7 +69,7 @@ class PreferencesController { return this.setFeatureFlag(key, value) } } -// PUBLIC METHODS + // PUBLIC METHODS /** * Sets the {@code forgottenPassword} state property @@ -81,14 +80,6 @@ class PreferencesController { } /** - * Sets the {@code seedWords} seed words - * @param {string|null} seedWords the seed words - */ - setSeedWords (seedWords) { - this.store.updateState({ seedWords }) - } - - /** * Setter for the `useBlockie` property * * @param {boolean} val Whether or not the user prefers blockie indicators @@ -139,9 +130,9 @@ class PreferencesController { * @param {String} type Indicates the type of first time flow - create or import - the user wishes to follow * */ - setFirstTimeFlowType (type) { - this.store.updateState({ firstTimeFlowType: type }) - } + setFirstTimeFlowType (type) { + this.store.updateState({ firstTimeFlowType: type }) + } getSuggestedTokens () { @@ -503,22 +494,22 @@ class PreferencesController { * @returns {Promise<array>} Promise resolving to updated frequentRpcList. * */ - addToFrequentRpcList (url, chainId, ticker = 'ETH', nickname = '', rpcPrefs = {}) { - const rpcList = this.getFrequentRpcListDetail() - const index = rpcList.findIndex((element) => { return element.rpcUrl === url }) - if (index !== -1) { - rpcList.splice(index, 1) - } - if (url !== 'http://localhost:8545') { - let checkedChainId - if (!!chainId && !Number.isNaN(parseInt(chainId))) { - checkedChainId = chainId - } - rpcList.push({ rpcUrl: url, chainId: checkedChainId, ticker, nickname, rpcPrefs }) + addToFrequentRpcList (url, chainId, ticker = 'ETH', nickname = '', rpcPrefs = {}) { + const rpcList = this.getFrequentRpcListDetail() + const index = rpcList.findIndex((element) => { return element.rpcUrl === url }) + if (index !== -1) { + rpcList.splice(index, 1) + } + if (url !== 'http://localhost:8545') { + let checkedChainId + if (!!chainId && !Number.isNaN(parseInt(chainId))) { + checkedChainId = chainId } - this.store.updateState({ frequentRpcListDetail: rpcList }) - return Promise.resolve(rpcList) + rpcList.push({ rpcUrl: url, chainId: checkedChainId, ticker, nickname, rpcPrefs }) } + this.store.updateState({ frequentRpcListDetail: rpcList }) + return Promise.resolve(rpcList) + } /** * Removes custom RPC url from state. @@ -613,12 +604,11 @@ class PreferencesController { return Promise.resolve(true) } - /** - * Sets the {@code completedUiMigration} state to {@code true}, indicating that the user has completed the UI switch. - */ - completeUiMigration () { - this.store.updateState({ completedUiMigration: true }) - return Promise.resolve(true) + unsetMigratedPrivacyMode () { + this.store.updateState({ + migratedPrivacyMode: false, + }) + return Promise.resolve() } // diff --git a/app/scripts/controllers/provider-approval.js b/app/scripts/controllers/provider-approval.js index 06c499780..5d565c385 100644 --- a/app/scripts/controllers/provider-approval.js +++ b/app/scripts/controllers/provider-approval.js @@ -18,12 +18,13 @@ class ProviderApprovalController extends SafeEventEmitter { */ constructor ({ closePopup, keyringController, openPopup, preferencesController } = {}) { super() - this.approvedOrigins = {} this.closePopup = closePopup this.keyringController = keyringController this.openPopup = openPopup this.preferencesController = preferencesController this.store = new ObservableStore({ + approvedOrigins: {}, + dismissedOrigins: {}, providerRequests: [], }) } @@ -45,7 +46,7 @@ class ProviderApprovalController extends SafeEventEmitter { } // register the provider request const metadata = await getSiteMetadata(origin) - this._handleProviderRequest(origin, metadata.name, metadata.icon, false, null) + this._handleProviderRequest(origin, metadata.name, metadata.icon) // wait for resolution of request const approved = await new Promise(resolve => this.once(`resolvedRequest:${origin}`, ({ approved }) => resolve(approved))) if (approved) { @@ -63,10 +64,12 @@ class ProviderApprovalController extends SafeEventEmitter { * @param {string} siteTitle - The title of the document requesting full provider access * @param {string} siteImage - The icon of the window requesting full provider access */ - _handleProviderRequest (origin, siteTitle, siteImage, force, tabID) { - this.store.updateState({ providerRequests: [{ origin, siteTitle, siteImage, tabID }] }) + _handleProviderRequest (origin, siteTitle, siteImage) { + this.store.updateState({ providerRequests: [{ origin, siteTitle, siteImage }] }) const isUnlocked = this.keyringController.memStore.getState().isUnlocked - if (!force && this.approvedOrigins[origin] && this.caching && isUnlocked) { + const { approvedOrigins, dismissedOrigins } = this.store.getState() + const originAlreadyHandled = approvedOrigins[origin] || dismissedOrigins[origin] + if (originAlreadyHandled && this.caching && isUnlocked) { return } this.openPopup && this.openPopup() @@ -78,11 +81,27 @@ class ProviderApprovalController extends SafeEventEmitter { * @param {string} origin - origin of the domain that had provider access approved */ approveProviderRequestByOrigin (origin) { - this.closePopup && this.closePopup() - const requests = this.store.getState().providerRequests - const providerRequests = requests.filter(request => request.origin !== origin) - this.store.updateState({ providerRequests }) - this.approvedOrigins[origin] = true + if (this.closePopup) { + this.closePopup() + } + + const { approvedOrigins, dismissedOrigins, providerRequests } = this.store.getState() + + let _dismissedOrigins = dismissedOrigins + if (dismissedOrigins[origin]) { + _dismissedOrigins = Object.assign({}, dismissedOrigins) + delete _dismissedOrigins[origin] + } + + const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin) + this.store.updateState({ + approvedOrigins: { + ...approvedOrigins, + [origin]: true, + }, + dismissedOrigins: _dismissedOrigins, + providerRequests: remainingProviderRequests, + }) this.emit(`resolvedRequest:${origin}`, { approved: true }) } @@ -92,19 +111,62 @@ class ProviderApprovalController extends SafeEventEmitter { * @param {string} origin - origin of the domain that had provider access approved */ rejectProviderRequestByOrigin (origin) { - this.closePopup && this.closePopup() - const requests = this.store.getState().providerRequests - const providerRequests = requests.filter(request => request.origin !== origin) - this.store.updateState({ providerRequests }) - delete this.approvedOrigins[origin] + if (this.closePopup) { + this.closePopup() + } + + const { approvedOrigins, providerRequests, dismissedOrigins } = this.store.getState() + const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin) + + // We're cloning and deleting keys here because we don't want to keep unneeded keys + const _approvedOrigins = Object.assign({}, approvedOrigins) + delete _approvedOrigins[origin] + + this.store.putState({ + approvedOrigins: _approvedOrigins, + providerRequests: remainingProviderRequests, + dismissedOrigins: { + ...dismissedOrigins, + [origin]: true, + }, + }) this.emit(`resolvedRequest:${origin}`, { approved: false }) } /** + * Silently approves access to a full Ethereum provider API for the origin + * + * @param {string} origin - origin of the domain that had provider access approved + */ + forceApproveProviderRequestByOrigin (origin) { + const { approvedOrigins, dismissedOrigins, providerRequests } = this.store.getState() + const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin) + + let _dismissedOrigins = dismissedOrigins + if (dismissedOrigins[origin]) { + _dismissedOrigins = Object.assign({}, dismissedOrigins) + delete _dismissedOrigins[origin] + } + + this.store.updateState({ + approvedOrigins: { + ...approvedOrigins, + [origin]: true, + }, + dismissedOrigins: _dismissedOrigins, + providerRequests: remainingProviderRequests, + }) + + this.emit(`forceResolvedRequest:${origin}`, { approved: true, forced: true }) + } + + /** * Clears any cached approvals for user-approved origins */ clearApprovedOrigins () { - this.approvedOrigins = {} + this.store.updateState({ + approvedOrigins: {}, + }) } /** @@ -115,8 +177,7 @@ class ProviderApprovalController extends SafeEventEmitter { */ shouldExposeAccounts (origin) { const privacyMode = this.preferencesController.getFeatureFlags().privacyMode - const result = !privacyMode || Boolean(this.approvedOrigins[origin]) - return result + return !privacyMode || Boolean(this.store.getState().approvedOrigins[origin]) } } diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index c4371c25b..a33b46851 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -129,7 +129,7 @@ class TransactionController extends EventEmitter { } } -/** + /** Adds a tx to the txlist @emits ${txMeta.id}:unapproved */ @@ -220,7 +220,7 @@ class TransactionController extends EventEmitter { return txMeta } -/** + /** adds the tx gas defaults: gas && gasPrice @param txMeta {Object} - the txMeta object @returns {Promise<object>} resolves with txMeta @@ -495,9 +495,9 @@ class TransactionController extends EventEmitter { this.txStateManager.updateTx(txMeta, 'transactions#setTxHash') } -// -// PRIVATE METHODS -// + // + // PRIVATE METHODS + // /** maps methods for convenience*/ _mapMethods () { /** @returns the state in transaction controller */ @@ -537,14 +537,14 @@ class TransactionController extends EventEmitter { loadingDefaults: true, }).forEach((tx) => { this.addTxGasDefaults(tx) - .then((txMeta) => { - txMeta.loadingDefaults = false - this.txStateManager.updateTx(txMeta, 'transactions: gas estimation for tx on boot') - }).catch((error) => { - tx.loadingDefaults = false - this.txStateManager.updateTx(tx, 'failed to estimate gas during boot cleanup.') - this.txStateManager.setTxStatusFailed(tx.id, error) - }) + .then((txMeta) => { + txMeta.loadingDefaults = false + this.txStateManager.updateTx(txMeta, 'transactions: gas estimation for tx on boot') + }).catch((error) => { + tx.loadingDefaults = false + this.txStateManager.updateTx(tx, 'failed to estimate gas during boot cleanup.') + this.txStateManager.setTxStatusFailed(tx.id, error) + }) }) this.txStateManager.getFilteredTxList({ diff --git a/app/scripts/controllers/transactions/lib/tx-state-history-helper.js b/app/scripts/controllers/transactions/lib/tx-state-history-helper.js index 4562568e9..76fc5c35b 100644 --- a/app/scripts/controllers/transactions/lib/tx-state-history-helper.js +++ b/app/scripts/controllers/transactions/lib/tx-state-history-helper.js @@ -17,10 +17,10 @@ function migrateFromSnapshotsToDiffs (longHistory) { return ( longHistory // convert non-initial history entries into diffs - .map((entry, index) => { - if (index === 0) return entry - return generateHistoryEntry(longHistory[index - 1], entry) - }) + .map((entry, index) => { + if (index === 0) return entry + return generateHistoryEntry(longHistory[index - 1], entry) + }) ) } diff --git a/app/scripts/controllers/transactions/lib/util.js b/app/scripts/controllers/transactions/lib/util.js index 5a8a0cefe..0d2ddddef 100644 --- a/app/scripts/controllers/transactions/lib/util.js +++ b/app/scripts/controllers/transactions/lib/util.js @@ -26,7 +26,7 @@ const normalizers = { gasPrice: gasPrice => addHexPrefix(gasPrice), } - /** +/** normalizes txParams @param txParams {object} @returns {object} normalized txParams @@ -40,7 +40,7 @@ function normalizeTxParams (txParams, LowerCase) { return normalizedTxParams } - /** +/** validates txParams @param txParams {object} */ @@ -59,7 +59,7 @@ function validateTxParams (txParams) { } } - /** +/** validates the from field in txParams @param txParams {object} */ @@ -68,7 +68,7 @@ function validateFrom (txParams) { if (!isValidAddress(txParams.from)) throw new Error('Invalid from address') } - /** +/** validates the to field in txParams @param txParams {object} */ @@ -85,7 +85,7 @@ function validateRecipient (txParams) { return txParams } - /** +/** @returns an {array} of states that can be considered final */ function getFinalStates () { diff --git a/app/scripts/controllers/transactions/pending-tx-tracker.js b/app/scripts/controllers/transactions/pending-tx-tracker.js index bc11f6633..1ef3be36e 100644 --- a/app/scripts/controllers/transactions/pending-tx-tracker.js +++ b/app/scripts/controllers/transactions/pending-tx-tracker.js @@ -186,7 +186,7 @@ class PendingTransactionTracker extends EventEmitter { this.emit('tx:warning', txMeta, err) } } - /** + /** checks to see if if the tx's nonce has been used by another transaction @param txMeta {Object} - txMeta object @emits tx:dropped @@ -198,7 +198,7 @@ class PendingTransactionTracker extends EventEmitter { const nextNonce = await this.query.getTransactionCount(from) const { blockNumber } = await this.query.getTransactionByHash(hash) || {} if (!blockNumber && parseInt(nextNonce) > parseInt(nonce)) { - return true + return true } return false } diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js index 2aa28c270..a91b59918 100644 --- a/app/scripts/controllers/transactions/tx-state-manager.js +++ b/app/scripts/controllers/transactions/tx-state-manager.js @@ -34,7 +34,7 @@ class TransactionStateManager extends EventEmitter { this.store = new ObservableStore( extend({ transactions: [], - }, initState)) + }, initState)) this.txHistoryLimit = txHistoryLimit this.getNetwork = getNetwork } @@ -245,7 +245,7 @@ class TransactionStateManager extends EventEmitter { }) } -/** + /** @param opts {object} - an object of fields to search for eg:<br> let <code>thingsToLookFor = {<br> to: '0x0..',<br> @@ -403,9 +403,9 @@ class TransactionStateManager extends EventEmitter { // Update state this._saveTxList(otherAccountTxs) } -// -// PRIVATE METHODS -// + // + // PRIVATE METHODS + // // STATUS METHODS // statuses: diff --git a/app/scripts/edge-encryptor.js b/app/scripts/edge-encryptor.js index dcb06873b..012672ed2 100644 --- a/app/scripts/edge-encryptor.js +++ b/app/scripts/edge-encryptor.js @@ -14,23 +14,23 @@ class EdgeEncryptor { * @returns {Promise<string>} Promise resolving to an object with ciphertext */ encrypt (password, dataObject) { - var salt = this._generateSalt() - return this._keyFromPassword(password, salt) - .then(function (key) { - var data = JSON.stringify(dataObject) - var dataBuffer = Unibabel.utf8ToBuffer(data) - var vector = global.crypto.getRandomValues(new Uint8Array(16)) - var resultbuffer = asmcrypto.AES_GCM.encrypt(dataBuffer, key, vector) + var salt = this._generateSalt() + return this._keyFromPassword(password, salt) + .then(function (key) { + var data = JSON.stringify(dataObject) + var dataBuffer = Unibabel.utf8ToBuffer(data) + var vector = global.crypto.getRandomValues(new Uint8Array(16)) + var resultbuffer = asmcrypto.AES_GCM.encrypt(dataBuffer, key, vector) - var buffer = new Uint8Array(resultbuffer) - var vectorStr = Unibabel.bufferToBase64(vector) - var vaultStr = Unibabel.bufferToBase64(buffer) - return JSON.stringify({ - data: vaultStr, - iv: vectorStr, - salt: salt, - }) - }) + var buffer = new Uint8Array(resultbuffer) + var vectorStr = Unibabel.bufferToBase64(vector) + var vaultStr = Unibabel.bufferToBase64(buffer) + return JSON.stringify({ + data: vaultStr, + iv: vectorStr, + salt: salt, + }) + }) } /** @@ -41,25 +41,25 @@ class EdgeEncryptor { * @returns {Promise<Object>} Promise resolving to copy of decrypted object */ decrypt (password, text) { - const payload = JSON.parse(text) - const salt = payload.salt - return this._keyFromPassword(password, salt) - .then(function (key) { - const encryptedData = Unibabel.base64ToBuffer(payload.data) - const vector = Unibabel.base64ToBuffer(payload.iv) - return new Promise((resolve, reject) => { - var result - try { - result = asmcrypto.AES_GCM.decrypt(encryptedData, key, vector) - } catch (err) { - return reject(new Error('Incorrect password')) - } - const decryptedData = new Uint8Array(result) - const decryptedStr = Unibabel.bufferToUtf8(decryptedData) - const decryptedObj = JSON.parse(decryptedStr) - resolve(decryptedObj) - }) - }) + const payload = JSON.parse(text) + const salt = payload.salt + return this._keyFromPassword(password, salt) + .then(function (key) { + const encryptedData = Unibabel.base64ToBuffer(payload.data) + const vector = Unibabel.base64ToBuffer(payload.iv) + return new Promise((resolve, reject) => { + var result + try { + result = asmcrypto.AES_GCM.decrypt(encryptedData, key, vector) + } catch (err) { + return reject(new Error('Incorrect password')) + } + const decryptedData = new Uint8Array(result) + const decryptedStr = Unibabel.bufferToUtf8(decryptedData) + const decryptedObj = JSON.parse(decryptedStr) + resolve(decryptedObj) + }) + }) } /** @@ -72,12 +72,14 @@ class EdgeEncryptor { */ _keyFromPassword (password, salt) { - var passBuffer = Unibabel.utf8ToBuffer(password) - var saltBuffer = Unibabel.base64ToBuffer(salt) - return new Promise((resolve) => { - var key = asmcrypto.PBKDF2_HMAC_SHA256.bytes(passBuffer, saltBuffer, 10000) - resolve(key) - }) + var passBuffer = Unibabel.utf8ToBuffer(password) + var saltBuffer = Unibabel.base64ToBuffer(salt) + const iterations = 10000 + const length = 32 // SHA256 hash size + return new Promise((resolve) => { + var key = asmcrypto.Pbkdf2HmacSha256(passBuffer, saltBuffer, iterations, length) + resolve(key) + }) } /** @@ -87,10 +89,10 @@ class EdgeEncryptor { * @returns {string} Randomized base64 encoded data */ _generateSalt (byteCount = 32) { - var view = new Uint8Array(byteCount) - global.crypto.getRandomValues(view) - var b64encoded = btoa(String.fromCharCode.apply(null, view)) - return b64encoded + var view = new Uint8Array(byteCount) + global.crypto.getRandomValues(view) + var b64encoded = btoa(String.fromCharCode.apply(null, view)) + return b64encoded } } diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index fb16dd43d..a94787b05 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -1,4 +1,36 @@ /*global Web3*/ + + +// need to make sure we aren't affected by overlapping namespaces +// and that we dont affect the app with our namespace +// mostly a fix for web3's BigNumber if AMD's "define" is defined... +let __define + +/** + * Caches reference to global define object and deletes it to + * avoid conflicts with other global define objects, such as + * AMD's define function + */ +const cleanContextForImports = () => { + __define = global.define + try { + global.define = undefined + } catch (_) { + console.warn('MetaMask - global.define could not be deleted.') + } +} + +/** + * Restores global define object from cached reference + */ +const restoreContextAfterImports = () => { + try { + global.define = __define + } catch (_) { + console.warn('MetaMask - global.define could not be overwritten.') + } +} + cleanContextForImports() require('web3/dist/web3.min.js') const log = require('loglevel') @@ -46,6 +78,20 @@ inpageProvider.enable = function ({ force } = {}) { // this will be default true so it does not break any old apps. inpageProvider.autoRefreshOnNetworkChange = true + +// publicConfig isn't populated until we get a message from background. +// Using this getter will ensure the state is available +const getPublicConfigWhenReady = async () => { + const store = inpageProvider.publicConfigStore + let state = store.getState() + // if state is missing, wait for first update + if (!state.networkVersion) { + state = await new Promise(resolve => store.once('update', resolve)) + console.log('new state', state) + } + return state +} + // add metamask-specific convenience methods inpageProvider._metamask = new Proxy({ /** @@ -87,21 +133,8 @@ inpageProvider._metamask = new Proxy({ }, }) -// publicConfig isn't populated until we get a message from background. -// Using this getter will ensure the state is available -async function getPublicConfigWhenReady () { - const store = inpageProvider.publicConfigStore - let state = store.getState() - // if state is missing, wait for first update - if (!state.networkVersion) { - state = await new Promise(resolve => store.once('update', resolve)) - console.log('new state', state) - } - return state -} - // Work around for web3@1.0 deleting the bound `sendAsync` but not the unbound -// `sendAsync` method on the prototype, causing `this` reference issues with drizzle +// `sendAsync` method on the prototype, causing `this` reference issues const proxiedInpageProvider = new Proxy(inpageProvider, { // straight up lie that we deleted the property so that it doesnt // throw an error in strict mode @@ -161,33 +194,3 @@ inpageProvider.publicConfigStore.subscribe(function (state) { window.postMessage('onboardingcomplete', '*') } }) - -// need to make sure we aren't affected by overlapping namespaces -// and that we dont affect the app with our namespace -// mostly a fix for web3's BigNumber if AMD's "define" is defined... -let __define - -/** - * Caches reference to global define object and deletes it to - * avoid conflicts with other global define objects, such as - * AMD's define function - */ -function cleanContextForImports () { - __define = global.define - try { - global.define = undefined - } catch (_) { - console.warn('MetaMask - global.define could not be deleted.') - } -} - -/** - * Restores global define object from cached reference - */ -function restoreContextAfterImports () { - try { - global.define = __define - } catch (_) { - console.warn('MetaMask - global.define could not be overwritten.') - } -} diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js index 24c5ef7ee..1cbf867cb 100644 --- a/app/scripts/lib/account-tracker.js +++ b/app/scripts/lib/account-tracker.js @@ -183,23 +183,23 @@ class AccountTracker { switch (currentNetwork) { case MAINNET_CODE: - await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS) - break + await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS) + break case RINKEYBY_CODE: - await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS_RINKEBY) - break + await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS_RINKEBY) + break case ROPSTEN_CODE: - await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN) - break + await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN) + break case KOVAN_CODE: - await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS_KOVAN) - break + await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS_KOVAN) + break default: - await Promise.all(addresses.map(this._updateAccount.bind(this))) + await Promise.all(addresses.map(this._updateAccount.bind(this))) } } diff --git a/app/scripts/lib/backend-metametrics.js b/app/scripts/lib/backend-metametrics.js index e3c163c1a..ad7874ead 100644 --- a/app/scripts/lib/backend-metametrics.js +++ b/app/scripts/lib/backend-metametrics.js @@ -15,11 +15,11 @@ function backEndMetaMetricsEvent (metaMaskState, eventData) { const stateEventData = getMetaMetricState({ metamask: metaMaskState }) if (stateEventData.participateInMetaMetrics) { - sendMetaMetricsEvent({ - ...stateEventData, - ...eventData, - url: METAMETRICS_TRACKING_URL + '/backend', - }) + sendMetaMetricsEvent({ + ...stateEventData, + ...eventData, + url: METAMETRICS_TRACKING_URL + '/backend', + }) } } diff --git a/app/scripts/lib/createStreamSink.js b/app/scripts/lib/createStreamSink.js index b93dbc089..eb9d0bd1a 100644 --- a/app/scripts/lib/createStreamSink.js +++ b/app/scripts/lib/createStreamSink.js @@ -1,13 +1,6 @@ const WritableStream = require('readable-stream').Writable const promiseToCallback = require('promise-to-callback') -module.exports = createStreamSink - - -function createStreamSink (asyncWriteFn, _opts) { - return new AsyncWritableStream(asyncWriteFn, _opts) -} - class AsyncWritableStream extends WritableStream { constructor (asyncWriteFn, _opts) { @@ -22,3 +15,9 @@ class AsyncWritableStream extends WritableStream { } } + +function createStreamSink (asyncWriteFn, _opts) { + return new AsyncWritableStream(asyncWriteFn, _opts) +} + +module.exports = createStreamSink diff --git a/app/scripts/lib/ens-ipfs/setup.js b/app/scripts/lib/ens-ipfs/setup.js index 82679db5d..86f3e7d47 100644 --- a/app/scripts/lib/ens-ipfs/setup.js +++ b/app/scripts/lib/ens-ipfs/setup.js @@ -51,6 +51,8 @@ function setupEnsIpfsResolver ({ provider }) { } } else if (type === 'swarm-ns') { url = `https://swarm-gateways.net/bzz:/${hash}${path}${search || ''}` + } else if (type === 'onion' || type === 'onion3') { + url = `http://${hash}.onion${path}${search || ''}` } } catch (err) { console.warn(err) diff --git a/app/scripts/lib/message-manager.js b/app/scripts/lib/message-manager.js index ac41de523..898378389 100644 --- a/app/scripts/lib/message-manager.js +++ b/app/scripts/lib/message-manager.js @@ -61,7 +61,7 @@ module.exports = class MessageManager extends EventEmitter { */ getUnapprovedMsgs () { return this.messages.filter(msg => msg.status === 'unapproved') - .reduce((result, msg) => { result[msg.id] = msg; return result }, {}) + .reduce((result, msg) => { result[msg.id] = msg; return result }, {}) } /** diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js index 7c13e521a..b5e28be13 100644 --- a/app/scripts/lib/personal-message-manager.js +++ b/app/scripts/lib/personal-message-manager.js @@ -64,7 +64,7 @@ module.exports = class PersonalMessageManager extends EventEmitter { */ getUnapprovedMsgs () { return this.messages.filter(msg => msg.status === 'unapproved') - .reduce((result, msg) => { result[msg.id] = msg; return result }, {}) + .reduce((result, msg) => { result[msg.id] = msg; return result }, {}) } /** diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 0b6f5fcb5..d999bb790 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -28,6 +28,7 @@ const PreferencesController = require('./controllers/preferences') const AppStateController = require('./controllers/app-state') const InfuraController = require('./controllers/infura') const CachedBalancesController = require('./controllers/cached-balances') +const OnboardingController = require('./controllers/onboarding') const RecentBlocksController = require('./controllers/recent-blocks') const MessageManager = require('./lib/message-manager') const PersonalMessageManager = require('./lib/personal-message-manager') @@ -68,7 +69,7 @@ module.exports = class MetamaskController extends EventEmitter { * @constructor * @param {Object} opts */ - constructor (opts) { + constructor (opts) { super() this.defaultMaxListeners = 20 @@ -158,6 +159,10 @@ module.exports = class MetamaskController extends EventEmitter { initState: initState.CachedBalancesController, }) + this.onboardingController = new OnboardingController({ + initState: initState.OnboardingController, + }) + // ensure accountTracker updates balances after network change this.networkController.on('networkDidChange', () => { this.accountTracker._updateAccounts() @@ -262,6 +267,7 @@ module.exports = class MetamaskController extends EventEmitter { NetworkController: this.networkController.store, InfuraController: this.infuraController.store, CachedBalancesController: this.cachedBalancesController.store, + OnboardingController: this.onboardingController.store, }) this.memStore = new ComposableObservableStore(null, { @@ -283,6 +289,7 @@ module.exports = class MetamaskController extends EventEmitter { ShapeshiftController: this.shapeshiftController, InfuraController: this.infuraController.store, ProviderApprovalController: this.providerApprovalController.store, + OnboardingController: this.onboardingController.store, }) this.memStore.subscribe(this.sendUpdate.bind(this)) } @@ -362,9 +369,9 @@ module.exports = class MetamaskController extends EventEmitter { return publicConfigStore } -//============================================================================= -// EXPOSED TO THE UI SUBSYSTEM -//============================================================================= + //============================================================================= + // EXPOSED TO THE UI SUBSYSTEM + //============================================================================= /** * The metamask-state of the various controllers, made available to the UI @@ -398,6 +405,7 @@ module.exports = class MetamaskController extends EventEmitter { const txController = this.txController const networkController = this.networkController const providerApprovalController = this.providerApprovalController + const onboardingController = this.onboardingController return { // etc @@ -420,9 +428,7 @@ module.exports = class MetamaskController extends EventEmitter { // primary HD keyring management addNewAccount: nodeify(this.addNewAccount, this), - placeSeedWords: this.placeSeedWords.bind(this), verifySeedPhrase: nodeify(this.verifySeedPhrase, this), - clearSeedWordCache: this.clearSeedWordCache.bind(this), resetAccount: nodeify(this.resetAccount, this), removeAccount: nodeify(this.removeAccount, this), importAccountWithStrategy: nodeify(this.importAccountWithStrategy, this), @@ -454,15 +460,16 @@ module.exports = class MetamaskController extends EventEmitter { setAccountLabel: nodeify(preferencesController.setAccountLabel, preferencesController), setFeatureFlag: nodeify(preferencesController.setFeatureFlag, preferencesController), setPreference: nodeify(preferencesController.setPreference, preferencesController), - completeUiMigration: nodeify(preferencesController.completeUiMigration, preferencesController), completeOnboarding: nodeify(preferencesController.completeOnboarding, preferencesController), addKnownMethodData: nodeify(preferencesController.addKnownMethodData, preferencesController), + unsetMigratedPrivacyMode: nodeify(preferencesController.unsetMigratedPrivacyMode, preferencesController), // BlacklistController whitelistPhishingDomain: this.whitelistPhishingDomain.bind(this), // AddressController setAddressBook: this.addressBookController.set.bind(this.addressBookController), + removeFromAddressBook: this.addressBookController.delete.bind(this.addressBookController), // AppStateController setLastActiveTime: nodeify(this.appStateController.setLastActiveTime, this.appStateController), @@ -500,14 +507,18 @@ module.exports = class MetamaskController extends EventEmitter { // provider approval approveProviderRequestByOrigin: providerApprovalController.approveProviderRequestByOrigin.bind(providerApprovalController), rejectProviderRequestByOrigin: providerApprovalController.rejectProviderRequestByOrigin.bind(providerApprovalController), + forceApproveProviderRequestByOrigin: providerApprovalController.forceApproveProviderRequestByOrigin.bind(providerApprovalController), clearApprovedOrigins: providerApprovalController.clearApprovedOrigins.bind(providerApprovalController), + + // onboarding controller + setSeedPhraseBackedUp: nodeify(onboardingController.setSeedPhraseBackedUp, onboardingController), } } -//============================================================================= -// VAULT / KEYRING RELATED METHODS -//============================================================================= + //============================================================================= + // VAULT / KEYRING RELATED METHODS + //============================================================================= /** * Creates a new Vault and create a new keychain. @@ -617,7 +628,7 @@ module.exports = class MetamaskController extends EventEmitter { * with the mobile client for syncing purposes * @returns Promise<Object> Parts of the state that we want to syncx */ - async fetchInfoToSync () { + async fetchInfoToSync () { // Preferences const { accountTokens, @@ -746,14 +757,14 @@ module.exports = class MetamaskController extends EventEmitter { const keyring = await this.getKeyringForDevice(deviceName, hdPath) let accounts = [] switch (page) { - case -1: - accounts = await keyring.getPreviousPage() - break - case 1: - accounts = await keyring.getNextPage() - break - default: - accounts = await keyring.getFirstPage() + case -1: + accounts = await keyring.getPreviousPage() + break + case 1: + accounts = await keyring.getNextPage() + break + default: + accounts = await keyring.getFirstPage() } // Merge with existing accounts @@ -810,7 +821,7 @@ module.exports = class MetamaskController extends EventEmitter { const { identities } = this.preferencesController.store.getState() return { ...keyState, identities } - } + } // @@ -846,26 +857,6 @@ module.exports = class MetamaskController extends EventEmitter { } /** - * Adds the current vault's seed words to the UI's state tree. - * - * Used when creating a first vault, to allow confirmation. - * Also used when revealing the seed words in the confirmation view. - * - * @param {Function} cb - A callback called on completion. - */ - placeSeedWords (cb) { - - this.verifySeedPhrase() - .then((seedWords) => { - this.preferencesController.setSeedWords(seedWords) - return cb(null, seedWords) - }) - .catch((err) => { - return cb(err) - }) - } - - /** * Verifies the validity of the current vault's seed phrase. * * Validity: seed phrase restores the accounts belonging to the current vault. @@ -899,18 +890,6 @@ module.exports = class MetamaskController extends EventEmitter { } /** - * Remove the primary account seed phrase from the UI's state tree. - * - * The seed phrase remains available in the background process. - * - * @param {function} cb Callback function called with the current address. - */ - clearSeedWordCache (cb) { - this.preferencesController.setSeedWords(null) - cb(null, this.preferencesController.getSelectedAddress()) - } - - /** * Clears the transaction history, to allow users to force-reset their nonces. * Mostly used in development environments, when networks are restarted with * the same network ID. @@ -1009,16 +988,16 @@ module.exports = class MetamaskController extends EventEmitter { // sets the status op the message to 'approved' // and removes the metamaskId for signing return this.messageManager.approveMessage(msgParams) - .then((cleanMsgParams) => { + .then((cleanMsgParams) => { // signs the message - return this.keyringController.signMessage(cleanMsgParams) - }) - .then((rawSig) => { + return this.keyringController.signMessage(cleanMsgParams) + }) + .then((rawSig) => { // tells the listener that the message has been signed // and can be returned to the dapp - this.messageManager.setMsgStatusSigned(msgId, rawSig) - return this.getState() - }) + this.messageManager.setMsgStatusSigned(msgId, rawSig) + return this.getState() + }) } /** @@ -1067,16 +1046,16 @@ module.exports = class MetamaskController extends EventEmitter { // sets the status op the message to 'approved' // and removes the metamaskId for signing return this.personalMessageManager.approveMessage(msgParams) - .then((cleanMsgParams) => { + .then((cleanMsgParams) => { // signs the message - return this.keyringController.signPersonalMessage(cleanMsgParams) - }) - .then((rawSig) => { + return this.keyringController.signPersonalMessage(cleanMsgParams) + }) + .then((rawSig) => { // tells the listener that the message has been signed // and can be returned to the dapp - this.personalMessageManager.setMsgStatusSigned(msgId, rawSig) - return this.getState() - }) + this.personalMessageManager.setMsgStatusSigned(msgId, rawSig) + return this.getState() + }) } /** @@ -1176,7 +1155,7 @@ module.exports = class MetamaskController extends EventEmitter { restoreOldVaultAccounts (migratorOutput) { const { serialized } = migratorOutput return this.keyringController.restoreKeyring(serialized) - .then(() => migratorOutput) + .then(() => migratorOutput) } /** @@ -1219,9 +1198,9 @@ module.exports = class MetamaskController extends EventEmitter { }) } -//============================================================================= -// END (VAULT / KEYRING RELATED METHODS) -//============================================================================= + //============================================================================= + // END (VAULT / KEYRING RELATED METHODS) + //============================================================================= /** * Allows a user to try to speed up a transaction by retrying it @@ -1270,9 +1249,9 @@ module.exports = class MetamaskController extends EventEmitter { }) } -//============================================================================= -// PASSWORD MANAGEMENT -//============================================================================= + //============================================================================= + // PASSWORD MANAGEMENT + //============================================================================= /** * Allows a user to begin the seed phrase recovery process. @@ -1294,9 +1273,9 @@ module.exports = class MetamaskController extends EventEmitter { cb() } -//============================================================================= -// SETUP -//============================================================================= + //============================================================================= + // SETUP + //============================================================================= /** * Used to create a multiplexed stream for connecting to an untrusted context @@ -1319,6 +1298,8 @@ module.exports = class MetamaskController extends EventEmitter { const publicApi = this.setupPublicApi(mux.createStream('publicApi'), originDomain) this.setupProviderConnection(mux.createStream('provider'), originDomain, publicApi) this.setupPublicConfig(mux.createStream('publicConfig'), originDomain) + + this.providerApprovalController.on(`forceResolvedRequest:${originDomain}`, publicApi.forceReloadSite) } /** @@ -1392,6 +1373,32 @@ module.exports = class MetamaskController extends EventEmitter { * @param {string} origin - The URI of the requesting resource. */ setupProviderConnection (outStream, origin, publicApi) { + const getSiteMetadata = publicApi && publicApi.getSiteMetadata + const engine = this.setupProviderEngine(origin, getSiteMetadata) + + // setup connection + const providerStream = createEngineStream({ engine }) + + pump( + outStream, + providerStream, + outStream, + (err) => { + // cleanup filter polyfill middleware + engine._middleware.forEach((mid) => { + if (mid.destroy && typeof mid.destroy === 'function') { + mid.destroy() + } + }) + if (err) log.error(err) + } + ) + } + + /** + * A method for creating a provider that is safely restricted for the requesting domain. + **/ + setupProviderEngine (origin, getSiteMetadata) { // setup json rpc engine stack const engine = new RpcEngine() const provider = this.provider @@ -1399,6 +1406,7 @@ module.exports = class MetamaskController extends EventEmitter { // create filter polyfill middleware const filterMiddleware = createFilterMiddleware({ provider, blockTracker }) + // create subscription polyfill middleware const subscriptionManager = createSubscriptionManager({ provider, blockTracker }) subscriptionManager.events.on('notification', (message) => engine.emit('notification', message)) @@ -1414,24 +1422,11 @@ module.exports = class MetamaskController extends EventEmitter { // requestAccounts engine.push(this.providerApprovalController.createMiddleware({ origin, - getSiteMetadata: publicApi && publicApi.getSiteMetadata, + getSiteMetadata, })) // forward to metamask primary provider engine.push(providerAsMiddleware(provider)) - - // setup connection - const providerStream = createEngineStream({ engine }) - - pump( - outStream, - providerStream, - outStream, - (err) => { - // cleanup filter polyfill middleware - filterMiddleware.destroy() - if (err) log.error(err) - } - ) + return engine } /** @@ -1485,6 +1480,10 @@ module.exports = class MetamaskController extends EventEmitter { const publicApi = { // wrap with an await remote + forceReloadSite: async () => { + const remote = await getRemote() + return await pify(remote.forceReloadSite)() + }, getSiteMetadata: async () => { const remote = await getRemote() return await pify(remote.getSiteMetadata)() @@ -1551,13 +1550,13 @@ module.exports = class MetamaskController extends EventEmitter { 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(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()) + .map(number => number.div(GWEI_BN).toNumber()) const percentileNum = percentile(65, lowestPrices) const percentileNumBn = new BN(percentileNum) @@ -1577,9 +1576,9 @@ module.exports = class MetamaskController extends EventEmitter { return pendingNonce } -//============================================================================= -// CONFIG -//============================================================================= + //============================================================================= + // CONFIG + //============================================================================= // Log blocks @@ -1774,7 +1773,7 @@ module.exports = class MetamaskController extends EventEmitter { this.tokenRatesController.isActive = active } - /** + /** * Creates RPC engine middleware for processing eth_signTypedData requests * * @param {Object} req - request object @@ -1798,3 +1797,4 @@ module.exports = class MetamaskController extends EventEmitter { return this.keyringController.setLocked() } } + diff --git a/app/scripts/migrations/018.js b/app/scripts/migrations/018.js index ffbf24a4b..f6442dacc 100644 --- a/app/scripts/migrations/018.js +++ b/app/scripts/migrations/018.js @@ -43,9 +43,9 @@ function transformState (state) { const newHistory = ( txStateHistoryHelper.migrateFromSnapshotsToDiffs(txMeta.history) // remove empty diffs - .filter((entry) => { - return !Array.isArray(entry) || entry.length > 0 - }) + .filter((entry) => { + return !Array.isArray(entry) || entry.length > 0 + }) ) txMeta.history = newHistory return txMeta diff --git a/app/scripts/migrations/019.js b/app/scripts/migrations/019.js index ce5da6859..7b726c3e8 100644 --- a/app/scripts/migrations/019.js +++ b/app/scripts/migrations/019.js @@ -38,13 +38,13 @@ function transformState (state) { if (txMeta.status !== 'submitted') return txMeta const confirmedTxs = txList.filter((tx) => tx.status === 'confirmed') - .filter((tx) => tx.txParams.from === txMeta.txParams.from) - .filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from) + .filter((tx) => tx.txParams.from === txMeta.txParams.from) + .filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from) const highestConfirmedNonce = getHighestNonce(confirmedTxs) const pendingTxs = txList.filter((tx) => tx.status === 'submitted') - .filter((tx) => tx.txParams.from === txMeta.txParams.from) - .filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from) + .filter((tx) => tx.txParams.from === txMeta.txParams.from) + .filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from) const highestContinuousNonce = getHighestContinuousFrom(pendingTxs, highestConfirmedNonce) const maxNonce = Math.max(highestContinuousNonce, highestConfirmedNonce) @@ -78,7 +78,7 @@ function getHighestContinuousFrom (txList, startPoint) { function getHighestNonce (txList) { const nonces = txList.map((txMeta) => { - const nonce = txMeta.txParams.nonce + const nonce = txMeta.txParams.nonce return parseInt(nonce || '0x0', 16) }) const highestNonce = Math.max.apply(null, nonces) diff --git a/app/scripts/migrations/024.js b/app/scripts/migrations/024.js index 6239bab13..5ffaea377 100644 --- a/app/scripts/migrations/024.js +++ b/app/scripts/migrations/024.js @@ -32,7 +32,7 @@ function transformState (state) { txMeta.status === 'unapproved' && txMeta.txParams && txMeta.txParams.from - ) { + ) { txMeta.txParams.from = txMeta.txParams.from.toLowerCase() } return txMeta diff --git a/app/scripts/migrations/031.js b/app/scripts/migrations/031.js index 98d182828..9c8cbeb09 100644 --- a/app/scripts/migrations/031.js +++ b/app/scripts/migrations/031.js @@ -2,14 +2,14 @@ const version = 31 const clone = require('clone') - /* +/* * The purpose of this migration is to properly set the completedOnboarding flag baesd on the state * of the KeyringController. */ module.exports = { version, - migrate: async function (originalVersionedData) { + migrate: async function (originalVersionedData) { const versionedData = clone(originalVersionedData) versionedData.meta.version = version const state = versionedData.data @@ -19,13 +19,13 @@ module.exports = { }, } - function transformState (state) { +function transformState (state) { const { KeyringController, PreferencesController } = state - if (KeyringController && PreferencesController) { + if (KeyringController && PreferencesController) { const { vault } = KeyringController PreferencesController.completedOnboarding = Boolean(vault) } - return state + return state } diff --git a/app/scripts/migrations/034.js b/app/scripts/migrations/034.js new file mode 100644 index 000000000..7c852de96 --- /dev/null +++ b/app/scripts/migrations/034.js @@ -0,0 +1,33 @@ +const version = 34 +const clone = require('clone') + +/** + * The purpose of this migration is to enable the {@code privacyMode} feature flag and set the user as being migrated + * if it was {@code false}. + */ +module.exports = { + version, + migrate: async function (originalVersionedData) { + const versionedData = clone(originalVersionedData) + versionedData.meta.version = version + const state = versionedData.data + versionedData.data = transformState(state) + return versionedData + }, +} + +function transformState (state) { + const { PreferencesController } = state + + if (PreferencesController) { + const featureFlags = PreferencesController.featureFlags || {} + + if (!featureFlags.privacyMode && typeof PreferencesController.migratedPrivacyMode === 'undefined') { + // Mark the state has being migrated and enable Privacy Mode + PreferencesController.migratedPrivacyMode = true + featureFlags.privacyMode = true + } + } + + return state +} diff --git a/app/scripts/migrations/035.js b/app/scripts/migrations/035.js new file mode 100644 index 000000000..02b01f588 --- /dev/null +++ b/app/scripts/migrations/035.js @@ -0,0 +1,28 @@ +// next version number +const version = 35 + +/* + +Removes the deprecated 'seedWords' state + +*/ + +const clone = require('clone') + +module.exports = { + version, + + migrate: async function (originalVersionedData) { + const versionedData = clone(originalVersionedData) + versionedData.meta.version = version + versionedData.data = transformState(versionedData.data) + return versionedData + }, +} + +function transformState (state) { + if (state.PreferencesController && state.PreferencesController.seedWords !== undefined) { + delete state.PreferencesController.seedWords + } + return state +} diff --git a/app/scripts/phishing-detect.js b/app/scripts/phishing-detect.js index b30c76b72..266e4fc31 100644 --- a/app/scripts/phishing-detect.js +++ b/app/scripts/phishing-detect.js @@ -37,11 +37,11 @@ function start () { function setupControllerConnection (connectionStream, cb) { const eventEmitter = new EventEmitter() - const accountManagerDnode = dnode({ + const metaMaskControllerDnode = dnode({ sendUpdate (state) { eventEmitter.emit('update', state) }, }) - connectionStream.pipe(accountManagerDnode).pipe(connectionStream) - accountManagerDnode.once('remote', (accountManager) => cb(null, accountManager)) + connectionStream.pipe(metaMaskControllerDnode).pipe(connectionStream) + metaMaskControllerDnode.once('remote', (backgroundConnection) => cb(null, backgroundConnection)) } diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js index 0c2d222b8..43820515d 100644 --- a/app/scripts/platforms/extension.js +++ b/app/scripts/platforms/extension.js @@ -68,7 +68,7 @@ class ExtensionPlatform { const nonce = parseInt(txMeta.txParams.nonce, 16) const title = 'Confirmed transaction' - const message = `Transaction ${nonce} confirmed! View on EtherScan` + const message = `Transaction ${nonce} confirmed! View on Etherscan` this._showNotification(title, message, url) } @@ -84,20 +84,20 @@ class ExtensionPlatform { extension.notifications.create( url, { - 'type': 'basic', - 'title': title, - 'iconUrl': extension.extension.getURL('../../images/icon-64.png'), - 'message': message, + 'type': 'basic', + 'title': title, + 'iconUrl': extension.extension.getURL('../../images/icon-64.png'), + 'message': message, }) } _subscribeToNotificationClicked () { - if (!extension.notifications.onClicked.hasListener(this._viewOnEtherScan)) { - extension.notifications.onClicked.addListener(this._viewOnEtherScan) + if (!extension.notifications.onClicked.hasListener(this._viewOnEtherscan)) { + extension.notifications.onClicked.addListener(this._viewOnEtherscan) } } - _viewOnEtherScan (txId) { + _viewOnEtherscan (txId) { if (txId.startsWith('http://')) { extension.tabs.create({ url: txId }) } diff --git a/app/scripts/popup-core.js b/app/scripts/popup-core.js deleted file mode 100644 index e964d001d..000000000 --- a/app/scripts/popup-core.js +++ /dev/null @@ -1,77 +0,0 @@ -const {EventEmitter} = require('events') -const async = require('async') -const Dnode = require('dnode') -const Eth = require('ethjs') -const EthQuery = require('eth-query') -const launchMetamaskUi = require('../../ui') -const StreamProvider = require('web3-stream-provider') -const {setupMultiplex} = require('./lib/stream-utils.js') - -module.exports = initializePopup - -/** - * Asynchronously initializes the MetaMask popup UI - * - * @param {{ container: Element, connectionStream: * }} config Popup configuration object - * @param {Function} cb Called when initialization is complete - */ -function initializePopup ({ container, connectionStream }, cb) { - // setup app - async.waterfall([ - (cb) => connectToAccountManager(connectionStream, cb), - (accountManager, cb) => launchMetamaskUi({ container, accountManager }, cb), - ], cb) -} - -/** - * Establishes streamed connections to background scripts and a Web3 provider - * - * @param {PortDuplexStream} connectionStream PortStream instance establishing a background connection - * @param {Function} cb Called when controller connection is established - */ -function connectToAccountManager (connectionStream, cb) { - // setup communication with background - // setup multiplexing - const mx = setupMultiplex(connectionStream) - // connect features - setupControllerConnection(mx.createStream('controller'), cb) - setupWeb3Connection(mx.createStream('provider')) -} - -/** - * Establishes a streamed connection to a Web3 provider - * - * @param {PortDuplexStream} connectionStream PortStream instance establishing a background connection - */ -function setupWeb3Connection (connectionStream) { - const providerStream = new StreamProvider() - providerStream.pipe(connectionStream).pipe(providerStream) - connectionStream.on('error', console.error.bind(console)) - providerStream.on('error', console.error.bind(console)) - global.ethereumProvider = providerStream - global.ethQuery = new EthQuery(providerStream) - global.eth = new Eth(providerStream) -} - -/** - * Establishes a streamed connection to the background account manager - * - * @param {PortDuplexStream} connectionStream PortStream instance establishing a background connection - * @param {Function} cb Called when the remote account manager connection is established - */ -function setupControllerConnection (connectionStream, cb) { - // this is a really sneaky way of adding EventEmitter api - // to a bi-directional dnode instance - const eventEmitter = new EventEmitter() - const accountManagerDnode = Dnode({ - sendUpdate: function (state) { - eventEmitter.emit('update', state) - }, - }) - connectionStream.pipe(accountManagerDnode).pipe(connectionStream) - accountManagerDnode.once('remote', function (accountManager) { - // setup push events - accountManager.on = eventEmitter.on.bind(eventEmitter) - cb(null, accountManager) - }) -} diff --git a/app/scripts/ui.js b/app/scripts/ui.js index 67ec6bf06..a99da37a0 100644 --- a/app/scripts/ui.js +++ b/app/scripts/ui.js @@ -1,14 +1,19 @@ -const injectCss = require('inject-css') -const NewMetaMaskUiCss = require('../../ui/css') -const startPopup = require('./popup-core') const PortStream = require('extension-port-stream') const { getEnvironmentType } = require('./lib/util') -const { ENVIRONMENT_TYPE_NOTIFICATION, ENVIRONMENT_TYPE_FULLSCREEN } = require('./lib/enums') +const { ENVIRONMENT_TYPE_NOTIFICATION, ENVIRONMENT_TYPE_FULLSCREEN, ENVIRONMENT_TYPE_POPUP } = require('./lib/enums') const extension = require('extensionizer') const ExtensionPlatform = require('./platforms/extension') const NotificationManager = require('./lib/notification-manager') const notificationManager = new NotificationManager() const setupSentry = require('./lib/setupSentry') +const {EventEmitter} = require('events') +const Dnode = require('dnode') +const Eth = require('ethjs') +const EthQuery = require('eth-query') +const urlUtil = require('url') +const launchMetaMaskUi = require('../../ui') +const StreamProvider = require('web3-stream-provider') +const {setupMultiplex} = require('./lib/stream-utils.js') const log = require('loglevel') start().catch(log.error) @@ -41,22 +46,8 @@ async function start () { const extensionPort = extension.runtime.connect({ name: windowType }) const connectionStream = new PortStream(extensionPort) - // start ui - const container = document.getElementById('app-content') - startPopup({ container, connectionStream }, (err, store) => { - if (err) return displayCriticalError(err) - - const state = store.getState() - const { metamask: { completedOnboarding } = {} } = state - - if (!completedOnboarding && windowType !== ENVIRONMENT_TYPE_FULLSCREEN) { - global.platform.openExtensionInBrowser() - return - } - - injectCss(NewMetaMaskUiCss()) - }) - + const activeTab = await queryCurrentActiveTab(windowType) + initializeUiWithTab(activeTab) function closePopupIfOpen (windowType) { if (windowType !== ENVIRONMENT_TYPE_NOTIFICATION) { @@ -65,11 +56,107 @@ async function start () { } } - function displayCriticalError (err) { + function displayCriticalError (container, err) { container.innerHTML = '<div class="critical-error">The MetaMask app failed to load: please open and close MetaMask again to restart.</div>' container.style.height = '80px' log.error(err.stack) throw err } + function initializeUiWithTab (tab) { + const container = document.getElementById('app-content') + initializeUi(tab, container, connectionStream, (err, store) => { + if (err) { + return displayCriticalError(container, err) + } + + const state = store.getState() + const { metamask: { completedOnboarding } = {} } = state + + if (!completedOnboarding && windowType !== ENVIRONMENT_TYPE_FULLSCREEN) { + global.platform.openExtensionInBrowser() + } + }) + } +} + +async function queryCurrentActiveTab (windowType) { + return new Promise((resolve) => { + // At the time of writing we only have the `activeTab` permission which means + // that this query will only succeed in the popup context (i.e. after a "browserAction") + if (windowType !== ENVIRONMENT_TYPE_POPUP) { + resolve({}) + return + } + + extension.tabs.query({active: true, currentWindow: true}, (tabs) => { + const [activeTab] = tabs + const {title, url} = activeTab + const { hostname: origin, protocol } = url ? urlUtil.parse(url) : {} + resolve({ + title, origin, protocol, url, + }) + }) + }) +} + +function initializeUi (activeTab, container, connectionStream, cb) { + connectToAccountManager(connectionStream, (err, backgroundConnection) => { + if (err) { + return cb(err) + } + + launchMetaMaskUi({ + activeTab, + container, + backgroundConnection, + }, cb) + }) +} + +/** + * Establishes a connection to the background and a Web3 provider + * + * @param {PortDuplexStream} connectionStream PortStream instance establishing a background connection + * @param {Function} cb Called when controller connection is established + */ +function connectToAccountManager (connectionStream, cb) { + const mx = setupMultiplex(connectionStream) + setupControllerConnection(mx.createStream('controller'), cb) + setupWeb3Connection(mx.createStream('provider')) +} + +/** + * Establishes a streamed connection to a Web3 provider + * + * @param {PortDuplexStream} connectionStream PortStream instance establishing a background connection + */ +function setupWeb3Connection (connectionStream) { + const providerStream = new StreamProvider() + providerStream.pipe(connectionStream).pipe(providerStream) + connectionStream.on('error', console.error.bind(console)) + providerStream.on('error', console.error.bind(console)) + global.ethereumProvider = providerStream + global.ethQuery = new EthQuery(providerStream) + global.eth = new Eth(providerStream) +} + +/** + * Establishes a streamed connection to the background account manager + * + * @param {PortDuplexStream} connectionStream PortStream instance establishing a background connection + * @param {Function} cb Called when the remote account manager connection is established + */ +function setupControllerConnection (connectionStream, cb) { + const eventEmitter = new EventEmitter() + const backgroundDnode = Dnode({ + sendUpdate: function (state) { + eventEmitter.emit('update', state) + }, + }) + connectionStream.pipe(backgroundDnode).pipe(connectionStream) + backgroundDnode.once('remote', function (backgroundConnection) { + backgroundConnection.on = eventEmitter.on.bind(eventEmitter) + cb(null, backgroundConnection) + }) } |