mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 18:00:18 +01:00
Merge branch 'develop' into reducers
This commit is contained in:
commit
7531de14f9
@ -324,7 +324,7 @@ jobs:
|
||||
command: >
|
||||
git config user.name metamaskbot &&
|
||||
git config user.email admin@metamask.io &&
|
||||
gh-pages -d docs/jsdocs
|
||||
npm run publish-docs
|
||||
|
||||
test-unit:
|
||||
docker:
|
||||
|
28
CHANGELOG.md
28
CHANGELOG.md
@ -2,8 +2,36 @@
|
||||
|
||||
## Current Develop Branch
|
||||
|
||||
- [#5283](https://github.com/MetaMask/metamask-extension/pull/5283): Fix bug when eth.getCode() called with no contract
|
||||
- [#5563](https://github.com/MetaMask/metamask-extension/pull/5563#pullrequestreview-166769174) Feature: improve Hatian Creole translations
|
||||
- [#5559](https://github.com/MetaMask/metamask-extension/pull/5559) Localize language names in translation select list
|
||||
|
||||
## 4.16.0 Wednesday October 17 2018
|
||||
|
||||
- Feature: Add toggle for primary currency (eth/fiat)
|
||||
- Feature: add tooltip for view etherscan tx
|
||||
- Feature: add Polish translations
|
||||
- Feature: improve Korean translations
|
||||
- Feature: improve Italian translations
|
||||
- Bug Fix: Fix bug with "pending" block reference
|
||||
- Bug Fix: Force AccountTracker to update balances on network change
|
||||
- Bug Fix: Fix document extension check when injecting web3
|
||||
- Bug Fix: Fix some support links
|
||||
|
||||
## 4.15.0 Thursday October 11 2018
|
||||
|
||||
- A rollback release, equivalent to `v4.11.1` to be deployed in the case that `v4.14.0` is found to have bugs.
|
||||
|
||||
## 4.14.0 Thursday October 11 2018
|
||||
|
||||
- Update transaction statuses when switching networks.
|
||||
- [#5470](https://github.com/MetaMask/metamask-extension/pull/5470) 100% coverage in French locale, fixed the procedure to verify proposed locale.
|
||||
- Added rudimentary support for the subscription API to support web3 1.0 and Truffle's Drizzle.
|
||||
- [#5502](https://github.com/MetaMask/metamask-extension/pull/5502) Update Italian translation.
|
||||
|
||||
## 4.13.0
|
||||
|
||||
- A rollback release, equivalent to `v4.11.1` to be deployed in the case that `v4.12.0` is found to have bugs.
|
||||
|
||||
## 4.12.0 Thursday September 27 2018
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
# 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)](https://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) [![Stories in Ready](https://badge.waffle.io/MetaMask/metamask-extension.png?label=in%20progress&title=waffle.io)](https://waffle.io/MetaMask/metamask-extension)
|
||||
|
||||
## Support
|
||||
|
||||
|
@ -372,7 +372,7 @@
|
||||
"message": "Import účtu"
|
||||
},
|
||||
"importAccountMsg": {
|
||||
"message":"Importované účty nebudou spojeny s vaší původní MetaMaskovou klíčovou frází. Zjistěte více o importovaných účtech "
|
||||
"message": "Importované účty nebudou spojeny s vaší původní MetaMaskovou klíčovou frází. Zjistěte více o importovaných účtech "
|
||||
},
|
||||
"importAnAccount": {
|
||||
"message": "Import účtu"
|
||||
@ -730,7 +730,7 @@
|
||||
"message": "Nastavení"
|
||||
},
|
||||
"info": {
|
||||
"message": "Informace"
|
||||
"message": "Informace"
|
||||
},
|
||||
"shapeshiftBuy": {
|
||||
"message": "Nakoupit na ShapeShift"
|
||||
|
@ -68,13 +68,13 @@
|
||||
"message": "Muss größer oder gleich $1 und kleiner oder gleich $2 sein.",
|
||||
"description": "Helfer für die Eingabe von hex als dezimal"
|
||||
},
|
||||
"blockiesIdenticon": {
|
||||
"blockiesIdenticon": {
|
||||
"message": "Blockies Identicon verwenden"
|
||||
},
|
||||
"borrowDharma": {
|
||||
"message": "Mit Dharma ausleihen (Beta)"
|
||||
},
|
||||
"builtInCalifornia": {
|
||||
"builtInCalifornia": {
|
||||
"message": "MetaMask wurde in Kalifornien entwickelt und gebaut."
|
||||
},
|
||||
"buy": {
|
||||
@ -86,13 +86,13 @@
|
||||
"buyCoinbaseExplainer": {
|
||||
"message": "Coinbase ist die weltweit bekannteste Art und Weise um Bitcoin, Ethereum und Litecoin zu kaufen und verkaufen."
|
||||
},
|
||||
"ok": {
|
||||
"ok": {
|
||||
"message": "Ok"
|
||||
},
|
||||
"cancel": {
|
||||
"message": "Abbrechen"
|
||||
},
|
||||
"classicInterface": {
|
||||
"classicInterface": {
|
||||
"message": "Klassische Oberfläche verwenden"
|
||||
},
|
||||
"clickCopy": {
|
||||
@ -101,7 +101,7 @@
|
||||
"confirm": {
|
||||
"message": "Bestätigen"
|
||||
},
|
||||
"confirmed": {
|
||||
"confirmed": {
|
||||
"message": "Bestätigt"
|
||||
},
|
||||
"confirmContract": {
|
||||
@ -369,7 +369,7 @@
|
||||
"message": "Account importieren"
|
||||
},
|
||||
"importAccountMsg": {
|
||||
"message":" Importierte Accounts werden nicht mit der Seed-Wörterfolge deines ursprünglichen MetaMask Accounts verknüpft. Erfahre mehr über importierte Accounts."
|
||||
"message": " Importierte Accounts werden nicht mit der Seed-Wörterfolge deines ursprünglichen MetaMask Accounts verknüpft. Erfahre mehr über importierte Accounts."
|
||||
},
|
||||
"importAnAccount": {
|
||||
"message": "Einen Account importieren"
|
||||
@ -709,7 +709,7 @@
|
||||
"message": "Einstellungen"
|
||||
},
|
||||
"info": {
|
||||
"message": "Info"
|
||||
"message": "Info"
|
||||
},
|
||||
"shapeshiftBuy": {
|
||||
"message": "Mit Shapeshift kaufen"
|
||||
|
@ -14,6 +14,9 @@
|
||||
"accountName": {
|
||||
"message": "Account Name"
|
||||
},
|
||||
"accountOptions": {
|
||||
"message": "Account Options"
|
||||
},
|
||||
"accountSelectionRequired": {
|
||||
"message": "You need to select an account!"
|
||||
},
|
||||
@ -137,10 +140,13 @@
|
||||
"clickCopy": {
|
||||
"message": "Click to Copy"
|
||||
},
|
||||
"clickToAdd": {
|
||||
"message": "Click on $1 to add them to your account"
|
||||
},
|
||||
"close": {
|
||||
"message": "Close"
|
||||
},
|
||||
"chromeRequiredForHardwareWallets":{
|
||||
"chromeRequiredForHardwareWallets": {
|
||||
"message": "You need to use MetaMask on Google Chrome in order to connect to your Hardware Wallet."
|
||||
},
|
||||
"confirm": {
|
||||
@ -358,6 +364,9 @@
|
||||
"enterPasswordContinue": {
|
||||
"message": "Enter password to continue"
|
||||
},
|
||||
"eth": {
|
||||
"message": "ETH"
|
||||
},
|
||||
"etherscanView": {
|
||||
"message": "View account on Etherscan"
|
||||
},
|
||||
@ -377,7 +386,7 @@
|
||||
"message": "Failed"
|
||||
},
|
||||
"fiat": {
|
||||
"message": "FIAT",
|
||||
"message": "Fiat",
|
||||
"description": "Exchange type"
|
||||
},
|
||||
"fileImportFail": {
|
||||
@ -421,6 +430,9 @@
|
||||
"gasLimitTooLow": {
|
||||
"message": "Gas limit must be at least 21000"
|
||||
},
|
||||
"gasUsed": {
|
||||
"message": "Gas Used"
|
||||
},
|
||||
"generatingSeed": {
|
||||
"message": "Generating Seed..."
|
||||
},
|
||||
@ -501,7 +513,7 @@
|
||||
"message": "Import Account"
|
||||
},
|
||||
"importAccountMsg": {
|
||||
"message":" Imported accounts will not be associated with your originally created MetaMask account seedphrase. Learn more about imported accounts "
|
||||
"message": " Imported accounts will not be associated with your originally created MetaMask account seedphrase. Learn more about imported accounts "
|
||||
},
|
||||
"importAnAccount": {
|
||||
"message": "Import an account"
|
||||
@ -632,6 +644,9 @@
|
||||
"min": {
|
||||
"message": "Minimum"
|
||||
},
|
||||
"missingYourTokens": {
|
||||
"message": "Don't see your tokens?"
|
||||
},
|
||||
"myAccounts": {
|
||||
"message": "My Accounts"
|
||||
},
|
||||
@ -677,9 +692,27 @@
|
||||
"newRecipient": {
|
||||
"message": "New Recipient"
|
||||
},
|
||||
"newRPC": {
|
||||
"newNetwork": {
|
||||
"message": "New Network"
|
||||
},
|
||||
"rpcURL": {
|
||||
"message": "New RPC URL"
|
||||
},
|
||||
"showAdvancedOptions": {
|
||||
"message": "Show Advanced Options"
|
||||
},
|
||||
"hideAdvancedOptions": {
|
||||
"message": "Hide Advanced Options"
|
||||
},
|
||||
"optionalChainId": {
|
||||
"message": "ChainID (optional)"
|
||||
},
|
||||
"optionalSymbol": {
|
||||
"message": "Symbol (optional)"
|
||||
},
|
||||
"optionalNickname": {
|
||||
"message": "Nickname (optional)"
|
||||
},
|
||||
"next": {
|
||||
"message": "Next"
|
||||
},
|
||||
@ -689,7 +722,7 @@
|
||||
"noDeposits": {
|
||||
"message": "No deposits received"
|
||||
},
|
||||
"noConversionRateAvailable":{
|
||||
"noConversionRateAvailable": {
|
||||
"message": "No Conversion Rate Available"
|
||||
},
|
||||
"noTransactionHistory": {
|
||||
@ -726,9 +759,6 @@
|
||||
"openInTab": {
|
||||
"message": "Open in tab"
|
||||
},
|
||||
"accountOptions": {
|
||||
"message": "Account Options"
|
||||
},
|
||||
"or": {
|
||||
"message": "or",
|
||||
"description": "choice between creating or importing a new account"
|
||||
@ -745,22 +775,22 @@
|
||||
"parameters": {
|
||||
"message": "Parameters"
|
||||
},
|
||||
"passwordNotLongEnough": {
|
||||
"message": "Password not long enough"
|
||||
},
|
||||
"passwordsDontMatch": {
|
||||
"message": "Passwords Don't Match"
|
||||
},
|
||||
"password": {
|
||||
"message": "Password"
|
||||
},
|
||||
"passwordCorrect": {
|
||||
"message": "Please make sure your password is correct."
|
||||
},
|
||||
"passwordsDontMatch": {
|
||||
"message": "Passwords Don't Match"
|
||||
},
|
||||
"passwordMismatch": {
|
||||
"message": "passwords don't match",
|
||||
"description": "in password creation process, the two new password fields did not match"
|
||||
},
|
||||
"passwordNotLongEnough": {
|
||||
"message": "Password not long enough"
|
||||
},
|
||||
"passwordShort": {
|
||||
"message": "password not long enough",
|
||||
"description": "in password creation process, the password is not long enough to be secure"
|
||||
@ -787,6 +817,12 @@
|
||||
"prev": {
|
||||
"message": "Prev"
|
||||
},
|
||||
"primaryCurrencySetting": {
|
||||
"message": "Primary Currency"
|
||||
},
|
||||
"primaryCurrencySettingDescription": {
|
||||
"message": "Select native to prioritize displaying values in the native currency of the chain (e.g. ETH). Select Fiat to prioritize displaying values in your selected fiat currency."
|
||||
},
|
||||
"privacyMsg": {
|
||||
"message": "Privacy Policy"
|
||||
},
|
||||
@ -795,7 +831,7 @@
|
||||
"description": "select this type of file to use to import an account"
|
||||
},
|
||||
"privateKeyWarning": {
|
||||
"message": "Warning: Never disclose this key. Anyone with your private keys can take steal any assets held in your account."
|
||||
"message": "Warning: Never disclose this key. Anyone with your private keys can steal any assets held in your account."
|
||||
},
|
||||
"privateNetwork": {
|
||||
"message": "Private Network"
|
||||
@ -863,9 +899,6 @@
|
||||
"retryWithMoreGas": {
|
||||
"message": "Retry with a higher gas price here"
|
||||
},
|
||||
"walletSeed": {
|
||||
"message": "Wallet Seed"
|
||||
},
|
||||
"restore": {
|
||||
"message": "Restore"
|
||||
},
|
||||
@ -1154,12 +1187,18 @@
|
||||
"transactionUpdatedGas": {
|
||||
"message": "Transaction updated with a gas price of $1 on $2."
|
||||
},
|
||||
"transactionErrored": {
|
||||
"message": "Transaction encountered an error."
|
||||
},
|
||||
"transactions": {
|
||||
"message": "transactions"
|
||||
},
|
||||
"transactionError": {
|
||||
"message": "Transaction Error. Exception thrown in contract code."
|
||||
},
|
||||
"transactionErrorNoContract": {
|
||||
"message": "Trying to call a function on a non-contract address."
|
||||
},
|
||||
"transactionMemo": {
|
||||
"message": "Transaction memo (optional)"
|
||||
},
|
||||
@ -1189,7 +1228,7 @@
|
||||
"message": "These 12 words are the only way to restore your MetaMask accounts.\nSave them somewhere safe and secret."
|
||||
},
|
||||
"typePassword": {
|
||||
"message": "Type Your Password"
|
||||
"message": "Type your MetaMask password"
|
||||
},
|
||||
"uiWelcome": {
|
||||
"message": "Welcome to the New UI (Beta)"
|
||||
@ -1225,7 +1264,7 @@
|
||||
"message": "Ooops! Something went wrong...."
|
||||
},
|
||||
"unknownCameraError": {
|
||||
"message": "There was an error while trying to access you camera. Please try again..."
|
||||
"message": "There was an error while trying to access your camera. Please try again..."
|
||||
},
|
||||
"unlock": {
|
||||
"message": "Unlock"
|
||||
@ -1264,6 +1303,9 @@
|
||||
"visitWebSite": {
|
||||
"message": "Visit our web site"
|
||||
},
|
||||
"walletSeed": {
|
||||
"message": "Wallet Seed"
|
||||
},
|
||||
"warning": {
|
||||
"message": "Warning"
|
||||
},
|
||||
|
@ -156,7 +156,7 @@
|
||||
"message": " Copiar "
|
||||
},
|
||||
"copyPrivateKey": {
|
||||
"message": "Ésta es tu llave privada (haz click para copiar)"
|
||||
"message": "Ésta es tu clave privada (haz click para copiar)"
|
||||
},
|
||||
"copyToClipboard": {
|
||||
"message": "Copiar al portapapeles"
|
||||
@ -278,10 +278,10 @@
|
||||
"message": "Tipo de cambio"
|
||||
},
|
||||
"exportPrivateKey": {
|
||||
"message": "Exportar llave privada"
|
||||
"message": "Exportar clave privada"
|
||||
},
|
||||
"exportPrivateKeyWarning": {
|
||||
"message": "Exportar llaves privadas bajo TU PROPIO riesgo"
|
||||
"message": "Exportar claves privadas bajo TU PROPIO riesgo"
|
||||
},
|
||||
"failed": {
|
||||
"message": "Fallo"
|
||||
@ -579,8 +579,8 @@
|
||||
"description": "En el proceso de creación de contraseña, esta no es lo suficientemente larga para ser segura"
|
||||
},
|
||||
"pastePrivateKey": {
|
||||
"message": "Pega tu llave privada aqui",
|
||||
"description": "Para importar una cuenta desde una llave privada"
|
||||
"message": "Pega tu clave privada aqui",
|
||||
"description": "Para importar una cuenta desde una clave privada"
|
||||
},
|
||||
"pasteSeed": {
|
||||
"message": "¡Pega tu frase semilla aquí!"
|
||||
@ -595,7 +595,7 @@
|
||||
"message": "Política de privacidad"
|
||||
},
|
||||
"privateKey": {
|
||||
"message": "Llave privada",
|
||||
"message": "Clave privada",
|
||||
"description": "Selecciona este tupo de archivo para importar una cuenta"
|
||||
},
|
||||
"privateKeyWarning": {
|
||||
@ -712,7 +712,7 @@
|
||||
"message": "Comprar con ShapeShift"
|
||||
},
|
||||
"showPrivateKeys": {
|
||||
"message": "Mostrar llaves privadas"
|
||||
"message": "Mostrar claves privadas"
|
||||
},
|
||||
"showQRCode": {
|
||||
"message": "Mostrar codigo QR"
|
||||
|
@ -140,7 +140,7 @@
|
||||
"close": {
|
||||
"message": "Fermer"
|
||||
},
|
||||
"chromeRequiredForHardwareWallets":{
|
||||
"chromeRequiredForHardwareWallets": {
|
||||
"message": "Pour connecter votre portefeuille hardware, vous devez utiliser MetaMask pour Google Chrome."
|
||||
},
|
||||
"confirm": {
|
||||
@ -498,7 +498,7 @@
|
||||
"message": "Importer un compte"
|
||||
},
|
||||
"importAccountMsg": {
|
||||
"message":" Les comptes importés ne seront pas associés avec votre phrase Seed que vous avez créé au départ dans MetaMask. Obtenir plus d'information sur les comptes importés"
|
||||
"message": " Les comptes importés ne seront pas associés avec votre phrase Seed que vous avez créé au départ dans MetaMask. Obtenir plus d'information sur les comptes importés"
|
||||
},
|
||||
"importAnAccount": {
|
||||
"message": "Importer un compte"
|
||||
@ -686,7 +686,7 @@
|
||||
"noDeposits": {
|
||||
"message": "Aucun dépôt reçu"
|
||||
},
|
||||
"noConversionRateAvailable":{
|
||||
"noConversionRateAvailable": {
|
||||
"message": "Aucun taux de conversion disponible"
|
||||
},
|
||||
"noTransactionHistory": {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,23 +1,24 @@
|
||||
[
|
||||
{ "code": "cs", "name": "Czech" },
|
||||
{ "code": "de", "name": "German" },
|
||||
{ "code": "cs", "name": "Čeština" },
|
||||
{ "code": "de", "name": "Deutsche" },
|
||||
{ "code": "en", "name": "English" },
|
||||
{ "code": "es", "name": "Spanish" },
|
||||
{ "code": "fr", "name": "French" },
|
||||
{ "code": "ht", "name": "Haitian Creole" },
|
||||
{ "code": "hn", "name": "Hindi" },
|
||||
{ "code": "it", "name": "Italian" },
|
||||
{ "code": "ja", "name": "Japanese" },
|
||||
{ "code": "ko", "name": "Korean" },
|
||||
{ "code": "nl", "name": "Dutch" },
|
||||
{ "code": "ph", "name": "Tagalog" },
|
||||
{ "code": "pt", "name": "Portuguese" },
|
||||
{ "code": "ru", "name": "Russian" },
|
||||
{ "code": "sl", "name": "Slovenian" },
|
||||
{ "code": "th", "name": "Thai" },
|
||||
{ "code": "tml", "name": "Tamil" },
|
||||
{ "code": "tr", "name": "Turkish" },
|
||||
{ "code": "vi", "name": "Vietnamese" },
|
||||
{ "code": "zh_CN", "name": "Chinese (Simplified)" },
|
||||
{ "code": "zh_TW", "name": "Chinese (Traditional)" }
|
||||
{ "code": "es", "name": "Español" },
|
||||
{ "code": "fr", "name": "Français" },
|
||||
{ "code": "ht", "name": "Kreyòl ayisyen" },
|
||||
{ "code": "hn", "name": "हिन्दी" },
|
||||
{ "code": "it", "name": "Italiano" },
|
||||
{ "code": "ja", "name": "日本語" },
|
||||
{ "code": "ko", "name": "한국어" },
|
||||
{ "code": "nl", "name": "Nederlands" },
|
||||
{ "code": "ph", "name": "Pilipino" },
|
||||
{ "code": "pl", "name": "Polskie" },
|
||||
{ "code": "pt", "name": "Português" },
|
||||
{ "code": "ru", "name": "Русский" },
|
||||
{ "code": "sl", "name": "Slovenščina" },
|
||||
{ "code": "th", "name": "ไทย" },
|
||||
{ "code": "tml", "name": "தமிழ்" },
|
||||
{ "code": "tr", "name": "Türkçe" },
|
||||
{ "code": "vi", "name": "Tiếng Việt" },
|
||||
{ "code": "zh_CN", "name": "中文(简体)" },
|
||||
{ "code": "zh_TW", "name": "中文(繁體)" }
|
||||
]
|
||||
|
@ -2,6 +2,9 @@
|
||||
"accept": {
|
||||
"message": "Accetta"
|
||||
},
|
||||
"accessingYourCamera": {
|
||||
"message": "Accesso alla fotocamera..."
|
||||
},
|
||||
"account": {
|
||||
"message": "Account"
|
||||
},
|
||||
@ -11,6 +14,15 @@
|
||||
"accountName": {
|
||||
"message": "Nome Account"
|
||||
},
|
||||
"accountOptions": {
|
||||
"message": "Account Options"
|
||||
},
|
||||
"accountSelectionRequired": {
|
||||
"message": "Devi selezionare un account!"
|
||||
},
|
||||
"activityLog": {
|
||||
"message": "log attività"
|
||||
},
|
||||
"address": {
|
||||
"message": "Indirizzo"
|
||||
},
|
||||
@ -23,6 +35,12 @@
|
||||
"addTokens": {
|
||||
"message": "Aggiungi più token"
|
||||
},
|
||||
"addSuggestedTokens": {
|
||||
"message": "Aggiungi Token Suggeriti"
|
||||
},
|
||||
"addAcquiredTokens": {
|
||||
"message": "Aggiungi i token che hai acquistato usando MetaMask"
|
||||
},
|
||||
"amount": {
|
||||
"message": "Importo"
|
||||
},
|
||||
@ -37,9 +55,21 @@
|
||||
"message": "MetaMask",
|
||||
"description": "Il nome dell'applicazione"
|
||||
},
|
||||
"approve": {
|
||||
"message": "Approva"
|
||||
},
|
||||
"approved": {
|
||||
"message": "Approvato"
|
||||
},
|
||||
"attemptingConnect": {
|
||||
"message": "Tentativo di connessione alla blockchain."
|
||||
},
|
||||
"attemptToCancel": {
|
||||
"message": "Tentativo di Annullamento?"
|
||||
},
|
||||
"attemptToCancelDescription": {
|
||||
"message": "Tentare di annullare non garantisce che la transazione verrà annullata. Se annullata, è comunque richiesto pagare alla rete la commissione sulla transazione."
|
||||
},
|
||||
"attributions": {
|
||||
"message": "Attribuzioni"
|
||||
},
|
||||
@ -71,8 +101,11 @@
|
||||
"borrowDharma": {
|
||||
"message": "Prendi in presisito con Dharma (Beta)"
|
||||
},
|
||||
"browserNotSupported": {
|
||||
"message": "Il tuo Browser non è supportato..."
|
||||
},
|
||||
"builtInCalifornia": {
|
||||
"message": "MetaMask è progettato e costruito in California."
|
||||
"message": "MetaMask è progettato e realizzato in California."
|
||||
},
|
||||
"buy": {
|
||||
"message": "Compra"
|
||||
@ -83,8 +116,23 @@
|
||||
"buyCoinbaseExplainer": {
|
||||
"message": "Coinbase è il servizio più popolare al mondo per comprare e vendere Bitcoin, Ethereum e Litecoin."
|
||||
},
|
||||
"bytes": {
|
||||
"message": "Bytes"
|
||||
},
|
||||
"ok": {
|
||||
"message": "Ok"
|
||||
},
|
||||
"cancel": {
|
||||
"message": "Cancella"
|
||||
"message": "Annulla"
|
||||
},
|
||||
"cancelAttempt": {
|
||||
"message": "Tentativo di Annullamento"
|
||||
},
|
||||
"cancellationGasFee": {
|
||||
"message": "Commissione di Annullamento in Gas"
|
||||
},
|
||||
"cancelN": {
|
||||
"message": "Cancel all $1 transactions"
|
||||
},
|
||||
"classicInterface": {
|
||||
"message": "Usa l'interfaccia classica"
|
||||
@ -92,9 +140,18 @@
|
||||
"clickCopy": {
|
||||
"message": "Clicca per Copiare"
|
||||
},
|
||||
"close": {
|
||||
"message": "Chiudi"
|
||||
},
|
||||
"chromeRequiredForHardwareWallets": {
|
||||
"message": "Devi usare MetaMask con Google Chrome per connettere il tuo Portafoglio Hardware"
|
||||
},
|
||||
"confirm": {
|
||||
"message": "Conferma"
|
||||
},
|
||||
"confirmed": {
|
||||
"message": "Confermata"
|
||||
},
|
||||
"confirmContract": {
|
||||
"message": "Conferma Contratto"
|
||||
},
|
||||
@ -104,6 +161,36 @@
|
||||
"confirmTransaction": {
|
||||
"message": "Conferma Transazione"
|
||||
},
|
||||
"connectHardwareWallet": {
|
||||
"message": "Connetti Portafoglio Hardware"
|
||||
},
|
||||
"connect": {
|
||||
"message": "Connetti"
|
||||
},
|
||||
"connecting": {
|
||||
"message": "Connessione..."
|
||||
},
|
||||
"connectingToKovan": {
|
||||
"message": "Connessione alla Rete di test Kovan"
|
||||
},
|
||||
"connectingToMainnet": {
|
||||
"message": "Connessione alla Rete Ethereum Principale"
|
||||
},
|
||||
"connectingToRopsten": {
|
||||
"message": "Connessione alla Rete di test Ropsten"
|
||||
},
|
||||
"connectingToRinkeby": {
|
||||
"message": "Connessione alla Rete di test Rinkeby"
|
||||
},
|
||||
"connectingToUnknown": {
|
||||
"message": "Connessione ad una Rete Sconosciuta"
|
||||
},
|
||||
"connectToLedger": {
|
||||
"message": "Connettersi al Ledger"
|
||||
},
|
||||
"connectToTrezor": {
|
||||
"message": "Connettersi al Trezor"
|
||||
},
|
||||
"continue": {
|
||||
"message": "Continua"
|
||||
},
|
||||
@ -126,11 +213,14 @@
|
||||
"message": "Copiato!"
|
||||
},
|
||||
"copiedSafe": {
|
||||
"message": "Le ho copiate in un posto sicuro"
|
||||
"message": "Le ho copiate in un posto sicuro"
|
||||
},
|
||||
"copy": {
|
||||
"message": "Copia"
|
||||
},
|
||||
"copyAddress": {
|
||||
"message": "Copia l'indirizzo"
|
||||
},
|
||||
"copyToClipboard": {
|
||||
"message": "Copia negli appunti"
|
||||
},
|
||||
@ -156,12 +246,21 @@
|
||||
"currentConversion": {
|
||||
"message": "Cambio Corrente"
|
||||
},
|
||||
"currentLanguage": {
|
||||
"message": "Lingua Corrente"
|
||||
},
|
||||
"currentNetwork": {
|
||||
"message": "Rete Corrente"
|
||||
},
|
||||
"currentRpc": {
|
||||
"message": "RPC Corrente"
|
||||
},
|
||||
"customGas": {
|
||||
"message": "Personalizza Gas"
|
||||
},
|
||||
"customToken": {
|
||||
"message": "Token Personalizzato"
|
||||
},
|
||||
"customize": {
|
||||
"message": "Personalizza"
|
||||
},
|
||||
@ -169,11 +268,11 @@
|
||||
"message": "RPC Personalizzata"
|
||||
},
|
||||
"decimalsMustZerotoTen": {
|
||||
"message": "Il numero di decimali deve essere almeno 0, e non oltre 36."
|
||||
"message": "Il numero di decimali deve essere almeno 0, e non oltre 36."
|
||||
},
|
||||
"decimal": {
|
||||
"message": "Precisione Decimali"
|
||||
},
|
||||
},
|
||||
"defaultNetwork": {
|
||||
"message": "La rete predefinita per transazioni in Ether è la Rete Ethereum Principale."
|
||||
},
|
||||
@ -218,38 +317,59 @@
|
||||
"message": "Deposita Direttamente Ether"
|
||||
},
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Se possiedi già degli Ether, questa è la via più veloce per aggiungere Ether al tuo portafoglio con un deposito diretto."
|
||||
"message": "Se possiedi già degli Ether, questa è la via più veloce per aggiungere Ether al tuo portafoglio con un deposito diretto."
|
||||
},
|
||||
"done": {
|
||||
"message": "Finito"
|
||||
},
|
||||
"downloadGoogleChrome": {
|
||||
"message": "Scarica Google Chrome"
|
||||
},
|
||||
"downloadStateLogs": {
|
||||
"message": "Scarica i log di Stato"
|
||||
},
|
||||
"dontHaveAHardwareWallet": {
|
||||
"message": "Non hai un portafoglio hardware?"
|
||||
},
|
||||
"dropped": {
|
||||
"message": "Abbandonata"
|
||||
},
|
||||
"edit": {
|
||||
"message": "Modifica"
|
||||
},
|
||||
"editAccountName": {
|
||||
"message": "Modifica Nome Account"
|
||||
},
|
||||
"editingTransaction": {
|
||||
"message": "Modifica la transazione"
|
||||
},
|
||||
"emailUs": {
|
||||
"message": "Mandaci una mail!"
|
||||
},
|
||||
"encryptNewDen": {
|
||||
"message": "Cripta il tuo nuovo DEN"
|
||||
},
|
||||
"ensNameNotFound": {
|
||||
"message": "Nome ENS non trovato"
|
||||
},
|
||||
"enterPassword": {
|
||||
"message": "Inserisci password"
|
||||
},
|
||||
"enterPasswordConfirm": {
|
||||
"message": "Inserisci la tua password per confermare"
|
||||
},
|
||||
"enterPasswordContinue": {
|
||||
"message": "Inserisci la tua password per continuare"
|
||||
},
|
||||
"etherscanView": {
|
||||
"message": "Vedi account su Etherscan"
|
||||
},
|
||||
"exchangeRate": {
|
||||
"message": "Tasso di cambio"
|
||||
},
|
||||
"expandView": {
|
||||
"message": "Expand View"
|
||||
},
|
||||
"exportPrivateKey": {
|
||||
"message": "Esporta Chiave Privata"
|
||||
},
|
||||
@ -257,7 +377,7 @@
|
||||
"message": "Esporta chiave privata a tuo rischio."
|
||||
},
|
||||
"failed": {
|
||||
"message": "Fallito"
|
||||
"message": "Fallita"
|
||||
},
|
||||
"fiat": {
|
||||
"message": "FIAT",
|
||||
@ -270,6 +390,9 @@
|
||||
"followTwitter": {
|
||||
"message": "Seguici su Twitter"
|
||||
},
|
||||
"forgetDevice": {
|
||||
"message": "Dimentica questo dispositivo"
|
||||
},
|
||||
"from": {
|
||||
"message": "Da"
|
||||
},
|
||||
@ -279,6 +402,9 @@
|
||||
"fromShapeShift": {
|
||||
"message": "Da ShapeShift"
|
||||
},
|
||||
"functionType": {
|
||||
"message": "Tipo della Funzione"
|
||||
},
|
||||
"gas": {
|
||||
"message": "Gas",
|
||||
"description": "Piccola indicazione del costo del gas"
|
||||
@ -310,6 +436,9 @@
|
||||
"gasPriceRequired": {
|
||||
"message": "Prezzo Gas Richiesto"
|
||||
},
|
||||
"generatingTransaction": {
|
||||
"message": "Generando la transazione"
|
||||
},
|
||||
"getEther": {
|
||||
"message": "Ottieni Ether"
|
||||
},
|
||||
@ -317,10 +446,28 @@
|
||||
"message": "Ottieni Get Ether da un faucet per $1",
|
||||
"description": "Visualizza il nome della rete per il faucet Ether"
|
||||
},
|
||||
"getHelp": {
|
||||
"message": "Aiuto."
|
||||
},
|
||||
"greaterThanMin": {
|
||||
"message": "deve essere maggiore o uguale a $1.",
|
||||
"description": "aiuto per inserire un input esadecimale come decimale"
|
||||
},
|
||||
"hardware": {
|
||||
"message": "hardware"
|
||||
},
|
||||
"hardwareWalletConnected": {
|
||||
"message": "Portafoglio hardware connesso"
|
||||
},
|
||||
"hardwareWallets": {
|
||||
"message": "Connetti portafoglio hardware"
|
||||
},
|
||||
"hardwareWalletsMsg": {
|
||||
"message": "Selezione un portafoglio hardware che vuoi utilizzare con MetaMask"
|
||||
},
|
||||
"havingTroubleConnecting": {
|
||||
"message": "Problemi di connessione?"
|
||||
},
|
||||
"here": {
|
||||
"message": "qui",
|
||||
"description": "per intendere -clicca qui- per maggiori informazioni (va con troubleTokenBalances)"
|
||||
@ -328,6 +475,9 @@
|
||||
"hereList": {
|
||||
"message": "Questa è una lista!!!!"
|
||||
},
|
||||
"hexData": {
|
||||
"message": "Dati Hex"
|
||||
},
|
||||
"hide": {
|
||||
"message": "Nascondi"
|
||||
},
|
||||
@ -337,11 +487,14 @@
|
||||
"hideTokenPrompt": {
|
||||
"message": "Nascondi Token?"
|
||||
},
|
||||
"history": {
|
||||
"message": "Storico"
|
||||
},
|
||||
"howToDeposit": {
|
||||
"message": "Come vuoi depositare Ether?"
|
||||
},
|
||||
"holdEther": {
|
||||
"message": "Ti permette di tenere ether & token, e serve da ponte per le applicazioni decentralizzate."
|
||||
"message": "Ti permette di tenere ether & token, e serve da ponte per le applicazioni decentralizzate."
|
||||
},
|
||||
"import": {
|
||||
"message": "Importa",
|
||||
@ -351,7 +504,7 @@
|
||||
"message": "Importa Account"
|
||||
},
|
||||
"importAccountMsg": {
|
||||
"message":" Gli account importati non saranno associati alla frase seed originariamente creata con MetaMask. Impara di più sugli account importati "
|
||||
"message": " Gli account importati non saranno associati alla frase seed originariamente creata con MetaMask. Impara di più sugli account importati "
|
||||
},
|
||||
"importAnAccount": {
|
||||
"message": "Importa un account"
|
||||
@ -363,9 +516,18 @@
|
||||
"message": "Importato",
|
||||
"description": "stato che conferma che un account è stato totalmente caricato nel portachiavi"
|
||||
},
|
||||
"importUsingSeed": {
|
||||
"message": "Importa account con frase seed"
|
||||
},
|
||||
"info": {
|
||||
"message": "Informazioni"
|
||||
},
|
||||
"infoHelp": {
|
||||
"message": "Informazioni & Aiuto"
|
||||
},
|
||||
"initialTransactionConfirmed": {
|
||||
"message": "La transazione iniziale è stata confermata dalla rete. Clicca OK per tornare indietro."
|
||||
},
|
||||
"insufficientFunds": {
|
||||
"message": "Fondi non sufficienti."
|
||||
},
|
||||
@ -390,19 +552,34 @@
|
||||
"invalidRPC": {
|
||||
"message": "URI RPC invalido"
|
||||
},
|
||||
"invalidSeedPhrase": {
|
||||
"message": "Frase seed non valida"
|
||||
},
|
||||
"jsonFail": {
|
||||
"message": "Qualcosa è andato storto. Assicurati che il file JSON sia formattato correttamente."
|
||||
"message": "Qualcosa è andato storto. Assicurati che il file JSON sia formattato correttamente."
|
||||
},
|
||||
"jsonFile": {
|
||||
"message": "File JSON",
|
||||
"description": "formato per importare un account"
|
||||
},
|
||||
"keepTrackTokens": {
|
||||
"message": "Tieni traccia dei tokens che hai acquistato con il tuo account MetaMask."
|
||||
},
|
||||
"kovan": {
|
||||
"message": "Rete di test Kovan"
|
||||
},
|
||||
"knowledgeDataBase": {
|
||||
"message": "Visita la nostra Knowledge Base"
|
||||
},
|
||||
"max": {
|
||||
"message": "Massimo"
|
||||
},
|
||||
"learnMore": {
|
||||
"message": "Scopri di più"
|
||||
},
|
||||
"ledgerAccountRestriction": {
|
||||
"message": "E' necessario utilizzare l'ultimo account prima di poterne aggiungere uno nuovo."
|
||||
},
|
||||
"lessThanMax": {
|
||||
"message": "deve essere minore o uguale a $1.",
|
||||
"description": "aiuto per inserire un input esadecimale come decimale"
|
||||
@ -410,6 +587,9 @@
|
||||
"likeToAddTokens": {
|
||||
"message": "Vorresti aggiungere questi token?"
|
||||
},
|
||||
"links": {
|
||||
"message": "Collegamenti"
|
||||
},
|
||||
"limit": {
|
||||
"message": "Limite"
|
||||
},
|
||||
@ -437,17 +617,26 @@
|
||||
"mainnet": {
|
||||
"message": "Rete Ethereum Principale"
|
||||
},
|
||||
"menu": {
|
||||
"message": "Menu"
|
||||
},
|
||||
"message": {
|
||||
"message": "Messaggio"
|
||||
},
|
||||
"metamaskDescription": {
|
||||
"message": "MetaMask è una cassaforte sicura per identità su Ethereum."
|
||||
},
|
||||
"metamaskSeedWords": {
|
||||
"message": "Parole Seed di MetaMask"
|
||||
},
|
||||
"metamaskVersion": {
|
||||
"message": "versione di MetaMask"
|
||||
},
|
||||
"min": {
|
||||
"message": "Minimo"
|
||||
},
|
||||
"myAccounts": {
|
||||
"message": "Account Miei"
|
||||
"message": "Miei Account"
|
||||
},
|
||||
"mustSelectOne": {
|
||||
"message": "Devi selezionare almeno un token."
|
||||
@ -469,6 +658,9 @@
|
||||
"networks": {
|
||||
"message": "Reti"
|
||||
},
|
||||
"nevermind": {
|
||||
"message": "Non importa"
|
||||
},
|
||||
"newAccount": {
|
||||
"message": "Nuovo Account"
|
||||
},
|
||||
@ -482,6 +674,9 @@
|
||||
"newPassword": {
|
||||
"message": "Nuova Password (minimo 8 caratteri)"
|
||||
},
|
||||
"newPassword8Chars": {
|
||||
"message": "Nuova Password (minimo 8 caratteri)"
|
||||
},
|
||||
"newRecipient": {
|
||||
"message": "Nuovo Destinatario"
|
||||
},
|
||||
@ -489,7 +684,7 @@
|
||||
"message": "Nuovo URL RPC"
|
||||
},
|
||||
"next": {
|
||||
"message": "Prossimo"
|
||||
"message": "Avanti"
|
||||
},
|
||||
"noAddressForName": {
|
||||
"message": "Nessun indirizzo è stato impostato per questo nome."
|
||||
@ -497,32 +692,75 @@
|
||||
"noDeposits": {
|
||||
"message": "Nessun deposito ricevuto"
|
||||
},
|
||||
"noConversionRateAvailable": {
|
||||
"message": "Tasso di Conversione non Disponibile"
|
||||
},
|
||||
"noTransactionHistory": {
|
||||
"message": "Nessuna cronologia delle transazioni."
|
||||
},
|
||||
"noTransactions": {
|
||||
"message": "Nessuna Transazione"
|
||||
},
|
||||
"notFound": {
|
||||
"message": "Non Trovata"
|
||||
},
|
||||
"notStarted": {
|
||||
"message": "Non Iniziato"
|
||||
},
|
||||
"noWebcamFoundTitle": {
|
||||
"message": "Webcam non trovata"
|
||||
},
|
||||
"noWebcamFound": {
|
||||
"message": "La webcam del tuo computer non è stata trovata. Per favore riprovaci."
|
||||
},
|
||||
"oldUI": {
|
||||
"message": "Vecchia interfaccia"
|
||||
},
|
||||
"oldUIMessage": {
|
||||
"message": "Sei ritornato alla vecchia interfaccia. Puoi ritornare alla nuova interfaccia tramite l'opzione nel menu a discesa in alto a destra."
|
||||
},
|
||||
"onlySendToEtherAddress": {
|
||||
"message": "Invia ETH solamente ad un account Ethereum."
|
||||
},
|
||||
"onlySendTokensToAccountAddress": {
|
||||
"message": "Invia solamente $1 ad un account Ethereum.",
|
||||
"description": "mostra il simbolo del token"
|
||||
},
|
||||
"openInTab": {
|
||||
"message": "Apri in una scheda"
|
||||
},
|
||||
"or": {
|
||||
"message": "o",
|
||||
"description": "scelta tra creare o importare un nuovo account"
|
||||
},
|
||||
"orderOneHere": {
|
||||
"message": "Compra un Trezor o un Ledger e tieni i tuoi soldi al sicuro"
|
||||
},
|
||||
"origin": {
|
||||
"message": "Origine"
|
||||
},
|
||||
"outgoing": {
|
||||
"message": "In Uscita"
|
||||
},
|
||||
"parameters": {
|
||||
"message": "Parametri"
|
||||
},
|
||||
"password": {
|
||||
"message": "Password"
|
||||
},
|
||||
"passwordCorrect": {
|
||||
"message": "Assicurati che la password sia corretta."
|
||||
},
|
||||
"passwordsDontMatch": {
|
||||
"message": "Le Password Non Corrispondonos"
|
||||
},
|
||||
"passwordMismatch": {
|
||||
"message": "le password non corrispondono",
|
||||
"description": "nella creazione della password, le due password all'interno dei campi non corrispondono"
|
||||
},
|
||||
"passwordNotLongEnough": {
|
||||
"message": "Password non abbastanza lunga"
|
||||
},
|
||||
"passwordShort": {
|
||||
"message": "password non sufficientemente lunga",
|
||||
"description": "nella creazione della password, la password non è lunga abbastanza"
|
||||
@ -534,12 +772,21 @@
|
||||
"pasteSeed": {
|
||||
"message": "Incolla la tua frase seed qui!"
|
||||
},
|
||||
"pending": {
|
||||
"message": "in corso"
|
||||
},
|
||||
"personalAddressDetected": {
|
||||
"message": "Rilevato indirizzo personale. Inserisci l'indirizzo del contratto del token."
|
||||
},
|
||||
"pleaseReviewTransaction": {
|
||||
"message": "Ricontrolla la tua transazione."
|
||||
},
|
||||
"popularTokens": {
|
||||
"message": "Tokens Popolari"
|
||||
},
|
||||
"prev": {
|
||||
"message": "Precedente"
|
||||
},
|
||||
"privacyMsg": {
|
||||
"message": "Politica sulla Privacy"
|
||||
},
|
||||
@ -556,8 +803,11 @@
|
||||
"qrCode": {
|
||||
"message": "Mostra Codice QR"
|
||||
},
|
||||
"queue": {
|
||||
"message": "Coda"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "Puoi aggiungere nuovamente questo token in futuro andando in “Aggiungi token” nel menu delle opzioni del tuo account."
|
||||
"message": "Puoi aggiungere nuovamente questo token in futuro andando in “Aggiungi token” nel menu delle opzioni del tuo account."
|
||||
},
|
||||
"readMore": {
|
||||
"message": "Leggi di più qui."
|
||||
@ -574,36 +824,87 @@
|
||||
"refundAddress": {
|
||||
"message": "Indirizzo di Rimborso"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Reject"
|
||||
},
|
||||
"rejectAll": {
|
||||
"message": "Reject All"
|
||||
},
|
||||
"rejectTxsN": {
|
||||
"message": "Reject $1 transactions"
|
||||
},
|
||||
"rejectTxsDescription": {
|
||||
"message": "You are about to batch reject $1 transactions."
|
||||
},
|
||||
"rejected": {
|
||||
"message": "Respinta"
|
||||
},
|
||||
"reset": {
|
||||
"message": "Reset"
|
||||
},
|
||||
"resetAccount": {
|
||||
"message": "Resetta Account"
|
||||
},
|
||||
"resetAccountDescription": {
|
||||
"message": "Resettare il tuo account cancellerà lo storico delle transazioni."
|
||||
},
|
||||
"restoreFromSeed": {
|
||||
"message": "Ripristina da una frase seed"
|
||||
},
|
||||
"restoreVault": {
|
||||
"message": "Ripristina Cassaforte"
|
||||
},
|
||||
"restoreAccountWithSeed": {
|
||||
"message": "Ripristina Account con la Frase Seed"
|
||||
},
|
||||
"required": {
|
||||
"message": "Richiesto"
|
||||
},
|
||||
"retryWithMoreGas": {
|
||||
"message": "Riprova con un prezzo del Gas maggiore qui"
|
||||
},
|
||||
"restore": {
|
||||
"message": "Ripristina"
|
||||
},
|
||||
"revealSeedWords": {
|
||||
"message": "Rivela Frase Seed"
|
||||
},
|
||||
"revealSeedWordsTitle": {
|
||||
"message": "Frase Seed"
|
||||
},
|
||||
"revealSeedWordsDescription": {
|
||||
"message": "Se cambierai browser o computer, ti servirà questa frase seed per accedere ai tuoi account. Salvala in un posto sicuro e segreto."
|
||||
},
|
||||
"revealSeedWordsWarningTitle": {
|
||||
"message": "NON CONDIVIDERE questa frase con nessuno!"
|
||||
},
|
||||
"revealSeedWordsWarning": {
|
||||
"message": "Non ripristinare la tua frase seed in pubblico!. Queste parole possono essere usate per rubare il tuo account."
|
||||
},
|
||||
"revert": {
|
||||
"message": "Annulla"
|
||||
},
|
||||
"remove": {
|
||||
"message": "rimuovi"
|
||||
},
|
||||
"removeAccount": {
|
||||
"message": "Rimuovi account"
|
||||
},
|
||||
"removeAccountDescription": {
|
||||
"message": "Questo account sarà rimosso dal tuo portafoglio. Per favore assicurati che hai la frase seed originale o la chiave privata per questo account importato prima di continuare. Puoi nuovamente importare o creare un account dal menù a tendina. "
|
||||
},
|
||||
"readyToConnect": {
|
||||
"message": "Pronto a Connetterti?"
|
||||
},
|
||||
"rinkeby": {
|
||||
"message": "Rete di test Rinkeby"
|
||||
},
|
||||
"ropsten": {
|
||||
"message": "Rete di test Ropsten"
|
||||
},
|
||||
"rpc": {
|
||||
"message": "RPC Personalizzata"
|
||||
},
|
||||
"sampleAccountName": {
|
||||
"message": "Es: Il mio nuovo account",
|
||||
"description": "Aiuta l'utente a capire il concetto di aggiungere un nome leggibile al loro account"
|
||||
@ -611,6 +912,9 @@
|
||||
"save": {
|
||||
"message": "Salva"
|
||||
},
|
||||
"saveAsCsvFile": {
|
||||
"message": "Salva Come File CSV"
|
||||
},
|
||||
"saveAsFile": {
|
||||
"message": "Salva come File",
|
||||
"description": "Processo per esportare un account"
|
||||
@ -618,9 +922,18 @@
|
||||
"saveSeedAsFile": {
|
||||
"message": "Salva la Frase Seed come File"
|
||||
},
|
||||
"scanInstructions": {
|
||||
"message": "Posizione il codice QR davanti alla fotocamera"
|
||||
},
|
||||
"scanQrCode": {
|
||||
"message": "Scansiona Codice QR"
|
||||
},
|
||||
"search": {
|
||||
"message": "Cerca"
|
||||
},
|
||||
"searchResults": {
|
||||
"message": "Risultati Ricerca"
|
||||
},
|
||||
"secretPhrase": {
|
||||
"message": "Inserisci la tua frase segreta di dodici parole per ripristinare la cassaforte."
|
||||
},
|
||||
@ -633,6 +946,9 @@
|
||||
"selectCurrency": {
|
||||
"message": "Seleziona Moneta"
|
||||
},
|
||||
"selectLocale": {
|
||||
"message": "Selezione Lingua"
|
||||
},
|
||||
"selectService": {
|
||||
"message": "Seleziona Servizio"
|
||||
},
|
||||
@ -648,6 +964,33 @@
|
||||
"sendTokens": {
|
||||
"message": "Invia Tokens"
|
||||
},
|
||||
"sentEther": {
|
||||
"message": "ether inviati"
|
||||
},
|
||||
"sentTokens": {
|
||||
"message": "tokens inviati"
|
||||
},
|
||||
"separateEachWord": {
|
||||
"message": "Separa ogni parola con un solo spazio"
|
||||
},
|
||||
"searchTokens": {
|
||||
"message": "Cerca Tokens"
|
||||
},
|
||||
"selectAnAddress": {
|
||||
"message": "Seleziona un Indirizzo"
|
||||
},
|
||||
"selectAnAccount": {
|
||||
"message": "Seleziona un Account"
|
||||
},
|
||||
"selectAnAccountHelp": {
|
||||
"message": "Selezione l'account da visualizzare in MetaMask"
|
||||
},
|
||||
"selectHdPath": {
|
||||
"message": "Seleziona Percorso HD"
|
||||
},
|
||||
"selectPathHelp": {
|
||||
"message": "Se non vedi il tuo account Ledger esistente di seguito, prova a cambiare il percorso in \"Legacy (MEW / MyCrypto)\""
|
||||
},
|
||||
"sendTokensAnywhere": {
|
||||
"message": "Invia Tokens a chiunque abbia un account Ethereum"
|
||||
},
|
||||
@ -663,14 +1006,26 @@
|
||||
"showQRCode": {
|
||||
"message": "Mostra Codie QR"
|
||||
},
|
||||
"showHexData": {
|
||||
"message": "Mostra Dati Hex"
|
||||
},
|
||||
"showHexDataDescription": {
|
||||
"message": "Seleziona per mostrare il campo dei dati hex nella schermata di invio"
|
||||
},
|
||||
"sign": {
|
||||
"message": "Firma"
|
||||
},
|
||||
"signatureRequest": {
|
||||
"message": "Firma Richiesta"
|
||||
},
|
||||
"signed": {
|
||||
"message": "Firmata"
|
||||
},
|
||||
"signMessage": {
|
||||
"message": "Firma Messaggio"
|
||||
},
|
||||
"signNotice": {
|
||||
"message": "Firmare questo messaggio può avere effetti collaterali pericolosi. \nFirma messaggi da siti di cui ti fidi totalmente. \nQuesto metodo pericoloso sarà rimosso in versioni future."
|
||||
"message": "Firmare questo messaggio può avere effetti collaterali pericolosi. \nFirma messaggi da siti di cui ti fidi totalmente. \nQuesto metodo pericoloso sarà rimosso in versioni future."
|
||||
},
|
||||
"sigRequest": {
|
||||
"message": "Firma Richiesta"
|
||||
@ -681,6 +1036,15 @@
|
||||
"spaceBetween": {
|
||||
"message": "ci può essere solo uno spazio tra le parole"
|
||||
},
|
||||
"speedUp": {
|
||||
"message": "velocizza"
|
||||
},
|
||||
"speedUpTitle": {
|
||||
"message": "Velocizza Transazione"
|
||||
},
|
||||
"speedUpSubtitle": {
|
||||
"message": "Aumenta il prezzo del gas per tentare di sovrascrivere e velocizzare la transazione"
|
||||
},
|
||||
"status": {
|
||||
"message": "Stato"
|
||||
},
|
||||
@ -690,9 +1054,33 @@
|
||||
"stateLogsDescription": {
|
||||
"message": "I log di stato contengono i tuoi indirizzi pubblici e le transazioni effettuate."
|
||||
},
|
||||
"stateLogError": {
|
||||
"message": "Errore nel recupero dei log di stato."
|
||||
},
|
||||
"step1HardwareWallet": {
|
||||
"message": "1. Connetti Portafoglio Hardware"
|
||||
},
|
||||
"step1HardwareWalletMsg": {
|
||||
"message": "Connetti il tuo portafoglio hardware al tuo computer."
|
||||
},
|
||||
"step2HardwareWallet": {
|
||||
"message": "2. Seleziona un Account"
|
||||
},
|
||||
"step2HardwareWalletMsg": {
|
||||
"message": "Selezione l'account che vuoi vedere. Puoi selezionarne solo uno alla volta."
|
||||
},
|
||||
"step3HardwareWallet": {
|
||||
"message": "3. Inizia a usare dApps e molto altro ancora!"
|
||||
},
|
||||
"step3HardwareWalletMsg": {
|
||||
"message": "Usa il tuo account hardware come utilizzeresti qualsiasi account Ethereum. Accedi alle dApps, invia Eth, compra e conserva token ERC20 e token non fungibili come CryptoKitties"
|
||||
},
|
||||
"submit": {
|
||||
"message": "Invia"
|
||||
},
|
||||
"submitted": {
|
||||
"message": "Inviata"
|
||||
},
|
||||
"supportCenter": {
|
||||
"message": "Visita il nostro Centro di Supporto"
|
||||
},
|
||||
@ -715,6 +1103,9 @@
|
||||
"message": "$1 a ETH via ShapeShift",
|
||||
"description": "il sistema riempirà il tipo di deposito all'inizio del messaggio"
|
||||
},
|
||||
"token": {
|
||||
"message": "Token"
|
||||
},
|
||||
"tokenAddress": {
|
||||
"message": "Indirizzo Token"
|
||||
},
|
||||
@ -736,23 +1127,62 @@
|
||||
"total": {
|
||||
"message": "Totale"
|
||||
},
|
||||
"transaction": {
|
||||
"message": "transazione"
|
||||
},
|
||||
"transactionConfirmed": {
|
||||
"message": "Transazione confermata il $2."
|
||||
},
|
||||
"transactionCreated": {
|
||||
"message": "Transazione di valore $1 creata il $2."
|
||||
},
|
||||
"transactionWithNonce": {
|
||||
"message": "Transazione $1"
|
||||
},
|
||||
"transactionDropped": {
|
||||
"message": "Transazione abbandonata il $2."
|
||||
},
|
||||
"transactionSubmitted": {
|
||||
"message": "Transazione inviata il $2."
|
||||
},
|
||||
"transactionUpdated": {
|
||||
"message": "Transazione aggiornata il $2."
|
||||
},
|
||||
"transactionUpdatedGas": {
|
||||
"message": "Transazione aggiornata con un prezzo del gas di $1 il $2."
|
||||
},
|
||||
"transactions": {
|
||||
"message": "transazioni"
|
||||
},
|
||||
"transactionError": {
|
||||
"message": "Errore Transazione. Eccceziona generata nel codice del contratto."
|
||||
},
|
||||
"transactionMemo": {
|
||||
"message": "Promemoria Transazione (opzionale)"
|
||||
},
|
||||
"transactionNumber": {
|
||||
"message": "Numero Transazione"
|
||||
},
|
||||
"transfer": {
|
||||
"message": "Trasferisci"
|
||||
},
|
||||
"transferFrom": {
|
||||
"message": "Transfer From"
|
||||
},
|
||||
"transfers": {
|
||||
"message": "Trasferimenti"
|
||||
},
|
||||
"trezorHardwareWallet": {
|
||||
"message": "TREZOR Portafoglio Hardware"
|
||||
},
|
||||
"troubleTokenBalances": {
|
||||
"message": "Abbiamo avuto un problema a caricare il bilancio dei tuoi token. Puoi vederlo ",
|
||||
"description": "Seguito da un link (qui) per vedere il bilancio dei token"
|
||||
},
|
||||
"twelveWords": {
|
||||
"tryAgain": {
|
||||
"message": "Prova di nuovo"
|
||||
},
|
||||
"twelveWords": {
|
||||
"message": "Queste 12 parole sono l'unico modo per ripristinare i tuoi account MetaMask. \nSalvale in un posto sicuro e segreto."
|
||||
},
|
||||
"typePassword": {
|
||||
@ -762,20 +1192,47 @@
|
||||
"message": "Benvenuto alla nuova interfaccia (Beta)"
|
||||
},
|
||||
"uiWelcomeMessage": {
|
||||
"message": "Stai utilizzanto la nuova interfaccia di MetaMask. Guarda in giro, prova nuove funzionalità come inviare token, e facci sapere se hai dei problemi."
|
||||
"message": "Stai utilizzanto la nuova interfaccia di MetaMask. Guarda in giro, prova nuove funzionalità come inviare token, e facci sapere se hai dei problemi."
|
||||
},
|
||||
"unapproved": {
|
||||
"message": "Non approvata"
|
||||
},
|
||||
"unavailable": {
|
||||
"message": "Non Disponibile"
|
||||
},
|
||||
"units": {
|
||||
"message": "unità"
|
||||
},
|
||||
"unknown": {
|
||||
"message": "Sconosciuto"
|
||||
},
|
||||
"unknownFunction": {
|
||||
"message": "Funzione Sconosciuta"
|
||||
},
|
||||
"unknownNetwork": {
|
||||
"message": "Rete Privata Sconosciuta"
|
||||
},
|
||||
"unknownNetworkId": {
|
||||
"message": "ID rete sconosciuto"
|
||||
},
|
||||
"unknownQrCode": {
|
||||
"message": "Errore: Non siamo riusciti a identificare il codice QR"
|
||||
},
|
||||
"unknownCameraErrorTitle": {
|
||||
"message": "Ooops! Qualcosa è andato storto...."
|
||||
},
|
||||
"unknownCameraError": {
|
||||
"message": "C'è stato un errore nel tentativo di accedere alla fotocamera. Per favore prova di nuovo..."
|
||||
},
|
||||
"unlock": {
|
||||
"message": "Sblocca"
|
||||
},
|
||||
"unlockMessage": {
|
||||
"message": "Il web decentralizzato ti attende"
|
||||
},
|
||||
"updatedWithDate": {
|
||||
"message": "Aggiornata $1"
|
||||
},
|
||||
"uriErrorMsg": {
|
||||
"message": "Gli URI richiedono un prefisso HTTP/HTTPS."
|
||||
},
|
||||
@ -798,22 +1255,40 @@
|
||||
"viewAccount": {
|
||||
"message": "Vedi Account"
|
||||
},
|
||||
"viewOnEtherscan": {
|
||||
"message": "Vedi su Etherscan"
|
||||
},
|
||||
"visitWebSite": {
|
||||
"message": "Visita il nostro sito web"
|
||||
},
|
||||
},
|
||||
"walletSeed": {
|
||||
"message": "Seed del Portafoglio"
|
||||
},
|
||||
"warning": {
|
||||
"message": "Attenzione"
|
||||
},
|
||||
"welcomeBack": {
|
||||
"message": "Bentornato!"
|
||||
},
|
||||
"welcomeBeta": {
|
||||
"message": "Benvenuto nella Beta di MetaMask"
|
||||
},
|
||||
"whatsThis": {
|
||||
"message": "Cos'è questo?"
|
||||
},
|
||||
"yesLetsTry": {
|
||||
"message": "Si, proviamo"
|
||||
},
|
||||
"youNeedToAllowCameraAccess": {
|
||||
"message": "Devi consentire l'accesso alla fotocamera per usare questa funzionalità."
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "E' richiesta la tua firma"
|
||||
},
|
||||
"youSign": {
|
||||
"message": "Ti stai connettendo"
|
||||
},
|
||||
"yourPrivateSeedPhrase": {
|
||||
"message": "La tua frase seed privata"
|
||||
}
|
||||
}
|
||||
|
@ -315,7 +315,7 @@
|
||||
"message": "アカウントのインポート"
|
||||
},
|
||||
"importAccountMsg": {
|
||||
"message":"追加したアカウントはMetaMaskのアカウントパスフレーズとは関連付けられません。インポートしたアカウントについての詳細は"
|
||||
"message": "追加したアカウントはMetaMaskのアカウントパスフレーズとは関連付けられません。インポートしたアカウントについての詳細は"
|
||||
},
|
||||
"importAnAccount": {
|
||||
"message": "アカウントをインポート"
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -351,7 +351,7 @@
|
||||
"message": "Account importeren"
|
||||
},
|
||||
"importAccountMsg": {
|
||||
"message":" Geïmporteerde accounts worden niet gekoppeld aan de seedphrase van uw oorspronkelijk gemaakte MetaMask-account. Meer informatie over geïmporteerde accounts"
|
||||
"message": " Geïmporteerde accounts worden niet gekoppeld aan de seedphrase van uw oorspronkelijk gemaakte MetaMask-account. Meer informatie over geïmporteerde accounts"
|
||||
},
|
||||
"importAnAccount": {
|
||||
"message": "Importeer een account"
|
||||
@ -435,7 +435,7 @@
|
||||
"message": "back-up woorden hebben alleen kleine letters"
|
||||
},
|
||||
"mainnet": {
|
||||
"message": "belangrijkste Ethereum-netwerk"
|
||||
"message": "Main Netwerk"
|
||||
},
|
||||
"message": {
|
||||
"message": "Bericht"
|
||||
|
1213
app/_locales/pl/messages.json
Normal file
1213
app/_locales/pl/messages.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -173,7 +173,7 @@
|
||||
},
|
||||
"decimal": {
|
||||
"message": "Precisão em Decimais"
|
||||
},
|
||||
},
|
||||
"defaultNetwork": {
|
||||
"message": "A rede pré definida para transações em Ether é a Main Net."
|
||||
},
|
||||
@ -351,7 +351,7 @@
|
||||
"message": "Importar Conta"
|
||||
},
|
||||
"importAccountMsg": {
|
||||
"message":"Contas importadas não irão ser associadas com a frase seed da conta criada originalmente pelo MetaMask. Saiba mais sobre contas importadas."
|
||||
"message": "Contas importadas não irão ser associadas com a frase seed da conta criada originalmente pelo MetaMask. Saiba mais sobre contas importadas."
|
||||
},
|
||||
"importAnAccount": {
|
||||
"message": "Importar uma conta"
|
||||
@ -800,7 +800,7 @@
|
||||
},
|
||||
"visitWebSite": {
|
||||
"message": "Visite o nosso site"
|
||||
},
|
||||
},
|
||||
"warning": {
|
||||
"message": "Aviso"
|
||||
},
|
||||
|
@ -366,7 +366,7 @@
|
||||
"message": "Импортировать счет"
|
||||
},
|
||||
"importAccountMsg": {
|
||||
"message":" Импортированные счета не будут ассоциированы с вашей ключевой фразой, созданной MetaMask. Узнать больше про импорт счетов "
|
||||
"message": " Импортированные счета не будут ассоциированы с вашей ключевой фразой, созданной MetaMask. Узнать больше про импорт счетов "
|
||||
},
|
||||
"importAnAccount": {
|
||||
"message": "Импортировать аккаунт"
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -351,7 +351,7 @@
|
||||
"message": "นำเข้าบัญชี"
|
||||
},
|
||||
"importAccountMsg": {
|
||||
"message":"บัญชีที่นำเข้าจะไม่ถูกรวมกับบัญชีที่สร้างด้วยคำเเริ่มต้นบนเมต้ามาร์สในตอนแรก เรียนรู้เพิ่มเติมเกี่ยวกับบัญชีที่นำเข้า"
|
||||
"message": "บัญชีที่นำเข้าจะไม่ถูกรวมกับบัญชีที่สร้างด้วยคำเเริ่มต้นบนเมต้ามาร์สในตอนแรก เรียนรู้เพิ่มเติมเกี่ยวกับบัญชีที่นำเข้า"
|
||||
},
|
||||
"importAnAccount": {
|
||||
"message": "นำเข้าบัญชี"
|
||||
|
@ -372,7 +372,7 @@
|
||||
"message": "கணக்கை இறக்குமதி செய்க"
|
||||
},
|
||||
"importAccountMsg": {
|
||||
"message":" இறக்குமதி செய்யப்பட்ட கணக்கு உங்கள் முதலில் உருவாக்கப்பட்ட மெட்டாமாஸ்க் கணக்கு விதை மூலம் தொடர்புடையதாக இருக்காது. இறக்குமதி செய்யப்பட்ட கணக்குகள் பற்றி மேலும் அறிக "
|
||||
"message": " இறக்குமதி செய்யப்பட்ட கணக்கு உங்கள் முதலில் உருவாக்கப்பட்ட மெட்டாமாஸ்க் கணக்கு விதை மூலம் தொடர்புடையதாக இருக்காது. இறக்குமதி செய்யப்பட்ட கணக்குகள் பற்றி மேலும் அறிக "
|
||||
},
|
||||
"importAnAccount": {
|
||||
"message": "ஒரு கணக்கை இறக்குமதி செய்க"
|
||||
@ -730,7 +730,7 @@
|
||||
"message": "அமைப்புகள்"
|
||||
},
|
||||
"info": {
|
||||
"message": "தகவல்"
|
||||
"message": "தகவல்"
|
||||
},
|
||||
"shapeshiftBuy": {
|
||||
"message": "Shapeshift உடன் வாங்கவும்"
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -387,7 +387,7 @@
|
||||
"message": "导入账户"
|
||||
},
|
||||
"importAccountMsg": {
|
||||
"message":" Imported accounts will not be associated with your originally created MetaMask account seedphrase. Learn more about imported accounts "
|
||||
"message": " Imported accounts will not be associated with your originally created MetaMask account seedphrase. Learn more about imported accounts "
|
||||
},
|
||||
"importAnAccount": {
|
||||
"message": "导入一个账户"
|
||||
@ -511,7 +511,7 @@
|
||||
"description": "User is important an account and needs to add a file to continue"
|
||||
},
|
||||
"needImportPassword": {
|
||||
"message": "必须为已选择的文件输入密码。",
|
||||
"message": "必须为已选择的文件输入密码。",
|
||||
"description": "Password and file needed to import an account"
|
||||
},
|
||||
"negativeETH": {
|
||||
@ -561,7 +561,7 @@
|
||||
"message": "旧版界面"
|
||||
},
|
||||
"oldUIMessage": {
|
||||
"message": "你已经切换到旧版界面。 你可以通过右上方下拉菜单中的选项切换回新的用户界面。"
|
||||
"message": "你已经切换到旧版界面。 你可以通过右上方下拉菜单中的选项切换回新的用户界面。"
|
||||
},
|
||||
"or": {
|
||||
"message": "或",
|
||||
@ -605,7 +605,7 @@
|
||||
"description": "select this type of file to use to import an account"
|
||||
},
|
||||
"privateKeyWarning": {
|
||||
"message": "注意:永远不要公开这个私钥。任何拥有你的私钥的人都可以窃取你帐户中的任何资产。"
|
||||
"message": "注意:永远不要公开这个私钥。任何拥有你的私钥的人都可以窃取你帐户中的任何资产。"
|
||||
},
|
||||
"privateNetwork": {
|
||||
"message": "私有网络"
|
||||
@ -614,7 +614,7 @@
|
||||
"message": "显示二维码"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "之后你还可以通过帐户选项菜单中的“添加代币”来添加此代币。"
|
||||
"message": "之后你还可以通过帐户选项菜单中的“添加代币”来添加此代币。"
|
||||
},
|
||||
"readMore": {
|
||||
"message": "了解更多。"
|
||||
@ -766,7 +766,7 @@
|
||||
"message": "设置"
|
||||
},
|
||||
"info": {
|
||||
"message": "信息"
|
||||
"message": "信息"
|
||||
},
|
||||
"shapeshiftBuy": {
|
||||
"message": "使用 Shapeshift 购买"
|
||||
@ -888,7 +888,7 @@
|
||||
"message": "欢迎使用新版界面 (Beta)"
|
||||
},
|
||||
"uiWelcomeMessage": {
|
||||
"message": "你现在正在使用新的 MetaMask 界面。 尝试发送代币等新功能,有任何问题请告知我们。"
|
||||
"message": "你现在正在使用新的 MetaMask 界面。 尝试发送代币等新功能,有任何问题请告知我们。"
|
||||
},
|
||||
"unapproved": {
|
||||
"message": "未批准"
|
||||
|
@ -372,7 +372,7 @@
|
||||
"message": "導入帳戶"
|
||||
},
|
||||
"importAccountMsg": {
|
||||
"message":" 匯入的帳戶與您原有 MetaMask 帳戶的助憶詞並無關聯. 請查看與導入帳戶相關的資料 "
|
||||
"message": " 匯入的帳戶與您原有 MetaMask 帳戶的助憶詞並無關聯. 請查看與導入帳戶相關的資料 "
|
||||
},
|
||||
"importAnAccount": {
|
||||
"message": "導入一個帳戶"
|
||||
|
14
app/images/eth.svg
Normal file
14
app/images/eth.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#38393A;}
|
||||
</style>
|
||||
<title>deposit-eth</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="deposit-eth" transform="translate(0.000000, 14.000000)">
|
||||
<path id="Shape" class="st0" d="M19.9,16L7.5,8.7L19.9,26L32.3,8.7L19.9,16L19.9,16z M20.1-14L7.7,6.4l12.4,7.3l12.4-7.2L20.1-14z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 670 B |
@ -1,5 +1,9 @@
|
||||
<html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>MetaMask Loading</title>
|
||||
<style>
|
||||
#div-logo {
|
||||
@ -31,5 +35,11 @@
|
||||
<div id="div-logo">
|
||||
<img id="logo" src="./images/loginglogo.svg">
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
// redirect to 404 after one minute
|
||||
setTimeout(() => {
|
||||
location.href = './404.html'
|
||||
}, 60000)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "__MSG_appName__",
|
||||
"short_name": "__MSG_appName__",
|
||||
"version": "4.12.0",
|
||||
"version": "4.16.0",
|
||||
"manifest_version": 2,
|
||||
"author": "https://metamask.io",
|
||||
"description": "__MSG_appDescription__",
|
||||
|
@ -2,6 +2,9 @@
|
||||
* @file The entry point for the web extension singleton process.
|
||||
*/
|
||||
|
||||
// this needs to run before anything else
|
||||
require('./lib/setupFetchDebugging')()
|
||||
|
||||
const urlUtil = require('url')
|
||||
const endOfStream = require('end-of-stream')
|
||||
const pump = require('pump')
|
||||
@ -20,13 +23,13 @@ const createStreamSink = require('./lib/createStreamSink')
|
||||
const NotificationManager = require('./lib/notification-manager.js')
|
||||
const MetamaskController = require('./metamask-controller')
|
||||
const rawFirstTimeState = require('./first-time-state')
|
||||
const setupRaven = require('./lib/setupRaven')
|
||||
const setupSentry = require('./lib/setupSentry')
|
||||
const reportFailedTxToSentry = require('./lib/reportFailedTxToSentry')
|
||||
const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics')
|
||||
const EdgeEncryptor = require('./edge-encryptor')
|
||||
const getFirstPreferredLangCode = require('./lib/get-first-preferred-lang-code')
|
||||
const getObjStructure = require('./lib/getObjStructure')
|
||||
const ipfsContent = require('./lib/ipfsContent.js')
|
||||
const setupEnsIpfsResolver = require('./lib/ens-ipfs/setup')
|
||||
|
||||
const {
|
||||
ENVIRONMENT_TYPE_POPUP,
|
||||
@ -47,7 +50,7 @@ global.METAMASK_NOTIFIER = notificationManager
|
||||
|
||||
// setup sentry error reporting
|
||||
const release = platform.getVersion()
|
||||
const raven = setupRaven({ release })
|
||||
const sentry = setupSentry({ release })
|
||||
|
||||
// browser check if it is Edge - https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
|
||||
// Internet Explorer 6-11
|
||||
@ -55,7 +58,6 @@ const isIE = !!document.documentMode
|
||||
// Edge 20+
|
||||
const isEdge = !isIE && !!window.StyleMedia
|
||||
|
||||
let ipfsHandle
|
||||
let popupIsOpen = false
|
||||
let notificationIsOpen = false
|
||||
const openMetamaskTabsIDs = {}
|
||||
@ -161,7 +163,6 @@ async function initialize () {
|
||||
const initLangCode = await getFirstPreferredLangCode()
|
||||
await setupController(initState, initLangCode)
|
||||
log.debug('MetaMask initialization complete.')
|
||||
ipfsHandle = ipfsContent(initState.NetworkController.provider)
|
||||
}
|
||||
|
||||
//
|
||||
@ -194,14 +195,14 @@ async function loadStateFromPersistence () {
|
||||
// we were able to recover (though it might be old)
|
||||
versionedData = diskStoreState
|
||||
const vaultStructure = getObjStructure(versionedData)
|
||||
raven.captureMessage('MetaMask - Empty vault found - recovered from diskStore', {
|
||||
sentry.captureMessage('MetaMask - Empty vault found - recovered from diskStore', {
|
||||
// "extra" key is required by Sentry
|
||||
extra: { vaultStructure },
|
||||
})
|
||||
} else {
|
||||
// unable to recover, clear state
|
||||
versionedData = migrator.generateInitialState(firstTimeState)
|
||||
raven.captureMessage('MetaMask - Empty vault found - unable to recover')
|
||||
sentry.captureMessage('MetaMask - Empty vault found - unable to recover')
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,7 +210,7 @@ async function loadStateFromPersistence () {
|
||||
migrator.on('error', (err) => {
|
||||
// get vault structure without secrets
|
||||
const vaultStructure = getObjStructure(versionedData)
|
||||
raven.captureException(err, {
|
||||
sentry.captureException(err, {
|
||||
// "extra" key is required by Sentry
|
||||
extra: { vaultStructure },
|
||||
})
|
||||
@ -266,17 +267,15 @@ function setupController (initState, initLangCode) {
|
||||
})
|
||||
global.metamaskController = controller
|
||||
|
||||
controller.networkController.on('networkDidChange', () => {
|
||||
ipfsHandle && ipfsHandle.remove()
|
||||
ipfsHandle = ipfsContent(controller.networkController.providerStore.getState())
|
||||
})
|
||||
const provider = controller.provider
|
||||
setupEnsIpfsResolver({ provider })
|
||||
|
||||
// report failed transactions to Sentry
|
||||
controller.txController.on(`tx:status-update`, (txId, status) => {
|
||||
if (status !== 'failed') return
|
||||
const txMeta = controller.txController.txStateManager.getTx(txId)
|
||||
try {
|
||||
reportFailedTxToSentry({ raven, txMeta })
|
||||
reportFailedTxToSentry({ sentry, txMeta })
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
@ -135,17 +135,22 @@ function doctypeCheck () {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the current document extension
|
||||
* Returns whether or not the extension (suffix) of the current document is prohibited
|
||||
*
|
||||
* @returns {boolean} {@code true} if the current extension is not prohibited
|
||||
* This checks {@code window.location.pathname} against a set of file extensions
|
||||
* that should not have web3 injected into them. This check is indifferent of query parameters
|
||||
* in the location.
|
||||
*
|
||||
* @returns {boolean} whether or not the extension of the current document is prohibited
|
||||
*/
|
||||
function suffixCheck () {
|
||||
var prohibitedTypes = ['xml', 'pdf']
|
||||
var currentUrl = window.location.href
|
||||
var currentRegex
|
||||
const prohibitedTypes = [
|
||||
/\.xml$/,
|
||||
/\.pdf$/,
|
||||
]
|
||||
const currentUrl = window.location.pathname
|
||||
for (let i = 0; i < prohibitedTypes.length; i++) {
|
||||
currentRegex = new RegExp(`\\.${prohibitedTypes[i]}$`)
|
||||
if (currentRegex.test(currentUrl)) {
|
||||
if (prohibitedTypes[i].test(currentUrl)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -83,8 +83,25 @@ class BlacklistController {
|
||||
*
|
||||
*/
|
||||
async updatePhishingList () {
|
||||
const response = await fetch('https://api.infura.io/v2/blacklist')
|
||||
const phishing = await response.json()
|
||||
// make request
|
||||
let response
|
||||
try {
|
||||
response = await fetch('https://api.infura.io/v2/blacklist')
|
||||
} catch (err) {
|
||||
log.error(new Error(`BlacklistController - failed to fetch blacklist:\n${err.stack}`))
|
||||
return
|
||||
}
|
||||
// parse response
|
||||
let rawResponse
|
||||
let phishing
|
||||
try {
|
||||
const rawResponse = await response.text()
|
||||
phishing = JSON.parse(rawResponse)
|
||||
} catch (err) {
|
||||
log.error(new Error(`BlacklistController - failed to parse blacklist:\n${rawResponse}`))
|
||||
return
|
||||
}
|
||||
// update current blacklist
|
||||
this.store.updateState({ phishing })
|
||||
this._setupPhishingDetector(phishing)
|
||||
return phishing
|
||||
@ -97,9 +114,9 @@ class BlacklistController {
|
||||
*/
|
||||
scheduleUpdates () {
|
||||
if (this._phishingUpdateIntervalRef) return
|
||||
this.updatePhishingList().catch(log.warn)
|
||||
this.updatePhishingList()
|
||||
this._phishingUpdateIntervalRef = setInterval(() => {
|
||||
this.updatePhishingList().catch(log.warn)
|
||||
this.updatePhishingList()
|
||||
}, POLLING_INTERVAL)
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ class CurrencyController {
|
||||
* since midnight of January 1, 1970
|
||||
* @property {number} conversionInterval The id of the interval created by the scheduleConversionInterval method.
|
||||
* Used to clear an existing interval on subsequent calls of that method.
|
||||
* @property {string} nativeCurrency The ticker/symbol of the native chain currency
|
||||
*
|
||||
*/
|
||||
constructor (opts = {}) {
|
||||
@ -28,6 +29,7 @@ class CurrencyController {
|
||||
currentCurrency: 'usd',
|
||||
conversionRate: 0,
|
||||
conversionDate: 'N/A',
|
||||
nativeCurrency: 'ETH',
|
||||
}, opts.initState)
|
||||
this.store = new ObservableStore(initState)
|
||||
}
|
||||
@ -36,6 +38,29 @@ class CurrencyController {
|
||||
// PUBLIC METHODS
|
||||
//
|
||||
|
||||
/**
|
||||
* A getter for the nativeCurrency property
|
||||
*
|
||||
* @returns {string} A 2-4 character shorthand that describes the specific currency
|
||||
*
|
||||
*/
|
||||
getNativeCurrency () {
|
||||
return this.store.getState().nativeCurrency
|
||||
}
|
||||
|
||||
/**
|
||||
* A setter for the nativeCurrency property
|
||||
*
|
||||
* @param {string} nativeCurrency The new currency to set as the nativeCurrency in the store
|
||||
*
|
||||
*/
|
||||
setNativeCurrency (nativeCurrency) {
|
||||
this.store.updateState({
|
||||
nativeCurrency,
|
||||
ticker: nativeCurrency,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* A getter for the currentCurrency property
|
||||
*
|
||||
@ -104,17 +129,60 @@ class CurrencyController {
|
||||
*
|
||||
*/
|
||||
async updateConversionRate () {
|
||||
let currentCurrency
|
||||
let currentCurrency, nativeCurrency
|
||||
try {
|
||||
currentCurrency = this.getCurrentCurrency()
|
||||
const response = await fetch(`https://api.infura.io/v1/ticker/eth${currentCurrency.toLowerCase()}`)
|
||||
const parsedResponse = await response.json()
|
||||
this.setConversionRate(Number(parsedResponse.bid))
|
||||
this.setConversionDate(Number(parsedResponse.timestamp))
|
||||
nativeCurrency = this.getNativeCurrency()
|
||||
// select api
|
||||
let apiUrl
|
||||
if (nativeCurrency === 'ETH') {
|
||||
// ETH
|
||||
apiUrl = `https://api.infura.io/v1/ticker/eth${currentCurrency.toLowerCase()}`
|
||||
} else {
|
||||
// ETC
|
||||
apiUrl = `https://min-api.cryptocompare.com/data/price?fsym=${nativeCurrency.toUpperCase()}&tsyms=${currentCurrency.toUpperCase()}`
|
||||
}
|
||||
// attempt request
|
||||
let response
|
||||
try {
|
||||
response = await fetch(apiUrl)
|
||||
} catch (err) {
|
||||
log.error(new Error(`CurrencyController - Failed to request currency from Infura:\n${err.stack}`))
|
||||
return
|
||||
}
|
||||
// parse response
|
||||
let rawResponse
|
||||
let parsedResponse
|
||||
try {
|
||||
rawResponse = await response.text()
|
||||
parsedResponse = JSON.parse(rawResponse)
|
||||
} catch (err) {
|
||||
log.error(new Error(`CurrencyController - Failed to parse response "${rawResponse}"`))
|
||||
return
|
||||
}
|
||||
// set conversion rate
|
||||
if (nativeCurrency === 'ETH') {
|
||||
// ETH
|
||||
this.setConversionRate(Number(parsedResponse.bid))
|
||||
this.setConversionDate(Number(parsedResponse.timestamp))
|
||||
} else {
|
||||
// ETC
|
||||
if (parsedResponse[currentCurrency.toUpperCase()]) {
|
||||
this.setConversionRate(Number(parsedResponse[currentCurrency.toUpperCase()]))
|
||||
this.setConversionDate(parseInt((new Date()).getTime() / 1000))
|
||||
} else {
|
||||
this.setConversionRate(0)
|
||||
this.setConversionDate('N/A')
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
log.warn(`MetaMask - Failed to query currency conversion:`, currentCurrency, err)
|
||||
// reset current conversion rate
|
||||
log.warn(`MetaMask - Failed to query currency conversion:`, nativeCurrency, currentCurrency, err)
|
||||
this.setConversionRate(0)
|
||||
this.setConversionDate('N/A')
|
||||
// throw error
|
||||
log.error(new Error(`CurrencyController - Failed to query rate for currency "${currentCurrency}":\n${err.stack}`))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ function createMetamaskMiddleware ({
|
||||
processTransaction,
|
||||
processEthSignMessage,
|
||||
processTypedMessage,
|
||||
processTypedMessageV3,
|
||||
processPersonalMessage,
|
||||
getPendingNonce,
|
||||
}) {
|
||||
@ -25,6 +26,7 @@ function createMetamaskMiddleware ({
|
||||
processTransaction,
|
||||
processEthSignMessage,
|
||||
processTypedMessage,
|
||||
processTypedMessageV3,
|
||||
processPersonalMessage,
|
||||
}),
|
||||
createPendingNonceMiddleware({ getPendingNonce }),
|
||||
|
@ -11,6 +11,8 @@ const createInfuraClient = require('./createInfuraClient')
|
||||
const createJsonRpcClient = require('./createJsonRpcClient')
|
||||
const createLocalhostClient = require('./createLocalhostClient')
|
||||
const { createSwappableProxy, createEventEmitterProxy } = require('swappable-obj-proxy')
|
||||
const extend = require('extend')
|
||||
const networks = { networkList: {} }
|
||||
|
||||
const {
|
||||
ROPSTEN,
|
||||
@ -29,6 +31,10 @@ const defaultProviderConfig = {
|
||||
type: testMode ? RINKEBY : MAINNET,
|
||||
}
|
||||
|
||||
const defaultNetworkConfig = {
|
||||
ticker: 'ETH',
|
||||
}
|
||||
|
||||
module.exports = class NetworkController extends EventEmitter {
|
||||
|
||||
constructor (opts = {}) {
|
||||
@ -39,7 +45,8 @@ module.exports = class NetworkController extends EventEmitter {
|
||||
// create stores
|
||||
this.providerStore = new ObservableStore(providerConfig)
|
||||
this.networkStore = new ObservableStore('loading')
|
||||
this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore })
|
||||
this.networkConfig = new ObservableStore(defaultNetworkConfig)
|
||||
this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore, settings: this.networkConfig })
|
||||
this.on('networkDidChange', this.lookupNetwork)
|
||||
// provider and block tracker
|
||||
this._provider = null
|
||||
@ -51,8 +58,8 @@ module.exports = class NetworkController extends EventEmitter {
|
||||
|
||||
initializeProvider (providerParams) {
|
||||
this._baseProviderParams = providerParams
|
||||
const { type, rpcTarget } = this.providerStore.getState()
|
||||
this._configureProvider({ type, rpcTarget })
|
||||
const { type, rpcTarget, chainId, ticker, nickname } = this.providerStore.getState()
|
||||
this._configureProvider({ type, rpcTarget, chainId, ticker, nickname })
|
||||
this.lookupNetwork()
|
||||
}
|
||||
|
||||
@ -72,7 +79,20 @@ module.exports = class NetworkController extends EventEmitter {
|
||||
return this.networkStore.getState()
|
||||
}
|
||||
|
||||
setNetworkState (network) {
|
||||
getNetworkConfig () {
|
||||
return this.networkConfig.getState()
|
||||
}
|
||||
|
||||
setNetworkState (network, type) {
|
||||
if (network === 'loading') {
|
||||
return this.networkStore.putState(network)
|
||||
}
|
||||
|
||||
// type must be defined
|
||||
if (!type) {
|
||||
return
|
||||
}
|
||||
network = networks.networkList[type] && networks.networkList[type].chainId ? networks.networkList[type].chainId : network
|
||||
return this.networkStore.putState(network)
|
||||
}
|
||||
|
||||
@ -85,18 +105,32 @@ module.exports = class NetworkController extends EventEmitter {
|
||||
if (!this._provider) {
|
||||
return log.warn('NetworkController - lookupNetwork aborted due to missing provider')
|
||||
}
|
||||
var { type } = this.providerStore.getState()
|
||||
const ethQuery = new EthQuery(this._provider)
|
||||
ethQuery.sendAsync({ method: 'net_version' }, (err, network) => {
|
||||
if (err) return this.setNetworkState('loading')
|
||||
log.info('web3.getNetwork returned ' + network)
|
||||
this.setNetworkState(network)
|
||||
// first attempt to perform lookup via eth_chainId
|
||||
ethQuery.sendAsync({ method: 'eth_chainId' }, (err, chainIdHex) => {
|
||||
if (err) {
|
||||
// if eth_chainId is not supported, fallback to net_verion
|
||||
ethQuery.sendAsync({ method: 'net_version' }, (err, network) => {
|
||||
if (err) return this.setNetworkState('loading')
|
||||
log.info(`net_version returned ${network}`)
|
||||
this.setNetworkState(network, type)
|
||||
})
|
||||
return
|
||||
}
|
||||
const chainId = Number.parseInt(chainIdHex, 16)
|
||||
log.info(`net_version returned ${chainId}`)
|
||||
this.setNetworkState(chainId, type)
|
||||
})
|
||||
}
|
||||
|
||||
setRpcTarget (rpcTarget) {
|
||||
setRpcTarget (rpcTarget, chainId, ticker = 'ETH', nickname = '') {
|
||||
const providerConfig = {
|
||||
type: 'rpc',
|
||||
rpcTarget,
|
||||
chainId,
|
||||
ticker,
|
||||
nickname,
|
||||
}
|
||||
this.providerConfig = providerConfig
|
||||
}
|
||||
@ -132,7 +166,7 @@ module.exports = class NetworkController extends EventEmitter {
|
||||
}
|
||||
|
||||
_configureProvider (opts) {
|
||||
const { type, rpcTarget } = opts
|
||||
const { type, rpcTarget, chainId, ticker, nickname } = opts
|
||||
// infura type-based endpoints
|
||||
const isInfura = INFURA_PROVIDER_TYPES.includes(type)
|
||||
if (isInfura) {
|
||||
@ -142,7 +176,7 @@ module.exports = class NetworkController extends EventEmitter {
|
||||
this._configureLocalhostProvider()
|
||||
// url-based rpc endpoints
|
||||
} else if (type === 'rpc') {
|
||||
this._configureStandardProvider({ rpcUrl: rpcTarget })
|
||||
this._configureStandardProvider({ rpcUrl: rpcTarget, chainId, ticker, nickname })
|
||||
} else {
|
||||
throw new Error(`NetworkController - _configureProvider - unknown type "${type}"`)
|
||||
}
|
||||
@ -152,6 +186,11 @@ module.exports = class NetworkController extends EventEmitter {
|
||||
log.info('NetworkController - configureInfuraProvider', type)
|
||||
const networkClient = createInfuraClient({ network: type })
|
||||
this._setNetworkClient(networkClient)
|
||||
// setup networkConfig
|
||||
var settings = {
|
||||
ticker: 'ETH',
|
||||
}
|
||||
this.networkConfig.putState(settings)
|
||||
}
|
||||
|
||||
_configureLocalhostProvider () {
|
||||
@ -160,9 +199,22 @@ module.exports = class NetworkController extends EventEmitter {
|
||||
this._setNetworkClient(networkClient)
|
||||
}
|
||||
|
||||
_configureStandardProvider ({ rpcUrl }) {
|
||||
_configureStandardProvider ({ rpcUrl, chainId, ticker, nickname }) {
|
||||
log.info('NetworkController - configureStandardProvider', rpcUrl)
|
||||
const networkClient = createJsonRpcClient({ rpcUrl })
|
||||
// hack to add a 'rpc' network with chainId
|
||||
networks.networkList['rpc'] = {
|
||||
chainId: chainId,
|
||||
rpcUrl,
|
||||
ticker: ticker || 'ETH',
|
||||
nickname,
|
||||
}
|
||||
// setup networkConfig
|
||||
var settings = {
|
||||
network: chainId,
|
||||
}
|
||||
settings = extend(settings, networks.networkList['rpc'])
|
||||
this.networkConfig.putState(settings)
|
||||
this._setNetworkClient(networkClient)
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ class PreferencesController {
|
||||
*/
|
||||
constructor (opts = {}) {
|
||||
const initState = extend({
|
||||
frequentRpcList: [],
|
||||
frequentRpcListDetail: [],
|
||||
currentAccountTab: 'history',
|
||||
accountTokens: {},
|
||||
assetImages: {},
|
||||
@ -38,6 +38,9 @@ class PreferencesController {
|
||||
lostIdentities: {},
|
||||
seedWords: null,
|
||||
forgottenPassword: false,
|
||||
preferences: {
|
||||
useNativeCurrencyAsPrimaryCurrency: true,
|
||||
},
|
||||
}, opts.initState)
|
||||
|
||||
this.diagnostics = opts.diagnostics
|
||||
@ -101,7 +104,7 @@ class PreferencesController {
|
||||
* @param {Function} - end
|
||||
*/
|
||||
async requestWatchAsset (req, res, next, end) {
|
||||
if (req.method === 'metamask_watchAsset') {
|
||||
if (req.method === 'metamask_watchAsset' || req.method === 'wallet_watchAsset') {
|
||||
const { type, options } = req.params
|
||||
switch (type) {
|
||||
case 'ERC20':
|
||||
@ -371,22 +374,6 @@ class PreferencesController {
|
||||
return Promise.resolve(label)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an updated rpc list from this.addToFrequentRpcList() and sets the `frequentRpcList` to this update list.
|
||||
*
|
||||
* @param {string} _url The the new rpc url to add to the updated list
|
||||
* @param {bool} remove Remove selected url
|
||||
* @returns {Promise<void>} Promise resolves with undefined
|
||||
*
|
||||
*/
|
||||
updateFrequentRpcList (_url, remove = false) {
|
||||
return this.addToFrequentRpcList(_url, remove)
|
||||
.then((rpcList) => {
|
||||
this.store.updateState({ frequentRpcList: rpcList })
|
||||
return Promise.resolve()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the `currentAccountTab` property
|
||||
*
|
||||
@ -402,35 +389,53 @@ class PreferencesController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an updated rpcList based on the passed url and the current list.
|
||||
* The returned list will have a max length of 3. If the _url currently exists it the list, it will be moved to the
|
||||
* end of the list. The current list is modified and returned as a promise.
|
||||
* Adds custom RPC url to state.
|
||||
*
|
||||
* @param {string} _url The rpc url to add to the frequentRpcList.
|
||||
* @param {bool} remove Remove selected url
|
||||
* @returns {Promise<array>} The updated frequentRpcList.
|
||||
* @param {string} url The RPC url to add to frequentRpcList.
|
||||
* @param {number} chainId Optional chainId of the selected network.
|
||||
* @param {string} ticker Optional ticker symbol of the selected network.
|
||||
* @param {string} nickname Optional nickname of the selected network.
|
||||
* @returns {Promise<array>} Promise resolving to updated frequentRpcList.
|
||||
*
|
||||
*/
|
||||
addToFrequentRpcList (_url, remove = false) {
|
||||
const rpcList = this.getFrequentRpcList()
|
||||
const index = rpcList.findIndex((element) => { return element === _url })
|
||||
addToFrequentRpcList (url, chainId, ticker = 'ETH', nickname = '') {
|
||||
const rpcList = this.getFrequentRpcListDetail()
|
||||
const index = rpcList.findIndex((element) => { return element.rpcUrl === url })
|
||||
if (index !== -1) {
|
||||
rpcList.splice(index, 1)
|
||||
}
|
||||
if (!remove && _url !== 'http://localhost:8545') {
|
||||
rpcList.push(_url)
|
||||
if (url !== 'http://localhost:8545') {
|
||||
rpcList.push({ rpcUrl: url, chainId, ticker, nickname })
|
||||
}
|
||||
this.store.updateState({ frequentRpcListDetail: rpcList })
|
||||
return Promise.resolve(rpcList)
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the `frequentRpcList` property.
|
||||
* Removes custom RPC url from state.
|
||||
*
|
||||
* @returns {array<string>} An array of one or two rpc urls.
|
||||
* @param {string} url The RPC url to remove from frequentRpcList.
|
||||
* @returns {Promise<array>} Promise resolving to updated frequentRpcList.
|
||||
*
|
||||
*/
|
||||
getFrequentRpcList () {
|
||||
return this.store.getState().frequentRpcList
|
||||
removeFromFrequentRpcList (url) {
|
||||
const rpcList = this.getFrequentRpcListDetail()
|
||||
const index = rpcList.findIndex((element) => { return element.rpcUrl === url })
|
||||
if (index !== -1) {
|
||||
rpcList.splice(index, 1)
|
||||
}
|
||||
this.store.updateState({ frequentRpcListDetail: rpcList })
|
||||
return Promise.resolve(rpcList)
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the `frequentRpcListDetail` property.
|
||||
*
|
||||
* @returns {array<array>} An array of rpc urls.
|
||||
*
|
||||
*/
|
||||
getFrequentRpcListDetail () {
|
||||
return this.store.getState().frequentRpcListDetail
|
||||
}
|
||||
|
||||
/**
|
||||
@ -463,6 +468,33 @@ class PreferencesController {
|
||||
getFeatureFlags () {
|
||||
return this.store.getState().featureFlags
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the `preferences` property, which is an object. These are user-controlled features
|
||||
* found in the settings page.
|
||||
* @param {string} preference The preference to enable or disable.
|
||||
* @param {boolean} value Indicates whether or not the preference should be enabled or disabled.
|
||||
* @returns {Promise<object>} Promises a new object; the updated preferences object.
|
||||
*/
|
||||
setPreference (preference, value) {
|
||||
const currentPreferences = this.getPreferences()
|
||||
const updatedPreferences = {
|
||||
...currentPreferences,
|
||||
[preference]: value,
|
||||
}
|
||||
|
||||
this.store.updateState({ preferences: updatedPreferences })
|
||||
return Promise.resolve(updatedPreferences)
|
||||
}
|
||||
|
||||
/**
|
||||
* A getter for the `preferences` property
|
||||
* @returns {object} A key-boolean map of user-selected preferences.
|
||||
*/
|
||||
getPreferences () {
|
||||
return this.store.getState().preferences
|
||||
}
|
||||
|
||||
//
|
||||
// PRIVATE METHODS
|
||||
//
|
||||
|
@ -166,6 +166,10 @@ class TransactionController extends EventEmitter {
|
||||
async addUnapprovedTransaction (txParams) {
|
||||
// validate
|
||||
const normalizedTxParams = txUtils.normalizeTxParams(txParams)
|
||||
// Assert the from address is the selected address
|
||||
if (normalizedTxParams.from !== this.getSelectedAddress()) {
|
||||
throw new Error(`Transaction from address isn't valid for this account`)
|
||||
}
|
||||
txUtils.validateTxParams(normalizedTxParams)
|
||||
// construct txMeta
|
||||
let txMeta = this.txStateManager.generateTxMeta({
|
||||
@ -362,7 +366,40 @@ class TransactionController extends EventEmitter {
|
||||
this.txStateManager.setTxStatusSubmitted(txId)
|
||||
}
|
||||
|
||||
confirmTransaction (txId) {
|
||||
/**
|
||||
* Sets the status of the transaction to confirmed and sets the status of nonce duplicates as
|
||||
* dropped if the txParams have data it will fetch the txReceipt
|
||||
* @param {number} txId - The tx's ID
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async confirmTransaction (txId) {
|
||||
// get the txReceipt before marking the transaction confirmed
|
||||
// to ensure the receipt is gotten before the ui revives the tx
|
||||
const txMeta = this.txStateManager.getTx(txId)
|
||||
|
||||
if (!txMeta) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const txReceipt = await this.query.getTransactionReceipt(txMeta.hash)
|
||||
|
||||
// It seems that sometimes the numerical values being returned from
|
||||
// this.query.getTransactionReceipt are BN instances and not strings.
|
||||
const gasUsed = typeof txReceipt.gasUsed !== 'string'
|
||||
? txReceipt.gasUsed.toString(16)
|
||||
: txReceipt.gasUsed
|
||||
|
||||
txMeta.txReceipt = {
|
||||
...txReceipt,
|
||||
gasUsed,
|
||||
}
|
||||
|
||||
this.txStateManager.updateTx(txMeta, 'transactions#confirmTransaction - add txReceipt')
|
||||
} catch (err) {
|
||||
log.error(err)
|
||||
}
|
||||
|
||||
this.txStateManager.setTxStatusConfirmed(txId)
|
||||
this._markNonceDuplicatesDropped(txId)
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ const {
|
||||
const { addHexPrefix } = require('ethereumjs-util')
|
||||
const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send.
|
||||
|
||||
import { TRANSACTION_NO_CONTRACT_ERROR_KEY } from '../../../../ui/app/constants/error-keys'
|
||||
|
||||
/**
|
||||
tx-gas-utils are gas utility methods for Transaction manager
|
||||
its passed ethquery
|
||||
@ -32,6 +34,7 @@ class TxGasUtil {
|
||||
} catch (err) {
|
||||
txMeta.simulationFails = {
|
||||
reason: err.message,
|
||||
errorKey: err.errorKey,
|
||||
}
|
||||
return txMeta
|
||||
}
|
||||
@ -56,24 +59,38 @@ class TxGasUtil {
|
||||
return txParams.gas
|
||||
}
|
||||
|
||||
// if recipient has no code, gas is 21k max:
|
||||
const recipient = txParams.to
|
||||
const hasRecipient = Boolean(recipient)
|
||||
let code
|
||||
if (recipient) 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
|
||||
// see if we can set the gas based on the recipient
|
||||
if (hasRecipient) {
|
||||
const code = await this.query.getCode(recipient)
|
||||
// For an address with no code, geth will return '0x', and ganache-core v2.2.1 will return '0x0'
|
||||
const codeIsEmpty = !code || code === '0x' || code === '0x0'
|
||||
|
||||
if (codeIsEmpty) {
|
||||
// if there's data in the params, but there's no contract code, it's not a valid transaction
|
||||
if (txParams.data) {
|
||||
const err = new Error('TxGasUtil - Trying to call a function on a non-contract address')
|
||||
// set error key so ui can display localized error message
|
||||
err.errorKey = TRANSACTION_NO_CONTRACT_ERROR_KEY
|
||||
throw err
|
||||
}
|
||||
|
||||
// This is a standard ether simple send, gas requirement is exactly 21k
|
||||
txParams.gas = SIMPLE_GAS_COST
|
||||
// prevents buffer addition
|
||||
txMeta.simpleSend = true
|
||||
return SIMPLE_GAS_COST
|
||||
}
|
||||
}
|
||||
|
||||
// if not, fall back to block gasLimit
|
||||
// fallback to block gasLimit
|
||||
const blockGasLimitBN = hexToBn(blockGasLimitHex)
|
||||
const saferGasLimitBN = BnMultiplyByFraction(blockGasLimitBN, 19, 20)
|
||||
txParams.gas = bnToHex(saferGasLimitBN)
|
||||
|
||||
// run tx
|
||||
// estimate tx gas requirements
|
||||
return await this.query.estimateGas(txParams)
|
||||
}
|
||||
|
||||
|
@ -400,6 +400,11 @@ class TransactionStateManager extends EventEmitter {
|
||||
*/
|
||||
_setTxStatus (txId, status) {
|
||||
const txMeta = this.getTx(txId)
|
||||
|
||||
if (!txMeta) {
|
||||
return
|
||||
}
|
||||
|
||||
txMeta.status = status
|
||||
setTimeout(() => {
|
||||
try {
|
||||
|
@ -27,6 +27,8 @@ var metamaskStream = new LocalMessageDuplexStream({
|
||||
|
||||
// compose the inpage provider
|
||||
var inpageProvider = new MetamaskInpageProvider(metamaskStream)
|
||||
// set a high max listener count to avoid unnecesary warnings
|
||||
inpageProvider.setMaxListeners(100)
|
||||
|
||||
// Augment the provider with its enable method
|
||||
inpageProvider.enable = function (options = {}) {
|
||||
|
54
app/scripts/lib/ens-ipfs/resolver.js
Normal file
54
app/scripts/lib/ens-ipfs/resolver.js
Normal file
@ -0,0 +1,54 @@
|
||||
const namehash = require('eth-ens-namehash')
|
||||
const multihash = require('multihashes')
|
||||
const Eth = require('ethjs-query')
|
||||
const EthContract = require('ethjs-contract')
|
||||
const registrarAbi = require('./contracts/registrar')
|
||||
const resolverAbi = require('./contracts/resolver')
|
||||
|
||||
module.exports = resolveEnsToIpfsContentId
|
||||
|
||||
|
||||
async function resolveEnsToIpfsContentId ({ provider, name }) {
|
||||
const eth = new Eth(provider)
|
||||
const hash = namehash.hash(name)
|
||||
const contract = new EthContract(eth)
|
||||
// lookup registrar
|
||||
const chainId = Number.parseInt(await eth.net_version(), 10)
|
||||
const registrarAddress = getRegistrarForChainId(chainId)
|
||||
if (!registrarAddress) {
|
||||
throw new Error(`EnsIpfsResolver - no known ens-ipfs registrar for chainId "${chainId}"`)
|
||||
}
|
||||
const Registrar = contract(registrarAbi).at(registrarAddress)
|
||||
// lookup resolver
|
||||
const resolverLookupResult = await Registrar.resolver(hash)
|
||||
const resolverAddress = resolverLookupResult[0]
|
||||
if (hexValueIsEmpty(resolverAddress)) {
|
||||
throw new Error(`EnsIpfsResolver - no resolver found for name "${name}"`)
|
||||
}
|
||||
const Resolver = contract(resolverAbi).at(resolverAddress)
|
||||
// lookup content id
|
||||
const contentLookupResult = await Resolver.content(hash)
|
||||
const contentHash = contentLookupResult[0]
|
||||
if (hexValueIsEmpty(contentHash)) {
|
||||
throw new Error(`EnsIpfsResolver - no content ID found for name "${name}"`)
|
||||
}
|
||||
const nonPrefixedHex = contentHash.slice(2)
|
||||
const buffer = multihash.fromHexString(nonPrefixedHex)
|
||||
const contentId = multihash.toB58String(multihash.encode(buffer, 'sha2-256'))
|
||||
return contentId
|
||||
}
|
||||
|
||||
function hexValueIsEmpty(value) {
|
||||
return [undefined, null, '0x', '0x0', '0x0000000000000000000000000000000000000000000000000000000000000000'].includes(value)
|
||||
}
|
||||
|
||||
function getRegistrarForChainId (chainId) {
|
||||
switch (chainId) {
|
||||
// mainnet
|
||||
case 1:
|
||||
return '0x314159265dd8dbb310642f98f50c066173c1259b'
|
||||
// ropsten
|
||||
case 3:
|
||||
return '0x112234455c3a32fd11230c42e7bccd4a84e02010'
|
||||
}
|
||||
}
|
63
app/scripts/lib/ens-ipfs/setup.js
Normal file
63
app/scripts/lib/ens-ipfs/setup.js
Normal file
@ -0,0 +1,63 @@
|
||||
const urlUtil = require('url')
|
||||
const extension = require('extensionizer')
|
||||
const resolveEnsToIpfsContentId = require('./resolver.js')
|
||||
|
||||
const supportedTopLevelDomains = ['eth']
|
||||
|
||||
module.exports = setupEnsIpfsResolver
|
||||
|
||||
function setupEnsIpfsResolver({ provider }) {
|
||||
|
||||
// install listener
|
||||
const urlPatterns = supportedTopLevelDomains.map(tld => `*://*.${tld}/*`)
|
||||
extension.webRequest.onErrorOccurred.addListener(webRequestDidFail, { urls: urlPatterns })
|
||||
|
||||
// return api object
|
||||
return {
|
||||
// uninstall listener
|
||||
remove () {
|
||||
extension.webRequest.onErrorOccurred.removeListener(webRequestDidFail)
|
||||
},
|
||||
}
|
||||
|
||||
async function webRequestDidFail (details) {
|
||||
const { tabId, url } = details
|
||||
// ignore requests that are not associated with tabs
|
||||
if (tabId === -1) return
|
||||
// parse ens name
|
||||
const urlData = urlUtil.parse(url)
|
||||
const { hostname: name, path, search } = urlData
|
||||
const domainParts = name.split('.')
|
||||
const topLevelDomain = domainParts[domainParts.length - 1]
|
||||
// if unsupported TLD, abort
|
||||
if (!supportedTopLevelDomains.includes(topLevelDomain)) return
|
||||
// otherwise attempt resolve
|
||||
attemptResolve({ tabId, name, path, search })
|
||||
}
|
||||
|
||||
async function attemptResolve({ tabId, name, path, search }) {
|
||||
extension.tabs.update(tabId, { url: `loading.html` })
|
||||
try {
|
||||
const ipfsContentId = await resolveEnsToIpfsContentId({ provider, name })
|
||||
let url = `https://gateway.ipfs.io/ipfs/${ipfsContentId}${path}${search || ''}`
|
||||
try {
|
||||
// check if ipfs gateway has result
|
||||
const response = await fetch(url, { method: 'HEAD' })
|
||||
// if failure, redirect to 404 page
|
||||
if (response.status !== 200) {
|
||||
extension.tabs.update(tabId, { url: '404.html' })
|
||||
return
|
||||
}
|
||||
// otherwise redirect to the correct page
|
||||
extension.tabs.update(tabId, { url })
|
||||
} catch (err) {
|
||||
console.warn(err)
|
||||
// if HEAD fetch failed, redirect so user can see relevant error page
|
||||
extension.tabs.update(tabId, { url })
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(err)
|
||||
extension.tabs.update(tabId, { url: `error.html?name=${name}` })
|
||||
}
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
const extension = require('extensionizer')
|
||||
const resolver = require('./resolver.js')
|
||||
|
||||
module.exports = function (provider) {
|
||||
function ipfsContent (details) {
|
||||
const name = details.url.substring(7, details.url.length - 1)
|
||||
let clearTime = null
|
||||
if (/^.+\.eth$/.test(name) === false) return
|
||||
|
||||
extension.tabs.query({active: true}, tab => {
|
||||
extension.tabs.update(tab.id, { url: 'loading.html' })
|
||||
|
||||
clearTime = setTimeout(() => {
|
||||
return extension.tabs.update(tab.id, { url: '404.html' })
|
||||
}, 60000)
|
||||
|
||||
resolver.resolve(name, provider).then(ipfsHash => {
|
||||
clearTimeout(clearTime)
|
||||
let url = 'https://ipfs.infura.io/ipfs/' + ipfsHash
|
||||
return fetch(url, { method: 'HEAD' }).then(response => response.status).then(statusCode => {
|
||||
if (statusCode !== 200) return extension.tabs.update(tab.id, { url: '404.html' })
|
||||
extension.tabs.update(tab.id, { url: url })
|
||||
})
|
||||
.catch(err => {
|
||||
url = 'https://ipfs.infura.io/ipfs/' + ipfsHash
|
||||
extension.tabs.update(tab.id, {url: url})
|
||||
return err
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
clearTimeout(clearTime)
|
||||
const url = err === 'unsupport' ? 'unsupport' : 'error'
|
||||
extension.tabs.update(tab.id, {url: `${url}.html?name=${name}`})
|
||||
})
|
||||
})
|
||||
return { cancel: true }
|
||||
}
|
||||
|
||||
extension.webRequest.onErrorOccurred.addListener(ipfsContent, {urls: ['*://*.eth/'], types: ['main_frame']})
|
||||
|
||||
return {
|
||||
remove () {
|
||||
extension.webRequest.onErrorOccurred.removeListener(ipfsContent)
|
||||
},
|
||||
}
|
||||
}
|
@ -7,10 +7,10 @@ module.exports = reportFailedTxToSentry
|
||||
// for sending to sentry
|
||||
//
|
||||
|
||||
function reportFailedTxToSentry ({ raven, txMeta }) {
|
||||
function reportFailedTxToSentry ({ sentry, txMeta }) {
|
||||
const errorMessage = 'Transaction Failed: ' + extractEthjsErrorMessage(txMeta.err.message)
|
||||
raven.captureMessage(errorMessage, {
|
||||
sentry.captureMessage(errorMessage, {
|
||||
// "extra" key is required by Sentry
|
||||
extra: txMeta,
|
||||
extra: { txMeta },
|
||||
})
|
||||
}
|
||||
|
@ -1,71 +0,0 @@
|
||||
const namehash = require('eth-ens-namehash')
|
||||
const multihash = require('multihashes')
|
||||
const HttpProvider = require('ethjs-provider-http')
|
||||
const Eth = require('ethjs-query')
|
||||
const EthContract = require('ethjs-contract')
|
||||
const registrarAbi = require('./contracts/registrar')
|
||||
const resolverAbi = require('./contracts/resolver')
|
||||
|
||||
function ens (name, provider) {
|
||||
const eth = new Eth(new HttpProvider(getProvider(provider.type)))
|
||||
const hash = namehash.hash(name)
|
||||
const contract = new EthContract(eth)
|
||||
const Registrar = contract(registrarAbi).at(getRegistrar(provider.type))
|
||||
return new Promise((resolve, reject) => {
|
||||
if (provider.type === 'mainnet' || provider.type === 'ropsten') {
|
||||
Registrar.resolver(hash).then((address) => {
|
||||
if (address === '0x0000000000000000000000000000000000000000') {
|
||||
reject(null)
|
||||
} else {
|
||||
const Resolver = contract(resolverAbi).at(address['0'])
|
||||
return Resolver.content(hash)
|
||||
}
|
||||
}).then((contentHash) => {
|
||||
if (contentHash['0'] === '0x0000000000000000000000000000000000000000000000000000000000000000') reject(null)
|
||||
if (contentHash.ret !== '0x') {
|
||||
const hex = contentHash['0'].substring(2)
|
||||
const buf = multihash.fromHexString(hex)
|
||||
resolve(multihash.toB58String(multihash.encode(buf, 'sha2-256')))
|
||||
} else {
|
||||
reject(null)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
return reject('unsupport')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getProvider (type) {
|
||||
switch (type) {
|
||||
case 'mainnet':
|
||||
return 'https://mainnet.infura.io/'
|
||||
case 'ropsten':
|
||||
return 'https://ropsten.infura.io/'
|
||||
default:
|
||||
return 'http://localhost:8545/'
|
||||
}
|
||||
}
|
||||
|
||||
function getRegistrar (type) {
|
||||
switch (type) {
|
||||
case 'mainnet':
|
||||
return '0x314159265dd8dbb310642f98f50c066173c1259b'
|
||||
case 'ropsten':
|
||||
return '0x112234455c3a32fd11230c42e7bccd4a84e02010'
|
||||
default:
|
||||
return '0x0000000000000000000000000000000000000000'
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.resolve = function (name, provider) {
|
||||
const path = name.split('.')
|
||||
const topLevelDomain = path[path.length - 1]
|
||||
if (topLevelDomain === 'eth' || topLevelDomain === 'test') {
|
||||
return ens(name, provider)
|
||||
} else {
|
||||
return new Promise((resolve, reject) => {
|
||||
reject(null)
|
||||
})
|
||||
}
|
||||
}
|
36
app/scripts/lib/setupFetchDebugging.js
Normal file
36
app/scripts/lib/setupFetchDebugging.js
Normal file
@ -0,0 +1,36 @@
|
||||
module.exports = setupFetchDebugging
|
||||
|
||||
//
|
||||
// This is a utility to help resolve cases where `window.fetch` throws a
|
||||
// `TypeError: Failed to Fetch` without any stack or context for the request
|
||||
// https://github.com/getsentry/sentry-javascript/pull/1293
|
||||
//
|
||||
|
||||
function setupFetchDebugging() {
|
||||
if (!global.fetch) return
|
||||
const originalFetch = global.fetch
|
||||
|
||||
global.fetch = wrappedFetch
|
||||
|
||||
async function wrappedFetch(...args) {
|
||||
const initialStack = getCurrentStack()
|
||||
try {
|
||||
return await originalFetch.call(window, ...args)
|
||||
} catch (err) {
|
||||
if (!err.stack) {
|
||||
console.warn('FetchDebugger - fetch encountered an Error without a stack', err)
|
||||
console.warn('FetchDebugger - overriding stack to point of original call')
|
||||
err.stack = initialStack
|
||||
}
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentStack() {
|
||||
try {
|
||||
throw new Error('Fake error for generating stack trace')
|
||||
} catch (err) {
|
||||
return err.stack
|
||||
}
|
||||
}
|
@ -1,58 +1,55 @@
|
||||
const Raven = require('raven-js')
|
||||
const Sentry = require('@sentry/browser')
|
||||
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
|
||||
const extractEthjsErrorMessage = require('./extractEthjsErrorMessage')
|
||||
const PROD = 'https://3567c198f8a8412082d32655da2961d0@sentry.io/273505'
|
||||
const DEV = 'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496'
|
||||
const SENTRY_DSN_PROD = 'https://3567c198f8a8412082d32655da2961d0@sentry.io/273505'
|
||||
const SENTRY_DSN_DEV = 'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496'
|
||||
|
||||
module.exports = setupRaven
|
||||
module.exports = setupSentry
|
||||
|
||||
// Setup raven / sentry remote error reporting
|
||||
function setupRaven (opts) {
|
||||
const { release } = opts
|
||||
let ravenTarget
|
||||
// Setup sentry remote error reporting
|
||||
function setupSentry (opts) {
|
||||
const { release, getState } = opts
|
||||
let sentryTarget
|
||||
// detect brave
|
||||
const isBrave = Boolean(window.chrome.ipcRenderer)
|
||||
|
||||
if (METAMASK_DEBUG) {
|
||||
console.log('Setting up Sentry Remote Error Reporting: DEV')
|
||||
ravenTarget = DEV
|
||||
console.log('Setting up Sentry Remote Error Reporting: SENTRY_DSN_DEV')
|
||||
sentryTarget = SENTRY_DSN_DEV
|
||||
} else {
|
||||
console.log('Setting up Sentry Remote Error Reporting: PROD')
|
||||
ravenTarget = PROD
|
||||
console.log('Setting up Sentry Remote Error Reporting: SENTRY_DSN_PROD')
|
||||
sentryTarget = SENTRY_DSN_PROD
|
||||
}
|
||||
|
||||
const client = Raven.config(ravenTarget, {
|
||||
Sentry.init({
|
||||
dsn: sentryTarget,
|
||||
debug: METAMASK_DEBUG,
|
||||
release,
|
||||
transport: function (opts) {
|
||||
opts.data.extra.isBrave = isBrave
|
||||
const report = opts.data
|
||||
beforeSend: (report) => rewriteReport(report),
|
||||
})
|
||||
|
||||
try {
|
||||
// handle error-like non-error exceptions
|
||||
rewriteErrorLikeExceptions(report)
|
||||
// simplify certain complex error messages (e.g. Ethjs)
|
||||
simplifyErrorMessages(report)
|
||||
// modify report urls
|
||||
rewriteReportUrls(report)
|
||||
} catch (err) {
|
||||
console.warn(err)
|
||||
Sentry.configureScope(scope => {
|
||||
scope.setExtra('isBrave', isBrave)
|
||||
})
|
||||
|
||||
function rewriteReport(report) {
|
||||
try {
|
||||
// simplify certain complex error messages (e.g. Ethjs)
|
||||
simplifyErrorMessages(report)
|
||||
// modify report urls
|
||||
rewriteReportUrls(report)
|
||||
// append app state
|
||||
if (getState) {
|
||||
const appState = getState()
|
||||
report.extra.appState = appState
|
||||
}
|
||||
// make request normally
|
||||
client._makeRequest(opts)
|
||||
},
|
||||
})
|
||||
client.install()
|
||||
} catch (err) {
|
||||
console.warn(err)
|
||||
}
|
||||
return report
|
||||
}
|
||||
|
||||
return Raven
|
||||
}
|
||||
|
||||
function rewriteErrorLikeExceptions (report) {
|
||||
// handle errors that lost their error-ness in serialization (e.g. dnode)
|
||||
rewriteErrorMessages(report, (errorMessage) => {
|
||||
if (!errorMessage.includes('Non-Error exception captured with keys:')) return errorMessage
|
||||
if (!(report.extra && report.extra.__serialized__ && report.extra.__serialized__.message)) return errorMessage
|
||||
return `Non-Error Exception: ${report.extra.__serialized__.message}`
|
||||
})
|
||||
return Sentry
|
||||
}
|
||||
|
||||
function simplifyErrorMessages (report) {
|
@ -129,6 +129,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
provider: this.provider,
|
||||
blockTracker: this.blockTracker,
|
||||
})
|
||||
|
||||
// start and stop polling for balances based on activeControllerConnections
|
||||
this.on('controllerConnectionChanged', (activeControllerConnections) => {
|
||||
if (activeControllerConnections > 0) {
|
||||
@ -138,6 +139,11 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
}
|
||||
})
|
||||
|
||||
// ensure accountTracker updates balances after network change
|
||||
this.networkController.on('networkDidChange', () => {
|
||||
this.accountTracker._updateAccounts()
|
||||
})
|
||||
|
||||
// key mgmt
|
||||
const additionalKeyrings = [TrezorKeyring, LedgerBridgeKeyring]
|
||||
this.keyringController = new KeyringController({
|
||||
@ -191,6 +197,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
})
|
||||
this.networkController.on('networkDidChange', () => {
|
||||
this.balancesController.updateAllBalances()
|
||||
var currentCurrency = this.currencyController.getCurrentCurrency()
|
||||
this.setCurrentCurrency(currentCurrency, function() {})
|
||||
})
|
||||
this.balancesController.updateAllBalances()
|
||||
|
||||
@ -269,6 +277,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
processTransaction: this.newUnapprovedTransaction.bind(this),
|
||||
// msg signing
|
||||
processEthSignMessage: this.newUnsignedMessage.bind(this),
|
||||
processTypedMessage: this.newUnsignedTypedMessage.bind(this),
|
||||
processTypedMessageV3: this.newUnsignedTypedMessage.bind(this),
|
||||
processPersonalMessage: this.newUnsignedPersonalMessage.bind(this),
|
||||
getPendingNonce: this.getPendingNonce.bind(this),
|
||||
}
|
||||
@ -387,6 +397,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
setCurrentAccountTab: nodeify(preferencesController.setCurrentAccountTab, preferencesController),
|
||||
setAccountLabel: nodeify(preferencesController.setAccountLabel, preferencesController),
|
||||
setFeatureFlag: nodeify(preferencesController.setFeatureFlag, preferencesController),
|
||||
setPreference: nodeify(preferencesController.setPreference, preferencesController),
|
||||
|
||||
// BlacklistController
|
||||
whitelistPhishingDomain: this.whitelistPhishingDomain.bind(this),
|
||||
@ -971,8 +982,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
* @param {Object} msgParams - The params passed to eth_signTypedData.
|
||||
* @param {Function} cb - The callback function, called with the signature.
|
||||
*/
|
||||
newUnsignedTypedMessage (msgParams, req) {
|
||||
const promise = this.typedMessageManager.addUnapprovedMessageAsync(msgParams, req)
|
||||
newUnsignedTypedMessage (msgParams, req, version) {
|
||||
const promise = this.typedMessageManager.addUnapprovedMessageAsync(msgParams, req, version)
|
||||
this.sendUpdate()
|
||||
this.opts.showUnconfirmedMessage()
|
||||
return promise
|
||||
@ -1266,10 +1277,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
engine.push(subscriptionManager.middleware)
|
||||
// watch asset
|
||||
engine.push(this.preferencesController.requestWatchAsset.bind(this.preferencesController))
|
||||
// sign typed data middleware
|
||||
engine.push(this.createTypedDataMiddleware('eth_signTypedData', 'V1').bind(this))
|
||||
engine.push(this.createTypedDataMiddleware('eth_signTypedData_v1', 'V1').bind(this))
|
||||
engine.push(this.createTypedDataMiddleware('eth_signTypedData_v3', 'V3', true).bind(this))
|
||||
// forward to metamask primary provider
|
||||
engine.push(createProviderMiddleware({ provider }))
|
||||
|
||||
@ -1405,10 +1412,13 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
* @param {Function} cb - A callback function returning currency info.
|
||||
*/
|
||||
setCurrentCurrency (currencyCode, cb) {
|
||||
const { ticker } = this.networkController.getNetworkConfig()
|
||||
try {
|
||||
this.currencyController.setNativeCurrency(ticker)
|
||||
this.currencyController.setCurrentCurrency(currencyCode)
|
||||
this.currencyController.updateConversionRate()
|
||||
const data = {
|
||||
nativeCurrency: ticker || 'ETH',
|
||||
conversionRate: this.currencyController.getConversionRate(),
|
||||
currentCurrency: this.currencyController.getCurrentCurrency(),
|
||||
conversionDate: this.currencyController.getConversionDate(),
|
||||
@ -1447,11 +1457,14 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
/**
|
||||
* A method for selecting a custom URL for an ethereum RPC provider.
|
||||
* @param {string} rpcTarget - A URL for a valid Ethereum RPC API.
|
||||
* @param {number} chainId - The chainId of the selected network.
|
||||
* @param {string} ticker - The ticker symbol of the selected network.
|
||||
* @param {string} nickname - Optional nickname of the selected network.
|
||||
* @returns {Promise<String>} - The RPC Target URL confirmed.
|
||||
*/
|
||||
async setCustomRpc (rpcTarget) {
|
||||
this.networkController.setRpcTarget(rpcTarget)
|
||||
await this.preferencesController.updateFrequentRpcList(rpcTarget)
|
||||
async setCustomRpc (rpcTarget, chainId, ticker = 'ETH', nickname = '') {
|
||||
this.networkController.setRpcTarget(rpcTarget, chainId, ticker, nickname)
|
||||
await this.preferencesController.addToFrequentRpcList(rpcTarget, chainId, ticker, nickname)
|
||||
return rpcTarget
|
||||
}
|
||||
|
||||
@ -1460,7 +1473,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
* @param {string} rpcTarget - A RPC URL to delete.
|
||||
*/
|
||||
async delCustomRpc (rpcTarget) {
|
||||
await this.preferencesController.updateFrequentRpcList(rpcTarget, true)
|
||||
await this.preferencesController.removeFromFrequentRpcList(rpcTarget)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1535,27 +1548,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
* @param {Function} - next
|
||||
* @param {Function} - end
|
||||
*/
|
||||
createTypedDataMiddleware (methodName, version, reverse) {
|
||||
return async (req, res, next, end) => {
|
||||
const { method, params } = req
|
||||
if (method === methodName) {
|
||||
const promise = this.typedMessageManager.addUnapprovedMessageAsync({
|
||||
data: reverse ? params[1] : params[0],
|
||||
from: reverse ? params[0] : params[1],
|
||||
}, req, version)
|
||||
this.sendUpdate()
|
||||
this.opts.showUnconfirmedMessage()
|
||||
try {
|
||||
res.result = await promise
|
||||
end()
|
||||
} catch (error) {
|
||||
end(error)
|
||||
}
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a domain to the {@link BlacklistController} whitelist
|
||||
|
@ -9,7 +9,7 @@ const extension = require('extensionizer')
|
||||
const ExtensionPlatform = require('./platforms/extension')
|
||||
const NotificationManager = require('./lib/notification-manager')
|
||||
const notificationManager = new NotificationManager()
|
||||
const setupRaven = require('./lib/setupRaven')
|
||||
const setupSentry = require('./lib/setupSentry')
|
||||
const log = require('loglevel')
|
||||
|
||||
start().catch(log.error)
|
||||
@ -21,7 +21,17 @@ async function start () {
|
||||
|
||||
// setup sentry error reporting
|
||||
const release = global.platform.getVersion()
|
||||
setupRaven({ release })
|
||||
setupSentry({ release, getState })
|
||||
// provide app state to append to error logs
|
||||
function getState() {
|
||||
// get app state
|
||||
const state = window.getCleanAppState()
|
||||
// remove unnecessary data
|
||||
delete state.localeMessages
|
||||
delete state.metamask.recentBlocks
|
||||
// return state to be added to request
|
||||
return state
|
||||
}
|
||||
|
||||
// inject css
|
||||
// const css = MetaMaskUiCss()
|
||||
|
@ -14,21 +14,27 @@ async function start () {
|
||||
const versionAlreadyExists = await checkIfVersionExists()
|
||||
// abort if versions exists
|
||||
if (versionAlreadyExists) {
|
||||
console.log(`Version "${VERSION}" already exists on Sentry, aborting sourcemap upload.`)
|
||||
return
|
||||
console.log(`Version "${VERSION}" already exists on Sentry, skipping version creation`)
|
||||
} else {
|
||||
// create sentry release
|
||||
console.log(`creating Sentry release for "${VERSION}"...`)
|
||||
await exec(`sentry-cli releases --org 'metamask' --project 'metamask' new ${VERSION}`)
|
||||
console.log(`removing any existing files from Sentry release "${VERSION}"...`)
|
||||
await exec(`sentry-cli releases --org 'metamask' --project 'metamask' files ${VERSION} delete --all`)
|
||||
}
|
||||
|
||||
// create sentry release
|
||||
console.log(`creating Sentry release for "${VERSION}"...`)
|
||||
await exec(`sentry-cli releases --org 'metamask' --project 'metamask' new ${VERSION}`)
|
||||
console.log(`removing any existing files from Sentry release "${VERSION}"...`)
|
||||
await exec(`sentry-cli releases --org 'metamask' --project 'metamask' files ${VERSION} delete --all`)
|
||||
// upload sentry source and sourcemaps
|
||||
console.log(`uploading source files Sentry release "${VERSION}"...`)
|
||||
await exec(`for FILEPATH in ./dist/chrome/*.js; do [ -e $FILEPATH ] || continue; export FILE=\`basename $FILEPATH\` && echo uploading $FILE && sentry-cli releases --org 'metamask' --project 'metamask' files ${VERSION} upload $FILEPATH metamask/$FILE; done;`)
|
||||
console.log(`uploading sourcemaps Sentry release "${VERSION}"...`)
|
||||
await exec(`sentry-cli releases --org 'metamask' --project 'metamask' files ${VERSION} upload-sourcemaps ./dist/sourcemaps/ --url-prefix 'sourcemaps'`)
|
||||
console.log('all done!')
|
||||
// check if version has artifacts or not
|
||||
const versionHasArtifacts = versionAlreadyExists && await checkIfVersionHasArtifacts()
|
||||
if (!versionHasArtifacts) {
|
||||
// upload sentry source and sourcemaps
|
||||
console.log(`uploading source files Sentry release "${VERSION}"...`)
|
||||
await exec(`for FILEPATH in ./dist/chrome/*.js; do [ -e $FILEPATH ] || continue; export FILE=\`basename $FILEPATH\` && echo uploading $FILE && sentry-cli releases --org 'metamask' --project 'metamask' files ${VERSION} upload $FILEPATH metamask/$FILE; done;`)
|
||||
console.log(`uploading sourcemaps Sentry release "${VERSION}"...`)
|
||||
await exec(`sentry-cli releases --org 'metamask' --project 'metamask' files ${VERSION} upload-sourcemaps ./dist/sourcemaps/ --url-prefix 'sourcemaps'`)
|
||||
console.log('all done!')
|
||||
} else {
|
||||
console.log(`Version "${VERSION}" already has artifacts on Sentry, skipping sourcemap upload`)
|
||||
}
|
||||
}
|
||||
|
||||
async function checkIfAuthWorks () {
|
||||
@ -45,6 +51,12 @@ async function checkIfVersionExists () {
|
||||
return versionAlreadyExists
|
||||
}
|
||||
|
||||
async function checkIfVersionHasArtifacts () {
|
||||
const artifacts = await exec(`sentry-cli releases --org 'metamask' --project 'metamask' files ${VERSION} list`)
|
||||
// When there's no artifacts, we get a response from the shell like this ['', '']
|
||||
return artifacts[0] && artifacts[0].length > 0
|
||||
}
|
||||
|
||||
async function doesNotFail (asyncFn) {
|
||||
try {
|
||||
await asyncFn()
|
||||
|
@ -107,7 +107,10 @@
|
||||
"maxModeOn": false,
|
||||
"editingTransactionId": null
|
||||
},
|
||||
"currentLocale": "en"
|
||||
"currentLocale": "en",
|
||||
"preferences": {
|
||||
"useNativeCurrencyAsPrimaryCurrency": true
|
||||
}
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
|
@ -150,7 +150,10 @@
|
||||
"maxModeOn": false,
|
||||
"editingTransactionId": null
|
||||
},
|
||||
"currentLocale": "en"
|
||||
"currentLocale": "en",
|
||||
"preferences": {
|
||||
"useNativeCurrencyAsPrimaryCurrency": true
|
||||
}
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
|
@ -108,7 +108,10 @@
|
||||
"maxModeOn": false,
|
||||
"editingTransactionId": null
|
||||
},
|
||||
"currentLocale": "en"
|
||||
"currentLocale": "en",
|
||||
"preferences": {
|
||||
"useNativeCurrencyAsPrimaryCurrency": true
|
||||
}
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
|
@ -37,7 +37,10 @@
|
||||
"shapeShiftTxList": [],
|
||||
"lostAccounts": [],
|
||||
"tokens": [],
|
||||
"currentLocale": "en"
|
||||
"currentLocale": "en",
|
||||
"preferences": {
|
||||
"useNativeCurrencyAsPrimaryCurrency": true
|
||||
}
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
|
@ -109,7 +109,10 @@
|
||||
"maxModeOn": false,
|
||||
"editingTransactionId": null
|
||||
},
|
||||
"currentLocale": "en"
|
||||
"currentLocale": "en",
|
||||
"preferences": {
|
||||
"useNativeCurrencyAsPrimaryCurrency": true
|
||||
}
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
|
@ -102,7 +102,10 @@
|
||||
"shapeShiftTxList": [{"depositAddress":"34vJ3AfmNcLiziA4VFgEVcQTwxVLD1qkke","depositType":"BTC","key":"shapeshift","response":{"status":"no_deposits","address":"34vJ3AfmNcLiziA4VFgEVcQTwxVLD1qkke"},"time":1522377459106}],
|
||||
"lostAccounts": [],
|
||||
"send": {},
|
||||
"currentLocale": "en"
|
||||
"currentLocale": "en",
|
||||
"preferences": {
|
||||
"useNativeCurrencyAsPrimaryCurrency": true
|
||||
}
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
|
@ -462,7 +462,9 @@ function generateBundler (opts, performBundle) {
|
||||
bundler.transform(envify({
|
||||
METAMASK_DEBUG: opts.devMode,
|
||||
NODE_ENV: opts.devMode ? 'development' : 'production',
|
||||
}))
|
||||
}), {
|
||||
global: true,
|
||||
})
|
||||
|
||||
if (opts.watch) {
|
||||
bundler = watchify(bundler)
|
||||
|
@ -17,7 +17,7 @@
|
||||
font-family: Roboto;
|
||||
}
|
||||
|
||||
@media screen and (min-height: 576px) {
|
||||
@media screen and (min-height: 601px) {
|
||||
.first-time-flow {
|
||||
height: 100vh;
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ function mapStateToProps (state) {
|
||||
forgottenPassword: state.appState.forgottenPassword,
|
||||
nextUnreadNotice: state.metamask.nextUnreadNotice,
|
||||
lostAccounts: state.metamask.lostAccounts,
|
||||
frequentRpcList: state.metamask.frequentRpcList || [],
|
||||
frequentRpcListDetail: state.metamask.frequentRpcListDetail || [],
|
||||
featureFlags,
|
||||
suggestedTokens: state.metamask.suggestedTokens,
|
||||
|
||||
|
@ -17,7 +17,7 @@ module.exports = class AppBar extends Component {
|
||||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
frequentRpcList: PropTypes.array.isRequired,
|
||||
frequentRpcListDetail: PropTypes.array.isRequired,
|
||||
isMascara: PropTypes.bool.isRequired,
|
||||
isOnboarding: PropTypes.bool.isRequired,
|
||||
identities: PropTypes.any.isRequired,
|
||||
@ -196,7 +196,7 @@ module.exports = class AppBar extends Component {
|
||||
renderNetworkDropdown () {
|
||||
const {
|
||||
dispatch,
|
||||
frequentRpcList: rpcList,
|
||||
frequentRpcListDetail: rpcList,
|
||||
provider,
|
||||
} = this.props
|
||||
const {
|
||||
@ -321,8 +321,8 @@ module.exports = class AppBar extends Component {
|
||||
])
|
||||
}
|
||||
|
||||
renderCustomOption ({ rpcTarget, type }) {
|
||||
const {dispatch} = this.props
|
||||
renderCustomOption ({ rpcTarget, type, ticker }) {
|
||||
const {dispatch, network} = this.props
|
||||
|
||||
if (type !== 'rpc') {
|
||||
return null
|
||||
@ -340,7 +340,7 @@ module.exports = class AppBar extends Component {
|
||||
default:
|
||||
return h(DropdownMenuItem, {
|
||||
key: rpcTarget,
|
||||
onClick: () => dispatch(actions.setRpcTarget(rpcTarget)),
|
||||
onClick: () => dispatch(actions.setRpcTarget(rpcTarget, network, ticker)),
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
}, [
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
@ -354,7 +354,8 @@ module.exports = class AppBar extends Component {
|
||||
const {dispatch} = this.props
|
||||
const reversedRpcList = rpcList.slice().reverse()
|
||||
|
||||
return reversedRpcList.map((rpc) => {
|
||||
return reversedRpcList.map((entry) => {
|
||||
const rpc = entry.rpcUrl
|
||||
const currentRpcTarget = provider.type === 'rpc' && rpc === provider.rpcTarget
|
||||
|
||||
if ((rpc === LOCALHOST_RPC_URL) || currentRpcTarget) {
|
||||
@ -363,7 +364,7 @@ module.exports = class AppBar extends Component {
|
||||
return h(DropdownMenuItem, {
|
||||
key: `common${rpc}`,
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
onClick: () => dispatch(actions.setRpcTarget(rpc)),
|
||||
onClick: () => dispatch(actions.setRpcTarget(rpc, entry.chainId, entry.ticker)),
|
||||
}, [
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
rpc,
|
||||
|
@ -1,12 +1,18 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const inherits = require('util').inherits
|
||||
const formatBalance = require('../util').formatBalance
|
||||
const generateBalanceObject = require('../util').generateBalanceObject
|
||||
const Tooltip = require('./tooltip.js')
|
||||
const FiatValue = require('./fiat-value.js')
|
||||
|
||||
module.exports = EthBalanceComponent
|
||||
module.exports = connect(mapStateToProps)(EthBalanceComponent)
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
ticker: state.metamask.ticker,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(EthBalanceComponent, Component)
|
||||
function EthBalanceComponent () {
|
||||
@ -16,9 +22,10 @@ function EthBalanceComponent () {
|
||||
EthBalanceComponent.prototype.render = function () {
|
||||
var props = this.props
|
||||
let { value } = props
|
||||
const { ticker } = props
|
||||
var style = props.style
|
||||
var needsParse = this.props.needsParse !== undefined ? this.props.needsParse : true
|
||||
value = value ? formatBalance(value, 6, needsParse) : '...'
|
||||
value = value ? formatBalance(value, 6, needsParse, ticker) : '...'
|
||||
var width = props.width
|
||||
|
||||
return (
|
||||
|
@ -1,12 +1,18 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const inherits = require('util').inherits
|
||||
const formatBalance = require('../util').formatBalance
|
||||
const generateBalanceObject = require('../util').generateBalanceObject
|
||||
const Tooltip = require('./tooltip.js')
|
||||
const FiatValue = require('./fiat-value.js')
|
||||
|
||||
module.exports = EthBalanceComponent
|
||||
module.exports = connect(mapStateToProps)(EthBalanceComponent)
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
ticker: state.metamask.ticker,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(EthBalanceComponent, Component)
|
||||
function EthBalanceComponent () {
|
||||
@ -16,9 +22,9 @@ function EthBalanceComponent () {
|
||||
EthBalanceComponent.prototype.render = function () {
|
||||
var props = this.props
|
||||
let { value } = props
|
||||
const { style, width } = props
|
||||
const { ticker, style, width } = props
|
||||
var needsParse = this.props.needsParse !== undefined ? this.props.needsParse : true
|
||||
value = value ? formatBalance(value, 6, needsParse) : '...'
|
||||
value = value ? formatBalance(value, 6, needsParse, ticker) : '...'
|
||||
|
||||
return (
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
const Component = require('react').Component
|
||||
const connect = require('react-redux').connect
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const actions = require('../../../ui/app/actions')
|
||||
@ -19,7 +20,9 @@ const BNInput = require('./bn-as-decimal-input')
|
||||
const MIN_GAS_PRICE_BN = new BN('0')
|
||||
const MIN_GAS_LIMIT_BN = new BN('21000')
|
||||
|
||||
module.exports = PendingTx
|
||||
module.exports = connect()(PendingTx)
|
||||
|
||||
|
||||
inherits(PendingTx, Component)
|
||||
function PendingTx () {
|
||||
Component.call(this)
|
||||
@ -445,7 +448,8 @@ PendingTx.prototype.onSubmit = function (event) {
|
||||
const txMeta = this.gatherTxMeta()
|
||||
const valid = this.checkValidity()
|
||||
this.setState({ valid, submitting: true })
|
||||
if (valid && this.verifyGasParams()) {
|
||||
const validGasParams = this.verifyGasParams()
|
||||
if (valid && validGasParams) {
|
||||
this.props.sendTransaction(txMeta, event)
|
||||
} else {
|
||||
this.props.dispatch(actions.displayWarning('Invalid Gas Parameters'))
|
||||
@ -488,8 +492,12 @@ PendingTx.prototype.verifyGasParams = function () {
|
||||
)
|
||||
}
|
||||
|
||||
PendingTx.prototype._notZeroOrEmptyString = function (obj) {
|
||||
return obj !== '' && obj !== '0x0'
|
||||
PendingTx.prototype._notZeroOrEmptyString = function (value) {
|
||||
// allow undefined values
|
||||
if (value === undefined) return true
|
||||
// Geth will return '0x', and ganache-core v2.2.1 will return '0x0'
|
||||
const valueIsEmpty = !value || value === '0x' || value === '0x0'
|
||||
return !valueIsEmpty
|
||||
}
|
||||
|
||||
PendingTx.prototype.bnMultiplyByFraction = function (targetBN, numerator, denominator) {
|
||||
|
@ -68,7 +68,7 @@ ConfigScreen.prototype.render = function () {
|
||||
|
||||
currentProviderDisplay(metamaskState),
|
||||
|
||||
h('div', { style: {display: 'flex'} }, [
|
||||
h('div', { style: {display: 'block'} }, [
|
||||
h('input#new_rpc', {
|
||||
placeholder: 'New RPC URL',
|
||||
style: {
|
||||
@ -81,7 +81,70 @@ ConfigScreen.prototype.render = function () {
|
||||
if (event.key === 'Enter') {
|
||||
var element = event.target
|
||||
var newRpc = element.value
|
||||
rpcValidation(newRpc, state)
|
||||
var chainid = document.querySelector('input#chainid')
|
||||
var ticker = document.querySelector('input#ticker')
|
||||
var nickname = document.querySelector('input#nickname')
|
||||
rpcValidation(newRpc, chainid.value, ticker.value, nickname.value, state)
|
||||
}
|
||||
},
|
||||
}),
|
||||
h('br'),
|
||||
h('input#chainid', {
|
||||
placeholder: 'ChainId (optional)',
|
||||
style: {
|
||||
width: 'inherit',
|
||||
flex: '1 0 auto',
|
||||
height: '30px',
|
||||
margin: '8px',
|
||||
},
|
||||
onKeyPress (event) {
|
||||
if (event.key === 'Enter') {
|
||||
var element = document.querySelector('input#new_rpc')
|
||||
var newRpc = element.value
|
||||
var chainid = document.querySelector('input#chainid')
|
||||
var ticker = document.querySelector('input#ticker')
|
||||
var nickname = document.querySelector('input#nickname')
|
||||
rpcValidation(newRpc, chainid.value, ticker.value, nickname.value, state)
|
||||
}
|
||||
},
|
||||
}),
|
||||
h('br'),
|
||||
h('input#ticker', {
|
||||
placeholder: 'Symbol (optional)',
|
||||
style: {
|
||||
width: 'inherit',
|
||||
flex: '1 0 auto',
|
||||
height: '30px',
|
||||
margin: '8px',
|
||||
},
|
||||
onKeyPress (event) {
|
||||
if (event.key === 'Enter') {
|
||||
var element = document.querySelector('input#new_rpc')
|
||||
var newRpc = element.value
|
||||
var chainid = document.querySelector('input#chainid')
|
||||
var ticker = document.querySelector('input#ticker')
|
||||
var nickname = document.querySelector('input#nickname')
|
||||
rpcValidation(newRpc, chainid.value, ticker.value, nickname.value, state)
|
||||
}
|
||||
},
|
||||
}),
|
||||
h('br'),
|
||||
h('input#nickname', {
|
||||
placeholder: 'Nickname (optional)',
|
||||
style: {
|
||||
width: 'inherit',
|
||||
flex: '1 0 auto',
|
||||
height: '30px',
|
||||
margin: '8px',
|
||||
},
|
||||
onKeyPress (event) {
|
||||
if (event.key === 'Enter') {
|
||||
var element = document.querySelector('input#new_rpc')
|
||||
var newRpc = element.value
|
||||
var chainid = document.querySelector('input#chainid')
|
||||
var ticker = document.querySelector('input#ticker')
|
||||
var nickname = document.querySelector('input#nickname')
|
||||
rpcValidation(newRpc, chainid.value, ticker.value, nickname.value, state)
|
||||
}
|
||||
},
|
||||
}),
|
||||
@ -93,7 +156,10 @@ ConfigScreen.prototype.render = function () {
|
||||
event.preventDefault()
|
||||
var element = document.querySelector('input#new_rpc')
|
||||
var newRpc = element.value
|
||||
rpcValidation(newRpc, state)
|
||||
var chainid = document.querySelector('input#chainid')
|
||||
var ticker = document.querySelector('input#ticker')
|
||||
var nickname = document.querySelector('input#nickname')
|
||||
rpcValidation(newRpc, chainid.value, ticker.value, nickname.value, state)
|
||||
},
|
||||
}, 'Save'),
|
||||
]),
|
||||
@ -189,9 +255,9 @@ ConfigScreen.prototype.render = function () {
|
||||
)
|
||||
}
|
||||
|
||||
function rpcValidation (newRpc, state) {
|
||||
function rpcValidation (newRpc, chainid, ticker = 'ETH', nickname = '', state) {
|
||||
if (validUrl.isWebUri(newRpc)) {
|
||||
state.dispatch(actions.setRpcTarget(newRpc))
|
||||
state.dispatch(actions.setRpcTarget(newRpc, chainid, ticker, nickname))
|
||||
} else {
|
||||
var appendedRpc = `http://${newRpc}`
|
||||
if (validUrl.isWebUri(appendedRpc)) {
|
||||
|
@ -102,7 +102,7 @@ function parseBalance (balance) {
|
||||
|
||||
// Takes wei hex, returns an object with three properties.
|
||||
// Its "formatted" property is what we generally use to render values.
|
||||
function formatBalance (balance, decimalsToKeep, needsParse = true) {
|
||||
function formatBalance (balance, decimalsToKeep, needsParse = true, ticker = 'ETH') {
|
||||
var parsed = needsParse ? parseBalance(balance) : balance.split('.')
|
||||
var beforeDecimal = parsed[0]
|
||||
var afterDecimal = parsed[1]
|
||||
@ -112,14 +112,14 @@ function formatBalance (balance, decimalsToKeep, needsParse = true) {
|
||||
if (afterDecimal !== '0') {
|
||||
var sigFigs = afterDecimal.match(/^0*(.{2})/) // default: grabs 2 most significant digits
|
||||
if (sigFigs) { afterDecimal = sigFigs[0] }
|
||||
formatted = '0.' + afterDecimal + ' ETH'
|
||||
formatted = '0.' + afterDecimal + ` ${ticker}`
|
||||
}
|
||||
} else {
|
||||
formatted = beforeDecimal + '.' + afterDecimal.slice(0, 3) + ' ETH'
|
||||
formatted = beforeDecimal + '.' + afterDecimal.slice(0, 3) + ` ${ticker}`
|
||||
}
|
||||
} else {
|
||||
afterDecimal += Array(decimalsToKeep).join('0')
|
||||
formatted = beforeDecimal + '.' + afterDecimal.slice(0, decimalsToKeep) + ' ETH'
|
||||
formatted = beforeDecimal + '.' + afterDecimal.slice(0, decimalsToKeep) + ` ${ticker}`
|
||||
}
|
||||
return formatted
|
||||
}
|
||||
|
933
package-lock.json
generated
933
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
@ -82,6 +82,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@material-ui/core": "1.0.0",
|
||||
"@sentry/browser": "^4.1.1",
|
||||
"@zxing/library": "^0.8.0",
|
||||
"abi-decoder": "^1.0.9",
|
||||
"asmcrypto.js": "0.22.0",
|
||||
@ -97,7 +98,7 @@
|
||||
"browserify-derequire": "^0.9.4",
|
||||
"browserify-unibabel": "^3.0.0",
|
||||
"classnames": "^2.2.5",
|
||||
"clone": "^2.1.1",
|
||||
"clone": "^2.1.2",
|
||||
"copy-to-clipboard": "^3.0.8",
|
||||
"css-loader": "^0.28.11",
|
||||
"currency-formatter": "^1.4.2",
|
||||
@ -112,7 +113,7 @@
|
||||
"ensnare": "^1.0.0",
|
||||
"eslint-plugin-react": "^7.4.0",
|
||||
"eth-bin-to-ops": "^1.0.1",
|
||||
"eth-block-tracker": "^4.0.3",
|
||||
"eth-block-tracker": "^4.1.0",
|
||||
"eth-contract-metadata": "github:MetaMask/eth-contract-metadata#master",
|
||||
"eth-ens-namehash": "^2.0.8",
|
||||
"eth-hd-keyring": "^1.2.2",
|
||||
@ -124,7 +125,7 @@
|
||||
"eth-phishing-detect": "^1.1.4",
|
||||
"eth-query": "^2.1.2",
|
||||
"eth-sig-util": "^2.0.2",
|
||||
"eth-token-tracker": "^1.1.4",
|
||||
"eth-token-tracker": "^1.1.5",
|
||||
"eth-trezor-keyring": "^0.1.0",
|
||||
"ethereumjs-abi": "^0.6.4",
|
||||
"ethereumjs-tx": "^1.3.0",
|
||||
@ -143,7 +144,7 @@
|
||||
"fast-levenshtein": "^2.0.6",
|
||||
"file-loader": "^1.1.11",
|
||||
"fuse.js": "^3.2.0",
|
||||
"gulp": "github:gulpjs/gulp#4.0",
|
||||
"gulp": "github:gulpjs/gulp#v4.0.0",
|
||||
"gulp-autoprefixer": "^5.0.0",
|
||||
"gulp-debug": "^3.2.0",
|
||||
"gulp-eslint": "^4.0.0",
|
||||
@ -186,7 +187,6 @@
|
||||
"pumpify": "^1.3.4",
|
||||
"qrcode-npm": "0.0.3",
|
||||
"ramda": "^0.24.1",
|
||||
"raven-js": "^3.24.2",
|
||||
"react": "^15.6.2",
|
||||
"react-addons-css-transition-group": "^15.6.0",
|
||||
"react-dom": "^15.6.2",
|
||||
@ -261,8 +261,9 @@
|
||||
"eslint-plugin-json": "^1.2.0",
|
||||
"eslint-plugin-mocha": "^5.0.0",
|
||||
"eslint-plugin-react": "^7.4.0",
|
||||
"eth-json-rpc-middleware": "^3.1.1",
|
||||
"eth-json-rpc-middleware": "^3.1.6",
|
||||
"eth-keyring-controller": "^3.3.1",
|
||||
"fetch-mock": "^6.5.2",
|
||||
"file-loader": "^1.1.11",
|
||||
"fs-extra": "^6.0.1",
|
||||
"fs-promise": "^2.0.3",
|
||||
|
70
test/data/2-state.json
Normal file
70
test/data/2-state.json
Normal file
@ -0,0 +1,70 @@
|
||||
{ "isInitialized": true,
|
||||
"provider": { "type": "rpc", "rpcTarget": "http://localhost:8545" },
|
||||
"network": "loading",
|
||||
"accounts": {
|
||||
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
|
||||
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
|
||||
"balance": "0x0"
|
||||
},
|
||||
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
|
||||
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b",
|
||||
"balance": "0x0"
|
||||
}
|
||||
},
|
||||
"currentBlockGasLimit": "",
|
||||
"unapprovedTxs": {},
|
||||
"selectedAddressTxList": [],
|
||||
"computedBalances": {},
|
||||
"unapprovedMsgs": {},
|
||||
"unapprovedMsgCount": 0,
|
||||
"unapprovedPersonalMsgs": {},
|
||||
"unapprovedPersonalMsgCount": 0,
|
||||
"unapprovedTypedMessages": {},
|
||||
"unapprovedTypedMessagesCount": 0,
|
||||
"isUnlocked": true,
|
||||
"keyringTypes": [ "Simple Key Pair", "HD Key Tree" ],
|
||||
"keyrings":[
|
||||
{ "type": "HD Key Tree",
|
||||
"accounts": [
|
||||
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Simple Key Pair",
|
||||
"accounts": [
|
||||
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b"
|
||||
]
|
||||
}
|
||||
],
|
||||
"frequentRpcList": [],
|
||||
"currentAccountTab": "history",
|
||||
"tokens": [],
|
||||
"useBlockie": false,
|
||||
"featureFlags": {},
|
||||
"currentLocale": null,
|
||||
"identities": {
|
||||
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
|
||||
"name": "Account 1",
|
||||
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
|
||||
},
|
||||
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
|
||||
"name": "Account 2",
|
||||
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b"
|
||||
}
|
||||
},
|
||||
|
||||
"lostIdentities": {},
|
||||
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
|
||||
"recentBlocks": [],
|
||||
"addressBook": [],
|
||||
"currentCurrency": "usd",
|
||||
"conversionRate": 288.45,
|
||||
"conversionDate": 1506444677,
|
||||
"nextUnreadNotice": null,
|
||||
"noActiveNotices": true,
|
||||
"shapeShiftTxList": [],
|
||||
"infuraNetworkStatus": {},
|
||||
"lostAccounts": [],
|
||||
"seedWords": "debris dizzy just program just float decrease vacant alarm reduce speak stadium",
|
||||
"forgottenPassword": null
|
||||
}
|
@ -111,7 +111,9 @@
|
||||
"0x108cf70c7d384c552f42c07c41c0e1e46d77ea0d": 0.00039345803819379796,
|
||||
"0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5": 0.00008189274407698049
|
||||
},
|
||||
"ticker": "ETH",
|
||||
"currentCurrency": "usd",
|
||||
"nativeCurrency": "ETH",
|
||||
"conversionRate": 556.12,
|
||||
"addressBook": [
|
||||
{
|
||||
@ -1248,4 +1250,4 @@
|
||||
"context": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -286,7 +286,7 @@ describe('Using MetaMask with an existing account', function () {
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const inputAddress = await findElement(driver, By.css('input[placeholder="Recipient Address"]'))
|
||||
const inputAmount = await findElement(driver, By.css('.currency-display__input'))
|
||||
const inputAmount = await findElement(driver, By.css('.unit-input__input'))
|
||||
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
|
||||
await inputAmount.sendKeys('1')
|
||||
|
||||
@ -319,7 +319,7 @@ describe('Using MetaMask with an existing account', function () {
|
||||
|
||||
const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
|
||||
assert.equal(txValues.length, 1)
|
||||
assert.equal(await txValues[0].getText(), '-1 ETH')
|
||||
assert.ok(/-1\s*ETH/.test(await txValues[0].getText()))
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -371,7 +371,7 @@ describe('MetaMask', function () {
|
||||
|
||||
it('balance renders', async () => {
|
||||
const balance = await findElement(driver, By.css('.balance-display .token-amount'))
|
||||
await driver.wait(until.elementTextMatches(balance, /100.+ETH/))
|
||||
await driver.wait(until.elementTextMatches(balance, /100\s*ETH/))
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
})
|
||||
@ -383,7 +383,7 @@ describe('MetaMask', function () {
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const inputAddress = await findElement(driver, By.css('input[placeholder="Recipient Address"]'))
|
||||
const inputAmount = await findElement(driver, By.css('.currency-display__input'))
|
||||
const inputAmount = await findElement(driver, By.css('.unit-input__input'))
|
||||
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
|
||||
await inputAmount.sendKeys('1')
|
||||
|
||||
@ -420,7 +420,7 @@ describe('MetaMask', function () {
|
||||
|
||||
if (process.env.SELENIUM_BROWSER !== 'firefox') {
|
||||
const txValues = await findElement(driver, By.css('.transaction-list-item__amount--primary'))
|
||||
await driver.wait(until.elementTextMatches(txValues, /-1\sETH/), 10000)
|
||||
await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/), 10000)
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -462,7 +462,7 @@ describe('MetaMask', function () {
|
||||
assert.equal(transactions.length, 2)
|
||||
|
||||
const txValues = await findElement(driver, By.css('.transaction-list-item__amount--primary'))
|
||||
await driver.wait(until.elementTextMatches(txValues, /-3\sETH/), 10000)
|
||||
await driver.wait(until.elementTextMatches(txValues, /-3\s*ETH/), 10000)
|
||||
})
|
||||
})
|
||||
|
||||
@ -540,7 +540,7 @@ describe('MetaMask', function () {
|
||||
|
||||
await findElements(driver, By.css('.transaction-list-item'))
|
||||
const [txListValue] = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
|
||||
await driver.wait(until.elementTextMatches(txListValue, /-4\sETH/), 10000)
|
||||
await driver.wait(until.elementTextMatches(txListValue, /-4\s*ETH/), 10000)
|
||||
await txListValue.click()
|
||||
await delay(regularDelayMs)
|
||||
|
||||
@ -574,7 +574,7 @@ describe('MetaMask', function () {
|
||||
}, 10000)
|
||||
|
||||
const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
|
||||
await driver.wait(until.elementTextMatches(txValues[0], /-4\sETH/), 10000)
|
||||
await driver.wait(until.elementTextMatches(txValues[0], /-4\s*ETH/), 10000)
|
||||
|
||||
// const txAccounts = await findElements(driver, By.css('.tx-list-account'))
|
||||
// const firstTxAddress = await txAccounts[0].getText()
|
||||
@ -606,7 +606,7 @@ describe('MetaMask', function () {
|
||||
}, 10000)
|
||||
|
||||
const txValues = await findElement(driver, By.css('.transaction-list-item__amount--primary'))
|
||||
await driver.wait(until.elementTextMatches(txValues, /-0\sETH/), 10000)
|
||||
await driver.wait(until.elementTextMatches(txValues, /-0\s*ETH/), 10000)
|
||||
|
||||
await closeAllWindowHandlesExcept(driver, [extension, dapp])
|
||||
await driver.switchTo().window(extension)
|
||||
@ -616,9 +616,9 @@ describe('MetaMask', function () {
|
||||
const balance = await findElement(driver, By.css('.transaction-view-balance__primary-balance'))
|
||||
await delay(regularDelayMs)
|
||||
if (process.env.SELENIUM_BROWSER !== 'firefox') {
|
||||
await driver.wait(until.elementTextMatches(balance, /^92.*ETH.*$/), 10000)
|
||||
await driver.wait(until.elementTextMatches(balance, /^92.*\s*ETH.*$/), 10000)
|
||||
const tokenAmount = await balance.getText()
|
||||
assert.ok(/^92.*ETH.*$/.test(tokenAmount))
|
||||
assert.ok(/^92.*\s*ETH.*$/.test(tokenAmount))
|
||||
await delay(regularDelayMs)
|
||||
}
|
||||
})
|
||||
@ -662,7 +662,7 @@ describe('MetaMask', function () {
|
||||
})
|
||||
|
||||
it('clicks on the Add Token button', async () => {
|
||||
const addToken = await driver.findElement(By.css('.wallet-view__add-token-button'))
|
||||
const addToken = await driver.findElement(By.xpath(`//div[contains(text(), 'Add Token')]`))
|
||||
await addToken.click()
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
@ -702,7 +702,7 @@ describe('MetaMask', function () {
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const inputAddress = await findElement(driver, By.css('input[placeholder="Recipient Address"]'))
|
||||
const inputAmount = await findElement(driver, By.css('.currency-display__input'))
|
||||
const inputAmount = await findElement(driver, By.css('.unit-input__input'))
|
||||
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
|
||||
await inputAmount.sendKeys('50')
|
||||
|
||||
@ -764,7 +764,7 @@ describe('MetaMask', function () {
|
||||
// test cancelled on firefox until https://github.com/mozilla/geckodriver/issues/906 is resolved,
|
||||
// or possibly until we use latest version of firefox in the tests
|
||||
if (process.env.SELENIUM_BROWSER !== 'firefox') {
|
||||
await driver.wait(until.elementTextMatches(txValues[0], /-50\sTST/), 10000)
|
||||
await driver.wait(until.elementTextMatches(txValues[0], /-50\s*TST/), 10000)
|
||||
}
|
||||
|
||||
driver.wait(async () => {
|
||||
@ -798,7 +798,7 @@ describe('MetaMask', function () {
|
||||
|
||||
await findElements(driver, By.css('.transaction-list__pending-transactions'))
|
||||
const [txListValue] = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
|
||||
await driver.wait(until.elementTextMatches(txListValue, /-7\sTST/), 10000)
|
||||
await driver.wait(until.elementTextMatches(txListValue, /-7\s*TST/), 10000)
|
||||
await txListValue.click()
|
||||
await delay(regularDelayMs)
|
||||
|
||||
@ -834,8 +834,8 @@ describe('MetaMask', function () {
|
||||
await save.click()
|
||||
await driver.wait(until.stalenessOf(gasModal))
|
||||
|
||||
const gasFeeInputs = await findElements(driver, By.css('.confirm-detail-row__eth'))
|
||||
assert.equal(await gasFeeInputs[0].getText(), '♦ 0.0006')
|
||||
const gasFeeInputs = await findElements(driver, By.css('.confirm-detail-row__primary'))
|
||||
assert.equal(await gasFeeInputs[0].getText(), '0.0006')
|
||||
})
|
||||
|
||||
it('submits the transaction', async function () {
|
||||
@ -851,7 +851,7 @@ describe('MetaMask', function () {
|
||||
}, 10000)
|
||||
|
||||
const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
|
||||
await driver.wait(until.elementTextMatches(txValues[0], /-7\sTST/))
|
||||
await driver.wait(until.elementTextMatches(txValues[0], /-7\s*TST/))
|
||||
const txStatuses = await findElements(driver, By.css('.transaction-list-item__action'))
|
||||
await driver.wait(until.elementTextMatches(txStatuses[0], /Sent\sToken/))
|
||||
|
||||
@ -897,7 +897,7 @@ describe('MetaMask', function () {
|
||||
|
||||
const [txListItem] = await findElements(driver, By.css('.transaction-list-item'))
|
||||
const [txListValue] = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
|
||||
await driver.wait(until.elementTextMatches(txListValue, /-7\sTST/))
|
||||
await driver.wait(until.elementTextMatches(txListValue, /-7\s*TST/))
|
||||
await txListItem.click()
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
@ -957,8 +957,8 @@ describe('MetaMask', function () {
|
||||
await save.click()
|
||||
await driver.wait(until.stalenessOf(gasModal))
|
||||
|
||||
const gasFeeInputs = await findElements(driver, By.css('.confirm-detail-row__eth'))
|
||||
assert.equal(await gasFeeInputs[0].getText(), '♦ 0.0006')
|
||||
const gasFeeInputs = await findElements(driver, By.css('.confirm-detail-row__primary'))
|
||||
assert.equal(await gasFeeInputs[0].getText(), '0.0006')
|
||||
})
|
||||
|
||||
it('submits the transaction', async function () {
|
||||
@ -974,7 +974,7 @@ describe('MetaMask', function () {
|
||||
}, 10000)
|
||||
|
||||
const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
|
||||
await driver.wait(until.elementTextMatches(txValues[0], /-7\sTST/))
|
||||
await driver.wait(until.elementTextMatches(txValues[0], /-7\s*TST/))
|
||||
const txStatuses = await findElements(driver, By.css('.transaction-list-item__action'))
|
||||
await driver.wait(until.elementTextMatches(txStatuses[0], /Approve/))
|
||||
})
|
||||
@ -1002,7 +1002,7 @@ describe('MetaMask', function () {
|
||||
|
||||
describe('Add existing token using search', () => {
|
||||
it('clicks on the Add Token button', async () => {
|
||||
const addToken = await findElement(driver, By.xpath(`//button[contains(text(), 'Add Token')]`))
|
||||
const addToken = await findElement(driver, By.xpath(`//div[contains(text(), 'Add Token')]`))
|
||||
await addToken.click()
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
@ -1027,7 +1027,7 @@ describe('MetaMask', function () {
|
||||
|
||||
it('renders the balance for the chosen token', async () => {
|
||||
const balance = await findElement(driver, By.css('.transaction-view-balance__token-balance'))
|
||||
await driver.wait(until.elementTextMatches(balance, /0\sBAT/))
|
||||
await driver.wait(until.elementTextMatches(balance, /0\s*BAT/))
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
})
|
||||
|
@ -11,7 +11,7 @@ sleep 5
|
||||
cd test/e2e/beta/
|
||||
rm -rf drizzle-test
|
||||
mkdir drizzle-test && cd drizzle-test
|
||||
npm install truffle
|
||||
sudo npm install -g truffle
|
||||
truffle unbox drizzle
|
||||
echo "Deploying contracts for Drizzle test..."
|
||||
truffle compile && truffle migrate
|
||||
|
@ -1,140 +0,0 @@
|
||||
const reactTriggerChange = require('react-trigger-change')
|
||||
const {
|
||||
timeout,
|
||||
queryAsync,
|
||||
findAsync,
|
||||
} = require('../../lib/util')
|
||||
|
||||
QUnit.module('Add token flow')
|
||||
|
||||
QUnit.test('successful add token flow', (assert) => {
|
||||
const done = assert.async()
|
||||
runAddTokenFlowTest(assert)
|
||||
.then(done)
|
||||
.catch(err => {
|
||||
assert.notOk(err, `Error was thrown: ${err.stack}`)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
async function runAddTokenFlowTest (assert, done) {
|
||||
const selectState = await queryAsync($, 'select')
|
||||
selectState.val('add token')
|
||||
reactTriggerChange(selectState[0])
|
||||
|
||||
// Used to set values on TextField input component
|
||||
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
|
||||
window.HTMLInputElement.prototype, 'value'
|
||||
).set
|
||||
|
||||
// Check that no tokens have been added
|
||||
assert.ok($('.token-list-item').length === 0, 'no tokens added')
|
||||
|
||||
// Go to Add Token screen
|
||||
let addTokenButton = await queryAsync($, 'button.btn-primary.wallet-view__add-token-button')
|
||||
assert.ok(addTokenButton[0], 'add token button present')
|
||||
addTokenButton[0].click()
|
||||
|
||||
// Verify Add Token screen
|
||||
let addTokenWrapper = await queryAsync($, '.page-container')
|
||||
assert.ok(addTokenWrapper[0], 'add token wrapper renders')
|
||||
|
||||
let addTokenTitle = await queryAsync($, '.page-container__title')
|
||||
assert.equal(addTokenTitle[0].textContent, 'Add Tokens', 'add token title is correct')
|
||||
|
||||
// Cancel Add Token
|
||||
const cancelAddTokenButton = await queryAsync($, 'button.btn-default.btn--large.page-container__footer-button')
|
||||
assert.ok(cancelAddTokenButton[0], 'cancel add token button present')
|
||||
cancelAddTokenButton.click()
|
||||
|
||||
assert.ok($('.wallet-view')[0], 'cancelled and returned to account detail wallet view')
|
||||
|
||||
// Return to Add Token Screen
|
||||
addTokenButton = await queryAsync($, 'button.btn-primary.wallet-view__add-token-button')
|
||||
assert.ok(addTokenButton[0], 'add token button present')
|
||||
addTokenButton[0].click()
|
||||
|
||||
// Verify Add Token Screen
|
||||
addTokenWrapper = await queryAsync($, '.page-container')
|
||||
addTokenTitle = await queryAsync($, '.page-container__title')
|
||||
assert.ok(addTokenWrapper[0], 'add token wrapper renders')
|
||||
assert.equal(addTokenTitle[0].textContent, 'Add Tokens', 'add token title is correct')
|
||||
|
||||
// Search for token
|
||||
const searchInput = (await findAsync(addTokenWrapper, '#search-tokens'))[0]
|
||||
searchInput.focus()
|
||||
await timeout(1000)
|
||||
nativeInputValueSetter.call(searchInput, 'a')
|
||||
searchInput.dispatchEvent(new Event('input', { bubbles: true}))
|
||||
|
||||
// Click token to add
|
||||
const tokenWrapper = await queryAsync($, 'div.token-list__token')
|
||||
assert.ok(tokenWrapper[0], 'token found')
|
||||
const tokenImageProp = tokenWrapper.find('.token-list__token-icon').css('background-image')
|
||||
const tokenImageUrl = tokenImageProp.slice(5, -2)
|
||||
tokenWrapper[0].click()
|
||||
|
||||
// Click Next button
|
||||
const nextButton = await queryAsync($, 'button.btn-primary.btn--large')
|
||||
assert.equal(nextButton[0].textContent, 'Next', 'next button rendered')
|
||||
nextButton[0].click()
|
||||
|
||||
// Confirm Add token
|
||||
const confirmAddToken = await queryAsync($, '.confirm-add-token')
|
||||
assert.ok(confirmAddToken[0], 'confirm add token rendered')
|
||||
assert.ok($('button.btn-primary.btn--large')[0], 'confirm add token button found')
|
||||
$('button.btn-primary.btn--large')[0].click()
|
||||
|
||||
// Verify added token image
|
||||
let heroBalance = await queryAsync($, '.transaction-view-balance__balance-container')
|
||||
assert.ok(heroBalance, 'rendered hero balance')
|
||||
assert.ok(tokenImageUrl.indexOf(heroBalance.find('img').attr('src')) > -1, 'token added')
|
||||
|
||||
// Return to Add Token Screen
|
||||
addTokenButton = await queryAsync($, 'button.btn-primary.wallet-view__add-token-button')
|
||||
assert.ok(addTokenButton[0], 'add token button present')
|
||||
addTokenButton[0].click()
|
||||
|
||||
addTokenWrapper = await queryAsync($, '.page-container')
|
||||
const addTokenTabs = await queryAsync($, '.page-container__tab')
|
||||
assert.equal(addTokenTabs.length, 2, 'expected number of tabs')
|
||||
assert.equal(addTokenTabs[1].textContent, 'Custom Token', 'Custom Token tab present')
|
||||
assert.ok(addTokenTabs[1], 'add custom token tab present')
|
||||
addTokenTabs[1].click()
|
||||
await timeout(1000)
|
||||
|
||||
// Input token contract address
|
||||
const customInput = (await findAsync(addTokenWrapper, '#custom-address'))[0]
|
||||
customInput.focus()
|
||||
await timeout(1000)
|
||||
nativeInputValueSetter.call(customInput, '0x177af043D3A1Aed7cc5f2397C70248Fc6cDC056c')
|
||||
customInput.dispatchEvent(new Event('input', { bubbles: true}))
|
||||
|
||||
|
||||
// Click Next button
|
||||
// nextButton = await queryAsync($, 'button.btn-primary--lg')
|
||||
// assert.equal(nextButton[0].textContent, 'Next', 'next button rendered')
|
||||
// nextButton[0].click()
|
||||
|
||||
// // Verify symbol length error since contract address won't return symbol
|
||||
const errorMessage = await queryAsync($, '#custom-symbol-helper-text')
|
||||
assert.ok(errorMessage[0], 'error rendered')
|
||||
|
||||
$('button.btn-default.btn--large')[0].click()
|
||||
|
||||
// await timeout(100000)
|
||||
|
||||
// Confirm Add token
|
||||
// assert.equal(
|
||||
// $('.page-container__subtitle')[0].textContent,
|
||||
// 'Would you like to add these tokens?',
|
||||
// 'confirm add token rendered'
|
||||
// )
|
||||
// assert.ok($('button.btn-primary--lg')[0], 'confirm add token button found')
|
||||
// $('button.btn-primary--lg')[0].click()
|
||||
|
||||
// Verify added token image
|
||||
heroBalance = await queryAsync($, '.transaction-view-balance__balance-container')
|
||||
assert.ok(heroBalance, 'rendered hero balance')
|
||||
assert.ok(heroBalance.find('.identicon')[0], 'token added')
|
||||
}
|
@ -25,5 +25,5 @@ async function runCurrencyLocalizationTest (assert, done) {
|
||||
const txView = await queryAsync($, '.transaction-view')
|
||||
const heroBalance = await findAsync($(txView), '.transaction-view-balance__balance')
|
||||
const fiatAmount = await findAsync($(heroBalance), '.transaction-view-balance__secondary-balance')
|
||||
assert.equal(fiatAmount[0].textContent, '₱102,707.97 PHP')
|
||||
assert.equal(fiatAmount[0].textContent, '₱102,707.97PHP')
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ async function customizeGas (assert, price, limit, ethFee, usdFee) {
|
||||
const sendGasField = await queryAsync($, '.send-v2__gas-fee-display')
|
||||
|
||||
assert.equal(
|
||||
(await findAsync(sendGasField, '.currency-display__input-wrapper > input')).val(),
|
||||
(await findAsync(sendGasField, '.currency-display-component'))[0].textContent,
|
||||
ethFee,
|
||||
'send gas field should show customized gas total'
|
||||
)
|
||||
@ -94,12 +94,12 @@ async function runSendFlowTest (assert, done) {
|
||||
sendToDropdownList.children()[2].click()
|
||||
|
||||
const sendToAccountAddress = sendToFieldInput.val()
|
||||
assert.equal(sendToAccountAddress, '0x2f8d4a878cfa04a6e60d46362f5644deab66572d', 'send to dropdown selects the correct address')
|
||||
assert.equal(sendToAccountAddress, '0x2f8D4a878cFA04A6E60D46362f5644DeAb66572D', 'send to dropdown selects the correct address')
|
||||
|
||||
const sendAmountField = await queryAsync($, '.send-v2__form-row:eq(2)')
|
||||
sendAmountField.find('.currency-display')[0].click()
|
||||
sendAmountField.find('.unit-input')[0].click()
|
||||
|
||||
const sendAmountFieldInput = await findAsync(sendAmountField, '.currency-display__input')
|
||||
const sendAmountFieldInput = await findAsync(sendAmountField, '.unit-input__input')
|
||||
sendAmountFieldInput.val('5.1')
|
||||
reactTriggerChange(sendAmountField.find('input')[0])
|
||||
|
||||
@ -112,9 +112,9 @@ async function runSendFlowTest (assert, done) {
|
||||
errorMessage = $('.send-v2__error')
|
||||
assert.equal(errorMessage.length, 0, 'send should stop rendering amount error message after amount is corrected')
|
||||
|
||||
await customizeGas(assert, 0, 21000, '0', '$0.00 USD')
|
||||
await customizeGas(assert, 1, 21000, '0.000021', '$0.03 USD')
|
||||
await customizeGas(assert, 500, 60000, '0.03', '$36.03 USD')
|
||||
await customizeGas(assert, 0, 21000, '0ETH', '$0.00USD')
|
||||
await customizeGas(assert, 1, 21000, '0.000021ETH', '$0.03USD')
|
||||
await customizeGas(assert, 500, 60000, '0.03ETH', '$36.03USD')
|
||||
|
||||
const sendButton = await queryAsync($, 'button.btn-primary.btn--large.page-container__footer-button')
|
||||
assert.equal(sendButton[0].textContent, 'Next', 'next button rendered')
|
||||
@ -130,11 +130,11 @@ async function runSendFlowTest (assert, done) {
|
||||
const confirmToName = (await queryAsync($, '.sender-to-recipient__name')).last()
|
||||
assert.equal(confirmToName[0].textContent, 'Send Account 3', 'confirm screen should show correct to name')
|
||||
|
||||
const confirmScreenRowFiats = await queryAsync($, '.confirm-detail-row__fiat')
|
||||
const confirmScreenRowFiats = await queryAsync($, '.confirm-detail-row__secondary')
|
||||
const confirmScreenGas = confirmScreenRowFiats[0]
|
||||
assert.equal(confirmScreenGas.textContent, '$3.60', 'confirm screen should show correct gas')
|
||||
const confirmScreenTotal = confirmScreenRowFiats[1]
|
||||
assert.equal(confirmScreenTotal.textContent, '$2,405.36', 'confirm screen should show correct total')
|
||||
assert.equal(confirmScreenTotal.textContent, '$2,405.37', 'confirm screen should show correct total')
|
||||
|
||||
const confirmScreenBackButton = await queryAsync($, '.confirm-page-container-header__back-button')
|
||||
confirmScreenBackButton[0].click()
|
||||
@ -150,9 +150,9 @@ async function runSendFlowTest (assert, done) {
|
||||
sendToFieldInputInEdit.val('0xd85a4b6a394794842887b8284293d69163007bbb')
|
||||
|
||||
const sendAmountFieldInEdit = await queryAsync($, '.send-v2__form-row:eq(2)')
|
||||
sendAmountFieldInEdit.find('.currency-display')[0].click()
|
||||
sendAmountFieldInEdit.find('.unit-input')[0].click()
|
||||
|
||||
const sendAmountFieldInputInEdit = sendAmountFieldInEdit.find('.currency-display__input')
|
||||
const sendAmountFieldInputInEdit = sendAmountFieldInEdit.find('.unit-input__input')
|
||||
sendAmountFieldInputInEdit.val('1.0')
|
||||
reactTriggerChange(sendAmountFieldInputInEdit[0])
|
||||
|
||||
|
@ -47,7 +47,7 @@ describe('# Network Controller', function () {
|
||||
|
||||
describe('#setNetworkState', function () {
|
||||
it('should update the network', function () {
|
||||
networkController.setNetworkState(1)
|
||||
networkController.setNetworkState(1, 'rpc')
|
||||
const networkState = networkController.getNetworkState()
|
||||
assert.equal(networkState, 1, 'network is 1')
|
||||
})
|
||||
|
@ -375,6 +375,11 @@ describe('preferences controller', function () {
|
||||
await preferencesController.requestWatchAsset(req, res, asy.next, asy.end)
|
||||
sandbox.assert.called(stubEnd)
|
||||
sandbox.assert.notCalled(stubNext)
|
||||
req.method = 'wallet_watchAsset'
|
||||
req.params.type = 'someasset'
|
||||
await preferencesController.requestWatchAsset(req, res, asy.next, asy.end)
|
||||
sandbox.assert.calledTwice(stubEnd)
|
||||
sandbox.assert.notCalled(stubNext)
|
||||
})
|
||||
it('should through error if method is supported but asset type is not', async function () {
|
||||
req.method = 'metamask_watchAsset'
|
||||
@ -479,5 +484,24 @@ describe('preferences controller', function () {
|
||||
assert.equal(preferencesController.store.getState().seedWords, 'foo bar baz')
|
||||
})
|
||||
})
|
||||
|
||||
describe('on updateFrequentRpcList', function () {
|
||||
it('should add custom RPC url to state', function () {
|
||||
preferencesController.addToFrequentRpcList('rpc_url', 1)
|
||||
preferencesController.addToFrequentRpcList('http://localhost:8545', 1)
|
||||
assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [{ rpcUrl: 'rpc_url', chainId: 1, ticker: 'ETH', nickname: '' }] )
|
||||
preferencesController.addToFrequentRpcList('rpc_url', 1)
|
||||
assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [{ rpcUrl: 'rpc_url', chainId: 1, ticker: 'ETH', nickname: '' }] )
|
||||
})
|
||||
|
||||
it('should remove custom RPC url from state', function () {
|
||||
preferencesController.addToFrequentRpcList('rpc_url', 1)
|
||||
assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [{ rpcUrl: 'rpc_url', chainId: 1, ticker: 'ETH', nickname: '' }] )
|
||||
preferencesController.removeFromFrequentRpcList('other_rpc_url')
|
||||
preferencesController.removeFromFrequentRpcList('http://localhost:8545')
|
||||
preferencesController.removeFromFrequentRpcList('rpc_url')
|
||||
assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -158,9 +158,19 @@ describe('Transaction Controller', function () {
|
||||
})
|
||||
|
||||
describe('#addUnapprovedTransaction', function () {
|
||||
const selectedAddress = '0x1678a085c290ebd122dc42cba69373b5953b831d'
|
||||
|
||||
let getSelectedAddress
|
||||
beforeEach(function () {
|
||||
getSelectedAddress = sinon.stub(txController, 'getSelectedAddress').returns(selectedAddress)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
getSelectedAddress.restore()
|
||||
})
|
||||
|
||||
it('should add an unapproved transaction and return a valid txMeta', function (done) {
|
||||
txController.addUnapprovedTransaction({ from: '0x1678a085c290ebd122dc42cba69373b5953b831d' })
|
||||
txController.addUnapprovedTransaction({ from: selectedAddress })
|
||||
.then((txMeta) => {
|
||||
assert(('id' in txMeta), 'should have a id')
|
||||
assert(('time' in txMeta), 'should have a time stamp')
|
||||
@ -180,25 +190,37 @@ describe('Transaction Controller', function () {
|
||||
assert(txMetaFromEmit, 'txMeta is falsey')
|
||||
done()
|
||||
})
|
||||
txController.addUnapprovedTransaction({ from: '0x1678a085c290ebd122dc42cba69373b5953b831d' })
|
||||
txController.addUnapprovedTransaction({ from: selectedAddress })
|
||||
.catch(done)
|
||||
})
|
||||
|
||||
it('should fail if recipient is public', function (done) {
|
||||
txController.networkStore = new ObservableStore(1)
|
||||
txController.addUnapprovedTransaction({ from: '0x1678a085c290ebd122dc42cba69373b5953b831d', to: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2' })
|
||||
txController.addUnapprovedTransaction({ from: selectedAddress, to: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2' })
|
||||
.catch((err) => {
|
||||
if (err.message === 'Recipient is a public account') done()
|
||||
else done(err)
|
||||
})
|
||||
})
|
||||
|
||||
it('should fail if the from address isn\'t the selected address', function (done) {
|
||||
txController.addUnapprovedTransaction({from: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2'})
|
||||
.then(function () {
|
||||
assert.fail('transaction should not have been added')
|
||||
done()
|
||||
})
|
||||
.catch(function () {
|
||||
assert.ok('pass')
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not fail if recipient is public but not on mainnet', function (done) {
|
||||
txController.once('newUnapprovedTx', (txMetaFromEmit) => {
|
||||
assert(txMetaFromEmit, 'txMeta is falsey')
|
||||
done()
|
||||
})
|
||||
txController.addUnapprovedTransaction({ from: '0x1678a085c290ebd122dc42cba69373b5953b831d', to: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2' })
|
||||
txController.addUnapprovedTransaction({ from: selectedAddress, to: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2' })
|
||||
.catch(done)
|
||||
})
|
||||
})
|
||||
|
@ -1,44 +0,0 @@
|
||||
const assert = require('assert')
|
||||
const h = require('react-hyperscript')
|
||||
const { createMockStore } = require('redux-test-utils')
|
||||
const { shallowWithStore } = require('../../lib/render-helpers')
|
||||
const BalanceComponent = require('../../../ui/app/components/balance-component')
|
||||
const mockState = {
|
||||
metamask: {
|
||||
accounts: { abc: {} },
|
||||
network: 1,
|
||||
selectedAddress: 'abc',
|
||||
},
|
||||
}
|
||||
|
||||
describe('BalanceComponent', function () {
|
||||
let balanceComponent
|
||||
let store
|
||||
let component
|
||||
beforeEach(function () {
|
||||
store = createMockStore(mockState)
|
||||
component = shallowWithStore(h(BalanceComponent), store)
|
||||
balanceComponent = component.dive()
|
||||
})
|
||||
|
||||
it('shows token balance and convert to fiat value based on conversion rate', function () {
|
||||
const formattedBalance = '1.23 ETH'
|
||||
|
||||
const tokenBalance = balanceComponent.instance().getTokenBalance(formattedBalance, false)
|
||||
const fiatDisplayNumber = balanceComponent.instance().getFiatDisplayNumber(formattedBalance, 2)
|
||||
|
||||
assert.equal('1.23 ETH', tokenBalance)
|
||||
assert.equal(2.46, fiatDisplayNumber)
|
||||
})
|
||||
|
||||
it('shows only the token balance when conversion rate is not available', function () {
|
||||
const formattedBalance = '1.23 ETH'
|
||||
|
||||
const tokenBalance = balanceComponent.instance().getTokenBalance(formattedBalance, false)
|
||||
const fiatDisplayNumber = balanceComponent.instance().getFiatDisplayNumber(formattedBalance, 0)
|
||||
|
||||
assert.equal('1.23 ETH', tokenBalance)
|
||||
assert.equal('N/A', fiatDisplayNumber)
|
||||
})
|
||||
|
||||
})
|
1468
test/unit/ui/app/actions.spec.js
Normal file
1468
test/unit/ui/app/actions.spec.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -305,6 +305,12 @@ var actions = {
|
||||
updateFeatureFlags,
|
||||
UPDATE_FEATURE_FLAGS: 'UPDATE_FEATURE_FLAGS',
|
||||
|
||||
// Preferences
|
||||
setPreference,
|
||||
updatePreferences,
|
||||
UPDATE_PREFERENCES: 'UPDATE_PREFERENCES',
|
||||
setUseNativeCurrencyAsPrimaryCurrencyPreference,
|
||||
|
||||
setMouseUserState,
|
||||
SET_MOUSE_USER_STATE: 'SET_MOUSE_USER_STATE',
|
||||
|
||||
@ -1762,7 +1768,7 @@ function markNoticeRead (notice) {
|
||||
background.markNoticeRead(notice, (err, notice) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
if (err) {
|
||||
dispatch(actions.displayWarning(err))
|
||||
dispatch(actions.displayWarning(err.message))
|
||||
return reject(err)
|
||||
}
|
||||
|
||||
@ -1852,7 +1858,7 @@ function setProviderType (type) {
|
||||
background.setProviderType(type, (err, result) => {
|
||||
if (err) {
|
||||
log.error(err)
|
||||
return dispatch(self.displayWarning('Had a problem changing networks!'))
|
||||
return dispatch(actions.displayWarning('Had a problem changing networks!'))
|
||||
}
|
||||
dispatch(actions.updateProviderType(type))
|
||||
dispatch(actions.setSelectedToken())
|
||||
@ -1868,13 +1874,13 @@ function updateProviderType (type) {
|
||||
}
|
||||
}
|
||||
|
||||
function setRpcTarget (newRpc) {
|
||||
function setRpcTarget (newRpc, chainId, ticker = 'ETH', nickname = '') {
|
||||
return (dispatch) => {
|
||||
log.debug(`background.setRpcTarget: ${newRpc}`)
|
||||
background.setCustomRpc(newRpc, (err, result) => {
|
||||
log.debug(`background.setRpcTarget: ${newRpc} ${chainId} ${ticker} ${nickname}`)
|
||||
background.setCustomRpc(newRpc, chainId, ticker, nickname, (err, result) => {
|
||||
if (err) {
|
||||
log.error(err)
|
||||
return dispatch(self.displayWarning('Had a problem changing networks!'))
|
||||
return dispatch(actions.displayWarning('Had a problem changing networks!'))
|
||||
}
|
||||
dispatch(actions.setSelectedToken())
|
||||
})
|
||||
@ -2298,6 +2304,36 @@ function updateFeatureFlags (updatedFeatureFlags) {
|
||||
}
|
||||
}
|
||||
|
||||
function setPreference (preference, value) {
|
||||
return dispatch => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
return new Promise((resolve, reject) => {
|
||||
background.setPreference(preference, value, (err, updatedPreferences) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
|
||||
if (err) {
|
||||
dispatch(actions.displayWarning(err.message))
|
||||
return reject(err)
|
||||
}
|
||||
|
||||
dispatch(actions.updatePreferences(updatedPreferences))
|
||||
resolve(updatedPreferences)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function updatePreferences (value) {
|
||||
return {
|
||||
type: actions.UPDATE_PREFERENCES,
|
||||
value,
|
||||
}
|
||||
}
|
||||
|
||||
function setUseNativeCurrencyAsPrimaryCurrencyPreference (value) {
|
||||
return setPreference('useNativeCurrencyAsPrimaryCurrency', value)
|
||||
}
|
||||
|
||||
function setNetworkNonce (networkNonce) {
|
||||
return {
|
||||
type: actions.SET_NETWORK_NONCE,
|
||||
@ -2309,6 +2345,10 @@ function updateNetworkNonce (address) {
|
||||
return (dispatch) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
global.ethQuery.getTransactionCount(address, (err, data) => {
|
||||
if (err) {
|
||||
dispatch(actions.displayWarning(err.message))
|
||||
return reject(err)
|
||||
}
|
||||
dispatch(setNetworkNonce(data))
|
||||
resolve(data)
|
||||
})
|
||||
@ -2396,7 +2436,7 @@ function setUseBlockie (val) {
|
||||
function updateCurrentLocale (key) {
|
||||
return (dispatch) => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
fetchLocale(key)
|
||||
return fetchLocale(key)
|
||||
.then((localeMessages) => {
|
||||
log.debug(`background.setCurrentLocale`)
|
||||
background.setCurrentLocale(key, (err) => {
|
||||
|
@ -101,7 +101,7 @@ class App extends Component {
|
||||
network,
|
||||
isMouseUser,
|
||||
provider,
|
||||
frequentRpcList,
|
||||
frequentRpcListDetail,
|
||||
currentView,
|
||||
setMouseUserState,
|
||||
sidebar,
|
||||
@ -147,7 +147,7 @@ class App extends Component {
|
||||
// network dropdown
|
||||
h(NetworkDropdown, {
|
||||
provider,
|
||||
frequentRpcList,
|
||||
frequentRpcListDetail,
|
||||
}, []),
|
||||
|
||||
h(AccountMenu),
|
||||
@ -230,7 +230,7 @@ App.propTypes = {
|
||||
alertMessage: PropTypes.string,
|
||||
network: PropTypes.string,
|
||||
provider: PropTypes.object,
|
||||
frequentRpcList: PropTypes.array,
|
||||
frequentRpcListDetail: PropTypes.array,
|
||||
currentView: PropTypes.object,
|
||||
sidebar: PropTypes.object,
|
||||
alertOpen: PropTypes.bool,
|
||||
@ -322,7 +322,7 @@ function mapStateToProps (state) {
|
||||
forgottenPassword: state.appState.forgottenPassword,
|
||||
nextUnreadNotice,
|
||||
lostAccounts,
|
||||
frequentRpcList: state.metamask.frequentRpcList || [],
|
||||
frequentRpcListDetail: state.metamask.frequentRpcListDetail || [],
|
||||
currentCurrency: state.metamask.currentCurrency,
|
||||
isMouseUser: state.appState.isMouseUser,
|
||||
betaUI: state.metamask.featureFlags.betaUI,
|
||||
|
@ -6,10 +6,11 @@ const genAccountLink = require('etherscan-link').createAccountLink
|
||||
const connect = require('react-redux').connect
|
||||
const Dropdown = require('./dropdown').Dropdown
|
||||
const DropdownMenuItem = require('./dropdown').DropdownMenuItem
|
||||
const Identicon = require('./identicon')
|
||||
const copyToClipboard = require('copy-to-clipboard')
|
||||
const { checksumAddress } = require('../util')
|
||||
|
||||
import Identicon from './identicon'
|
||||
|
||||
class AccountDropdowns extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
@ -7,12 +7,12 @@ const PropTypes = require('prop-types')
|
||||
const h = require('react-hyperscript')
|
||||
const actions = require('../../actions')
|
||||
const { Menu, Item, Divider, CloseArea } = require('../dropdowns/components/menu')
|
||||
const Identicon = require('../identicon')
|
||||
const { formatBalance } = require('../../util')
|
||||
const { ENVIRONMENT_TYPE_POPUP } = require('../../../../app/scripts/lib/enums')
|
||||
const { getEnvironmentType } = require('../../../../app/scripts/lib/util')
|
||||
const Tooltip = require('../tooltip')
|
||||
|
||||
import Identicon from '../identicon'
|
||||
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
|
||||
import { PRIMARY } from '../../constants/common'
|
||||
|
||||
const {
|
||||
SETTINGS_ROUTE,
|
||||
@ -163,7 +163,6 @@ AccountMenu.prototype.renderAccounts = function () {
|
||||
const isSelected = identity.address === selectedAddress
|
||||
|
||||
const balanceValue = accounts[address] ? accounts[address].balance : ''
|
||||
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6) : '...'
|
||||
const simpleAddress = identity.address.substring(2).toLowerCase()
|
||||
|
||||
const keyring = keyrings.find((kr) => {
|
||||
@ -189,7 +188,11 @@ AccountMenu.prototype.renderAccounts = function () {
|
||||
|
||||
h('div.account-menu__account-info', [
|
||||
h('div.account-menu__name', identity.name || ''),
|
||||
h('div.account-menu__balance', formattedBalance),
|
||||
h(UserPreferencedCurrencyDisplay, {
|
||||
className: 'account-menu__balance',
|
||||
value: balanceValue,
|
||||
type: PRIMARY,
|
||||
}),
|
||||
]),
|
||||
|
||||
this.renderKeyringType(keyring),
|
||||
|
@ -1,7 +1,7 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const Identicon = require('./identicon')
|
||||
import Identicon from './identicon'
|
||||
const formatBalance = require('../util').formatBalance
|
||||
const addressSummary = require('../util').addressSummary
|
||||
|
||||
|
@ -0,0 +1,34 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React, {PureComponent} from 'react'
|
||||
|
||||
export default class AddTokenButton extends PureComponent {
|
||||
static contextTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
onClick: () => {},
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
onClick: PropTypes.func,
|
||||
}
|
||||
|
||||
render () {
|
||||
const { t } = this.context
|
||||
const { onClick } = this.props
|
||||
|
||||
return (
|
||||
<div className="add-token-button">
|
||||
<h1 className="add-token-button__help-header">{t('missingYourTokens')}</h1>
|
||||
<p className="add-token-button__help-desc">{t('clickToAdd', [t('addToken')])}</p>
|
||||
<div
|
||||
className="add-token-button__button"
|
||||
onClick={onClick}
|
||||
>
|
||||
{t('addToken')}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
1
ui/app/components/add-token-button/index.js
Normal file
1
ui/app/components/add-token-button/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './add-token-button.component'
|
26
ui/app/components/add-token-button/index.scss
Normal file
26
ui/app/components/add-token-button/index.scss
Normal file
@ -0,0 +1,26 @@
|
||||
.add-token-button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: lighten($scorpion, 25%);
|
||||
width: 185px;
|
||||
margin: 36px auto;
|
||||
text-align: center;
|
||||
|
||||
&__help-header {
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
&__help-desc {
|
||||
font-size: 0.75rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
&__button {
|
||||
font-size: 0.75rem;
|
||||
margin: 1rem;
|
||||
text-transform: uppercase;
|
||||
color: $curious-blue;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
@ -2,13 +2,13 @@ import React, { PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import { matchPath } from 'react-router-dom'
|
||||
import Identicon from '../identicon'
|
||||
|
||||
const {
|
||||
ENVIRONMENT_TYPE_NOTIFICATION,
|
||||
ENVIRONMENT_TYPE_POPUP,
|
||||
} = require('../../../../app/scripts/lib/enums')
|
||||
const { DEFAULT_ROUTE, INITIALIZE_ROUTE, CONFIRM_TRANSACTION_ROUTE } = require('../../routes')
|
||||
const Identicon = require('../identicon')
|
||||
const NetworkIndicator = require('../network')
|
||||
|
||||
export default class AppHeader extends PureComponent {
|
||||
|
@ -2,12 +2,13 @@ const Component = require('react').Component
|
||||
const connect = require('react-redux').connect
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const TokenBalance = require('./token-balance')
|
||||
const Identicon = require('./identicon')
|
||||
import CurrencyDisplay from './currency-display'
|
||||
const { getAssetImages, conversionRateSelector, getCurrentCurrency} = require('../selectors')
|
||||
import TokenBalance from './token-balance'
|
||||
import Identicon from './identicon'
|
||||
import UserPreferencedCurrencyDisplay from './user-preferenced-currency-display'
|
||||
import { PRIMARY, SECONDARY } from '../constants/common'
|
||||
const { getNativeCurrency, getAssetImages, conversionRateSelector, getCurrentCurrency} = require('../selectors')
|
||||
|
||||
const { formatBalance, generateBalanceObject } = require('../util')
|
||||
const { formatBalance } = require('../util')
|
||||
|
||||
module.exports = connect(mapStateToProps)(BalanceComponent)
|
||||
|
||||
@ -20,6 +21,7 @@ function mapStateToProps (state) {
|
||||
return {
|
||||
account,
|
||||
network,
|
||||
nativeCurrency: getNativeCurrency(state),
|
||||
conversionRate: conversionRateSelector(state),
|
||||
currentCurrency: getCurrentCurrency(state),
|
||||
assetImages: getAssetImages(state),
|
||||
@ -65,10 +67,10 @@ BalanceComponent.prototype.renderTokenBalance = function () {
|
||||
|
||||
BalanceComponent.prototype.renderBalance = function () {
|
||||
const props = this.props
|
||||
const { shorten, account } = props
|
||||
const { account, nativeCurrency } = props
|
||||
const balanceValue = account && account.balance
|
||||
const needsParse = 'needsParse' in props ? props.needsParse : true
|
||||
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6, needsParse) : '...'
|
||||
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6, needsParse, nativeCurrency) : '...'
|
||||
const showFiat = 'showFiat' in props ? props.showFiat : true
|
||||
|
||||
if (formattedBalance === 'None' || formattedBalance === '...') {
|
||||
@ -80,25 +82,21 @@ BalanceComponent.prototype.renderBalance = function () {
|
||||
}
|
||||
|
||||
return h('div.flex-column.balance-display', {}, [
|
||||
h('div.token-amount', {
|
||||
style: {},
|
||||
}, this.getTokenBalance(formattedBalance, shorten)),
|
||||
|
||||
showFiat && h(CurrencyDisplay, {
|
||||
h(UserPreferencedCurrencyDisplay, {
|
||||
className: 'token-amount',
|
||||
value: balanceValue,
|
||||
type: PRIMARY,
|
||||
ethNumberOfDecimals: 3,
|
||||
}),
|
||||
|
||||
showFiat && h(UserPreferencedCurrencyDisplay, {
|
||||
value: balanceValue,
|
||||
type: SECONDARY,
|
||||
ethNumberOfDecimals: 3,
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
||||
BalanceComponent.prototype.getTokenBalance = function (formattedBalance, shorten) {
|
||||
const balanceObj = generateBalanceObject(formattedBalance, shorten ? 1 : 3)
|
||||
|
||||
const balanceValue = shorten ? balanceObj.shortBalance : balanceObj.balance
|
||||
const label = balanceObj.label
|
||||
|
||||
return `${balanceValue} ${label}`
|
||||
}
|
||||
|
||||
BalanceComponent.prototype.getFiatDisplayNumber = function (formattedBalance, conversionRate) {
|
||||
if (formattedBalance === 'None') return formattedBalance
|
||||
if (conversionRate === 0) return 'N/A'
|
||||
|
@ -1,16 +1,19 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
|
||||
import { PRIMARY, SECONDARY } from '../../../constants/common'
|
||||
|
||||
const ConfirmDetailRow = props => {
|
||||
const {
|
||||
label,
|
||||
fiatText,
|
||||
ethText,
|
||||
primaryText,
|
||||
secondaryText,
|
||||
onHeaderClick,
|
||||
fiatTextColor,
|
||||
primaryValueTextColor,
|
||||
headerText,
|
||||
headerTextClassName,
|
||||
value,
|
||||
} = props
|
||||
|
||||
return (
|
||||
@ -25,28 +28,57 @@ const ConfirmDetailRow = props => {
|
||||
>
|
||||
{ headerText }
|
||||
</div>
|
||||
<div
|
||||
className="confirm-detail-row__fiat"
|
||||
style={{ color: fiatTextColor }}
|
||||
>
|
||||
{ fiatText }
|
||||
</div>
|
||||
<div className="confirm-detail-row__eth">
|
||||
{ ethText }
|
||||
</div>
|
||||
{
|
||||
primaryText
|
||||
? (
|
||||
<div
|
||||
className="confirm-detail-row__primary"
|
||||
style={{ color: primaryValueTextColor }}
|
||||
>
|
||||
{ primaryText }
|
||||
</div>
|
||||
) : (
|
||||
<UserPreferencedCurrencyDisplay
|
||||
className="confirm-detail-row__primary"
|
||||
type={PRIMARY}
|
||||
value={value}
|
||||
showEthLogo
|
||||
ethLogoHeight="18"
|
||||
style={{ color: primaryValueTextColor }}
|
||||
hideLabel
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
secondaryText
|
||||
? (
|
||||
<div className="confirm-detail-row__secondary">
|
||||
{ secondaryText }
|
||||
</div>
|
||||
) : (
|
||||
<UserPreferencedCurrencyDisplay
|
||||
className="confirm-detail-row__secondary"
|
||||
type={SECONDARY}
|
||||
value={value}
|
||||
showEthLogo
|
||||
hideLabel
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
ConfirmDetailRow.propTypes = {
|
||||
label: PropTypes.string,
|
||||
fiatText: PropTypes.string,
|
||||
ethText: PropTypes.string,
|
||||
fiatTextColor: PropTypes.string,
|
||||
onHeaderClick: PropTypes.func,
|
||||
headerText: PropTypes.string,
|
||||
headerTextClassName: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
onHeaderClick: PropTypes.func,
|
||||
primaryValueTextColor: PropTypes.string,
|
||||
primaryText: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
||||
secondaryText: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
}
|
||||
|
||||
export default ConfirmDetailRow
|
||||
|
@ -18,18 +18,14 @@
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__fiat {
|
||||
&__primary {
|
||||
font-size: 1.5rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
&__eth {
|
||||
&__secondary {
|
||||
color: $oslo-gray;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
&__header-text {
|
||||
|
@ -12,17 +12,19 @@ describe('Confirm Detail Row Component', function () {
|
||||
let wrapper
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<ConfirmDetailRow
|
||||
errorType={'mockErrorType'}
|
||||
label={'mockLabel'}
|
||||
showError={false}
|
||||
fiatText = {'mockFiatText'}
|
||||
ethText = {'mockEthText'}
|
||||
fiatTextColor= {'mockColor'}
|
||||
onHeaderClick= {propsMethodSpies.onHeaderClick}
|
||||
headerText = {'mockHeaderText'}
|
||||
headerTextClassName = {'mockHeaderClass'}
|
||||
/>)
|
||||
wrapper = shallow(
|
||||
<ConfirmDetailRow
|
||||
errorType={'mockErrorType'}
|
||||
label={'mockLabel'}
|
||||
showError={false}
|
||||
primaryText = {'mockFiatText'}
|
||||
secondaryText = {'mockEthText'}
|
||||
primaryValueTextColor= {'mockColor'}
|
||||
onHeaderClick= {propsMethodSpies.onHeaderClick}
|
||||
headerText = {'mockHeaderText'}
|
||||
headerTextClassName = {'mockHeaderClass'}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
describe('render', () => {
|
||||
@ -38,16 +40,16 @@ describe('Confirm Detail Row Component', function () {
|
||||
assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__header-text').childAt(0).text(), 'mockHeaderText')
|
||||
})
|
||||
|
||||
it('should render the fiatText as a child of the confirm-detail-row__fiat', () => {
|
||||
assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__fiat').childAt(0).text(), 'mockFiatText')
|
||||
it('should render the primaryText as a child of the confirm-detail-row__primary', () => {
|
||||
assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__primary').childAt(0).text(), 'mockFiatText')
|
||||
})
|
||||
|
||||
it('should render the ethText as a child of the confirm-detail-row__eth', () => {
|
||||
assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__eth').childAt(0).text(), 'mockEthText')
|
||||
it('should render the ethText as a child of the confirm-detail-row__secondary', () => {
|
||||
assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__secondary').childAt(0).text(), 'mockEthText')
|
||||
})
|
||||
|
||||
it('should set the fiatTextColor on confirm-detail-row__fiat', () => {
|
||||
assert.equal(wrapper.find('.confirm-detail-row__fiat').props().style.color, 'mockColor')
|
||||
it('should set the fiatTextColor on confirm-detail-row__primary', () => {
|
||||
assert.equal(wrapper.find('.confirm-detail-row__primary').props().style.color, 'mockColor')
|
||||
})
|
||||
|
||||
it('should assure the confirm-detail-row__header-text classname is correct', () => {
|
||||
@ -58,7 +60,5 @@ describe('Confirm Detail Row Component', function () {
|
||||
wrapper.find('.confirm-detail-row__header-text').props().onClick()
|
||||
assert.equal(assert.equal(propsMethodSpies.onHeaderClick.callCount, 1))
|
||||
})
|
||||
|
||||
|
||||
})
|
||||
})
|
||||
|
@ -17,9 +17,10 @@ export default class ConfirmPageContainerContent extends Component {
|
||||
nonce: PropTypes.string,
|
||||
assetImage: PropTypes.string,
|
||||
subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
subtitleComponent: PropTypes.node,
|
||||
summaryComponent: PropTypes.node,
|
||||
title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
titleComponent: PropTypes.func,
|
||||
titleComponent: PropTypes.node,
|
||||
warning: PropTypes.string,
|
||||
}
|
||||
|
||||
@ -54,7 +55,9 @@ export default class ConfirmPageContainerContent extends Component {
|
||||
errorKey,
|
||||
errorMessage,
|
||||
title,
|
||||
titleComponent,
|
||||
subtitle,
|
||||
subtitleComponent,
|
||||
hideSubtitle,
|
||||
identiconAddress,
|
||||
nonce,
|
||||
@ -80,7 +83,9 @@ export default class ConfirmPageContainerContent extends Component {
|
||||
})}
|
||||
action={action}
|
||||
title={title}
|
||||
titleComponent={titleComponent}
|
||||
subtitle={subtitle}
|
||||
subtitleComponent={subtitleComponent}
|
||||
hideSubtitle={hideSubtitle}
|
||||
identiconAddress={identiconAddress}
|
||||
nonce={nonce}
|
||||
|
@ -4,7 +4,18 @@ import classnames from 'classnames'
|
||||
import Identicon from '../../../identicon'
|
||||
|
||||
const ConfirmPageContainerSummary = props => {
|
||||
const { action, title, subtitle, hideSubtitle, className, identiconAddress, nonce, assetImage } = props
|
||||
const {
|
||||
action,
|
||||
title,
|
||||
titleComponent,
|
||||
subtitle,
|
||||
subtitleComponent,
|
||||
hideSubtitle,
|
||||
className,
|
||||
identiconAddress,
|
||||
nonce,
|
||||
assetImage,
|
||||
} = props
|
||||
|
||||
return (
|
||||
<div className={classnames('confirm-page-container-summary', className)}>
|
||||
@ -32,12 +43,12 @@ const ConfirmPageContainerSummary = props => {
|
||||
)
|
||||
}
|
||||
<div className="confirm-page-container-summary__title-text">
|
||||
{ title }
|
||||
{ titleComponent || title }
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
hideSubtitle || <div className="confirm-page-container-summary__subtitle">
|
||||
{ subtitle }
|
||||
{ subtitleComponent || subtitle }
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@ -47,7 +58,9 @@ const ConfirmPageContainerSummary = props => {
|
||||
ConfirmPageContainerSummary.propTypes = {
|
||||
action: PropTypes.string,
|
||||
title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
titleComponent: PropTypes.node,
|
||||
subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
subtitleComponent: PropTypes.node,
|
||||
hideSubtitle: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
identiconAddress: PropTypes.string,
|
||||
|
@ -16,8 +16,9 @@ export default class ConfirmPageContainer extends Component {
|
||||
onEdit: PropTypes.func,
|
||||
showEdit: PropTypes.bool,
|
||||
subtitle: PropTypes.string,
|
||||
subtitleComponent: PropTypes.node,
|
||||
title: PropTypes.string,
|
||||
titleComponent: PropTypes.func,
|
||||
titleComponent: PropTypes.node,
|
||||
// Sender to Recipient
|
||||
fromAddress: PropTypes.string,
|
||||
fromName: PropTypes.string,
|
||||
@ -65,6 +66,7 @@ export default class ConfirmPageContainer extends Component {
|
||||
title,
|
||||
titleComponent,
|
||||
subtitle,
|
||||
subtitleComponent,
|
||||
hideSubtitle,
|
||||
summaryComponent,
|
||||
detailsComponent,
|
||||
@ -101,6 +103,7 @@ export default class ConfirmPageContainer extends Component {
|
||||
title={title}
|
||||
titleComponent={titleComponent}
|
||||
subtitle={subtitle}
|
||||
subtitleComponent={subtitleComponent}
|
||||
hideSubtitle={hideSubtitle}
|
||||
summaryComponent={summaryComponent}
|
||||
detailsComponent={detailsComponent}
|
||||
|
@ -1,14 +1,18 @@
|
||||
import React, { PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { ETH, GWEI } from '../../constants/common'
|
||||
import classnames from 'classnames'
|
||||
import { GWEI } from '../../constants/common'
|
||||
|
||||
export default class CurrencyDisplay extends PureComponent {
|
||||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
displayValue: PropTypes.string,
|
||||
prefix: PropTypes.string,
|
||||
prefixComponent: PropTypes.node,
|
||||
style: PropTypes.object,
|
||||
suffix: PropTypes.string,
|
||||
// Used in container
|
||||
currency: PropTypes.oneOf([ETH]),
|
||||
currency: PropTypes.string,
|
||||
denomination: PropTypes.oneOf([GWEI]),
|
||||
value: PropTypes.string,
|
||||
numberOfDecimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
@ -16,15 +20,25 @@ export default class CurrencyDisplay extends PureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { className, displayValue, prefix } = this.props
|
||||
const { className, displayValue, prefix, prefixComponent, style, suffix } = this.props
|
||||
const text = `${prefix || ''}${displayValue}`
|
||||
const title = `${text} ${suffix}`
|
||||
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
title={text}
|
||||
className={classnames('currency-display-component', className)}
|
||||
style={style}
|
||||
title={title}
|
||||
>
|
||||
{ text }
|
||||
{ prefixComponent}
|
||||
<span className="currency-display-component__text">{ text }</span>
|
||||
{
|
||||
suffix && (
|
||||
<span className="currency-display-component__suffix">
|
||||
{ suffix }
|
||||
</span>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -2,20 +2,41 @@ import { connect } from 'react-redux'
|
||||
import CurrencyDisplay from './currency-display.component'
|
||||
import { getValueFromWeiHex, formatCurrency } from '../../helpers/confirm-transaction/util'
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const { value, numberOfDecimals = 2, currency, denomination, hideLabel } = ownProps
|
||||
const { metamask: { currentCurrency, conversionRate } } = state
|
||||
|
||||
const toCurrency = currency || currentCurrency
|
||||
const convertedValue = getValueFromWeiHex({
|
||||
value, toCurrency, conversionRate, numberOfDecimals, toDenomination: denomination,
|
||||
})
|
||||
const formattedValue = formatCurrency(convertedValue, toCurrency)
|
||||
const displayValue = hideLabel ? formattedValue : `${formattedValue} ${toCurrency.toUpperCase()}`
|
||||
const mapStateToProps = state => {
|
||||
const { metamask: { nativeCurrency, currentCurrency, conversionRate } } = state
|
||||
|
||||
return {
|
||||
displayValue,
|
||||
currentCurrency,
|
||||
conversionRate,
|
||||
nativeCurrency,
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(CurrencyDisplay)
|
||||
const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
||||
const { nativeCurrency, currentCurrency, conversionRate, ...restStateProps } = stateProps
|
||||
const {
|
||||
value,
|
||||
numberOfDecimals = 2,
|
||||
currency,
|
||||
denomination,
|
||||
hideLabel,
|
||||
...restOwnProps
|
||||
} = ownProps
|
||||
|
||||
const toCurrency = currency || currentCurrency
|
||||
const convertedValue = getValueFromWeiHex({
|
||||
value, fromCurrency: nativeCurrency, toCurrency, conversionRate, numberOfDecimals, toDenomination: denomination,
|
||||
})
|
||||
const displayValue = formatCurrency(convertedValue, toCurrency)
|
||||
const suffix = hideLabel ? undefined : toCurrency.toUpperCase()
|
||||
|
||||
return {
|
||||
...restStateProps,
|
||||
...dispatchProps,
|
||||
...restOwnProps,
|
||||
displayValue,
|
||||
suffix,
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null, mergeProps)(CurrencyDisplay)
|
||||
|
14
ui/app/components/currency-display/index.scss
Normal file
14
ui/app/components/currency-display/index.scss
Normal file
@ -0,0 +1,14 @@
|
||||
.currency-display-component {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&__text {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&__suffix {
|
||||
padding-left: 4px;
|
||||
}
|
||||
}
|
@ -1,12 +1,13 @@
|
||||
import assert from 'assert'
|
||||
import proxyquire from 'proxyquire'
|
||||
|
||||
let mapStateToProps
|
||||
let mapStateToProps, mergeProps
|
||||
|
||||
proxyquire('../currency-display.container.js', {
|
||||
'react-redux': {
|
||||
connect: ms => {
|
||||
connect: (ms, md, mp) => {
|
||||
mapStateToProps = ms
|
||||
mergeProps = mp
|
||||
return () => ({})
|
||||
},
|
||||
},
|
||||
@ -19,86 +20,125 @@ describe('CurrencyDisplay container', () => {
|
||||
metamask: {
|
||||
conversionRate: 280.45,
|
||||
currentCurrency: 'usd',
|
||||
nativeCurrency: 'ETH',
|
||||
},
|
||||
}
|
||||
|
||||
assert.deepEqual(mapStateToProps(mockState), {
|
||||
conversionRate: 280.45,
|
||||
currentCurrency: 'usd',
|
||||
nativeCurrency: 'ETH',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('mergeProps()', () => {
|
||||
it('should return the correct props', () => {
|
||||
const mockStateProps = {
|
||||
conversionRate: 280.45,
|
||||
currentCurrency: 'usd',
|
||||
nativeCurrency: 'ETH',
|
||||
}
|
||||
|
||||
const tests = [
|
||||
{
|
||||
props: {
|
||||
value: '0x2386f26fc10000',
|
||||
numberOfDecimals: 2,
|
||||
currency: 'usd',
|
||||
nativeCurrency: 'ETH',
|
||||
},
|
||||
result: {
|
||||
displayValue: '$2.80 USD',
|
||||
displayValue: '$2.80',
|
||||
suffix: 'USD',
|
||||
nativeCurrency: 'ETH',
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
value: '0x2386f26fc10000',
|
||||
currency: 'usd',
|
||||
nativeCurrency: 'ETH',
|
||||
},
|
||||
result: {
|
||||
displayValue: '$2.80 USD',
|
||||
displayValue: '$2.80',
|
||||
suffix: 'USD',
|
||||
nativeCurrency: 'ETH',
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
value: '0x1193461d01595930',
|
||||
currency: 'ETH',
|
||||
nativeCurrency: 'ETH',
|
||||
numberOfDecimals: 3,
|
||||
},
|
||||
result: {
|
||||
displayValue: '1.266 ETH',
|
||||
displayValue: '1.266',
|
||||
suffix: 'ETH',
|
||||
nativeCurrency: 'ETH',
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
value: '0x1193461d01595930',
|
||||
currency: 'ETH',
|
||||
nativeCurrency: 'ETH',
|
||||
numberOfDecimals: 3,
|
||||
hideLabel: true,
|
||||
},
|
||||
result: {
|
||||
nativeCurrency: 'ETH',
|
||||
displayValue: '1.266',
|
||||
suffix: undefined,
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
value: '0x3b9aca00',
|
||||
currency: 'ETH',
|
||||
nativeCurrency: 'ETH',
|
||||
denomination: 'GWEI',
|
||||
hideLabel: true,
|
||||
},
|
||||
result: {
|
||||
nativeCurrency: 'ETH',
|
||||
displayValue: '1',
|
||||
suffix: undefined,
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
value: '0x3b9aca00',
|
||||
currency: 'ETH',
|
||||
nativeCurrency: 'ETH',
|
||||
denomination: 'WEI',
|
||||
hideLabel: true,
|
||||
},
|
||||
result: {
|
||||
nativeCurrency: 'ETH',
|
||||
displayValue: '1000000000',
|
||||
suffix: undefined,
|
||||
},
|
||||
},
|
||||
{
|
||||
props: {
|
||||
value: '0x3b9aca00',
|
||||
currency: 'ETH',
|
||||
nativeCurrency: 'ETH',
|
||||
numberOfDecimals: 100,
|
||||
hideLabel: true,
|
||||
},
|
||||
result: {
|
||||
nativeCurrency: 'ETH',
|
||||
displayValue: '1e-9',
|
||||
suffix: undefined,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
tests.forEach(({ props, result }) => {
|
||||
assert.deepEqual(mapStateToProps(mockState, props), result)
|
||||
assert.deepEqual(mergeProps(mockStateProps, {}, { ...props }), result)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
121
ui/app/components/currency-input/currency-input.component.js
Normal file
121
ui/app/components/currency-input/currency-input.component.js
Normal file
@ -0,0 +1,121 @@
|
||||
import React, { PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import UnitInput from '../unit-input'
|
||||
import CurrencyDisplay from '../currency-display'
|
||||
import { getValueFromWeiHex, getWeiHexFromDecimalValue } from '../../helpers/conversions.util'
|
||||
import { ETH } from '../../constants/common'
|
||||
|
||||
/**
|
||||
* Component that allows user to enter currency values as a number, and props receive a converted
|
||||
* hex value in WEI. props.value, used as a default or forced value, should be a hex value, which
|
||||
* gets converted into a decimal value depending on the currency (ETH or Fiat).
|
||||
*/
|
||||
export default class CurrencyInput extends PureComponent {
|
||||
static propTypes = {
|
||||
conversionRate: PropTypes.number,
|
||||
currentCurrency: PropTypes.string,
|
||||
nativeCurrency: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
onBlur: PropTypes.func,
|
||||
suffix: PropTypes.string,
|
||||
useFiat: PropTypes.bool,
|
||||
value: PropTypes.string,
|
||||
}
|
||||
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
const { value: hexValue } = props
|
||||
const decimalValue = hexValue ? this.getDecimalValue(props) : 0
|
||||
|
||||
this.state = {
|
||||
decimalValue,
|
||||
hexValue,
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
const { value: prevPropsHexValue } = prevProps
|
||||
const { value: propsHexValue } = this.props
|
||||
const { hexValue: stateHexValue } = this.state
|
||||
|
||||
if (prevPropsHexValue !== propsHexValue && propsHexValue !== stateHexValue) {
|
||||
const decimalValue = this.getDecimalValue(this.props)
|
||||
this.setState({ hexValue: propsHexValue, decimalValue })
|
||||
}
|
||||
}
|
||||
|
||||
getDecimalValue (props) {
|
||||
const { value: hexValue, useFiat, currentCurrency, conversionRate } = props
|
||||
const decimalValueString = useFiat
|
||||
? getValueFromWeiHex({
|
||||
value: hexValue, toCurrency: currentCurrency, conversionRate, numberOfDecimals: 2,
|
||||
})
|
||||
: getValueFromWeiHex({
|
||||
value: hexValue, toCurrency: ETH, numberOfDecimals: 6,
|
||||
})
|
||||
|
||||
return Number(decimalValueString) || 0
|
||||
}
|
||||
|
||||
handleChange = decimalValue => {
|
||||
const { useFiat, currentCurrency: fromCurrency, conversionRate, onChange } = this.props
|
||||
|
||||
const hexValue = useFiat
|
||||
? getWeiHexFromDecimalValue({
|
||||
value: decimalValue, fromCurrency, conversionRate, invertConversionRate: true,
|
||||
})
|
||||
: getWeiHexFromDecimalValue({
|
||||
value: decimalValue, fromCurrency: ETH, fromDenomination: ETH, conversionRate,
|
||||
})
|
||||
|
||||
this.setState({ hexValue, decimalValue })
|
||||
onChange(hexValue)
|
||||
}
|
||||
|
||||
handleBlur = () => {
|
||||
this.props.onBlur(this.state.hexValue)
|
||||
}
|
||||
|
||||
renderConversionComponent () {
|
||||
const { useFiat, currentCurrency, nativeCurrency } = this.props
|
||||
const { hexValue } = this.state
|
||||
let currency, numberOfDecimals
|
||||
|
||||
if (useFiat) {
|
||||
// Display ETH
|
||||
currency = nativeCurrency || ETH
|
||||
numberOfDecimals = 6
|
||||
} else {
|
||||
// Display Fiat
|
||||
currency = currentCurrency
|
||||
numberOfDecimals = 2
|
||||
}
|
||||
|
||||
return (
|
||||
<CurrencyDisplay
|
||||
className="currency-input__conversion-component"
|
||||
currency={currency}
|
||||
value={hexValue}
|
||||
numberOfDecimals={numberOfDecimals}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { suffix, ...restProps } = this.props
|
||||
const { decimalValue } = this.state
|
||||
|
||||
return (
|
||||
<UnitInput
|
||||
{...restProps}
|
||||
suffix={suffix}
|
||||
onChange={this.handleChange}
|
||||
onBlur={this.handleBlur}
|
||||
value={decimalValue}
|
||||
>
|
||||
{ this.renderConversionComponent() }
|
||||
</UnitInput>
|
||||
)
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user