mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 09:57:02 +01:00
Merge tag 'v4.5.5'
# Conflicts: # app/_locales/ja/messages.json # package-lock.json messages.jsonのローカライズ
This commit is contained in:
commit
cc246528b5
@ -6,12 +6,28 @@ workflows:
|
||||
jobs:
|
||||
- prep-deps-npm
|
||||
- prep-deps-firefox
|
||||
- prep-build:
|
||||
requires:
|
||||
- prep-deps-npm
|
||||
- prep-scss:
|
||||
requires:
|
||||
- prep-deps-npm
|
||||
- test-lint:
|
||||
requires:
|
||||
- prep-deps-npm
|
||||
- test-e2e:
|
||||
requires:
|
||||
- prep-deps-npm
|
||||
- prep-build
|
||||
- job-screens:
|
||||
requires:
|
||||
- prep-deps-npm
|
||||
- prep-build
|
||||
- job-publish:
|
||||
requires:
|
||||
- prep-deps-npm
|
||||
- prep-build
|
||||
- job-screens
|
||||
- test-unit:
|
||||
requires:
|
||||
- prep-deps-npm
|
||||
@ -33,6 +49,16 @@ workflows:
|
||||
- prep-deps-npm
|
||||
- prep-deps-firefox
|
||||
- prep-scss
|
||||
- all-tests-pass:
|
||||
requires:
|
||||
- test-lint
|
||||
- test-unit
|
||||
- test-e2e
|
||||
- job-screens
|
||||
- test-integration-mascara-chrome
|
||||
- test-integration-mascara-firefox
|
||||
- test-integration-flat-chrome
|
||||
- test-integration-flat-firefox
|
||||
|
||||
jobs:
|
||||
prep-deps-npm:
|
||||
@ -41,7 +67,7 @@ jobs:
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: dependency-cache-{{ checksum "package-lock.json" }}
|
||||
key: dependency-cache-{{ .Revision }}
|
||||
- run:
|
||||
name: Install deps via npm
|
||||
command: npm install
|
||||
@ -49,6 +75,10 @@ jobs:
|
||||
key: dependency-cache-{{ checksum "package-lock.json" }}
|
||||
paths:
|
||||
- node_modules
|
||||
- save_cache:
|
||||
key: dependency-cache-{{ .Revision }}
|
||||
paths:
|
||||
- node_modules
|
||||
|
||||
prep-deps-firefox:
|
||||
docker:
|
||||
@ -65,6 +95,24 @@ jobs:
|
||||
paths:
|
||||
- firefox
|
||||
|
||||
prep-build:
|
||||
docker:
|
||||
- image: circleci/node:8-browsers
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: dependency-cache-{{ .Revision }}
|
||||
- run:
|
||||
name: build:dist
|
||||
command: npm run dist
|
||||
- run:
|
||||
name: build:debug
|
||||
command: find dist/ -type f -exec md5sum {} \; | sort -k 2
|
||||
- save_cache:
|
||||
key: build-cache-{{ .Revision }}
|
||||
paths:
|
||||
- dist
|
||||
- builds
|
||||
|
||||
prep-scss:
|
||||
docker:
|
||||
@ -72,7 +120,7 @@ jobs:
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: dependency-cache-{{ checksum "package-lock.json" }}
|
||||
key: dependency-cache-{{ .Revision }}
|
||||
- run:
|
||||
name: Get Scss Cache key
|
||||
# this allows us to checksum against a whole directory
|
||||
@ -91,18 +139,81 @@ jobs:
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: dependency-cache-{{ checksum "package-lock.json" }}
|
||||
key: dependency-cache-{{ .Revision }}
|
||||
- run:
|
||||
name: Test
|
||||
command: npm run lint
|
||||
|
||||
test-e2e:
|
||||
docker:
|
||||
- image: circleci/node:8-browsers
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: dependency-cache-{{ .Revision }}
|
||||
- restore_cache:
|
||||
key: build-cache-{{ .Revision }}
|
||||
- run:
|
||||
name: Test
|
||||
command: npm run test:e2e
|
||||
- store_artifacts:
|
||||
path: test-artifacts
|
||||
destination: test-artifacts
|
||||
|
||||
job-screens:
|
||||
docker:
|
||||
- image: circleci/node:8-browsers
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: dependency-cache-{{ .Revision }}
|
||||
- restore_cache:
|
||||
key: build-cache-{{ .Revision }}
|
||||
- run:
|
||||
name: Test
|
||||
command: npm run test:screens
|
||||
- save_cache:
|
||||
key: job-screens-{{ .Revision }}
|
||||
paths:
|
||||
- test-artifacts
|
||||
|
||||
job-publish:
|
||||
docker:
|
||||
- image: circleci/node:8-browsers
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: dependency-cache-{{ .Revision }}
|
||||
- restore_cache:
|
||||
key: build-cache-{{ .Revision }}
|
||||
- restore_cache:
|
||||
key: job-screens-{{ .Revision }}
|
||||
- store_artifacts:
|
||||
path: dist/mascara
|
||||
destination: builds/mascara
|
||||
- store_artifacts:
|
||||
path: dist/sourcemaps
|
||||
destination: builds/sourcemaps
|
||||
- store_artifacts:
|
||||
path: builds
|
||||
destination: builds
|
||||
- store_artifacts:
|
||||
path: test-artifacts
|
||||
destination: test-artifacts
|
||||
- run:
|
||||
name: build:announce
|
||||
command: ./development/metamaskbot-build-announce.js
|
||||
- run:
|
||||
name: sentry sourcemaps upload
|
||||
command: npm run sentry:publish
|
||||
|
||||
test-unit:
|
||||
docker:
|
||||
- image: circleci/node:8-browsers
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: dependency-cache-{{ checksum "package-lock.json" }}
|
||||
key: dependency-cache-{{ .Revision }}
|
||||
- run:
|
||||
name: test:coverage
|
||||
command: npm run test:coverage
|
||||
@ -124,7 +235,7 @@ jobs:
|
||||
&& sudo mv /usr/bin/firefox /usr/bin/firefox-old
|
||||
&& sudo ln -s /opt/firefox58/firefox /usr/bin/firefox
|
||||
- restore_cache:
|
||||
key: dependency-cache-{{ checksum "package-lock.json" }}
|
||||
key: dependency-cache-{{ .Revision }}
|
||||
- run:
|
||||
name: Get Scss Cache key
|
||||
# this allows us to checksum against a whole directory
|
||||
@ -143,7 +254,7 @@ jobs:
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: dependency-cache-{{ checksum "package-lock.json" }}
|
||||
key: dependency-cache-{{ .Revision }}
|
||||
- run:
|
||||
name: Get Scss Cache key
|
||||
# this allows us to checksum against a whole directory
|
||||
@ -171,7 +282,7 @@ jobs:
|
||||
&& sudo mv /usr/bin/firefox /usr/bin/firefox-old
|
||||
&& sudo ln -s /opt/firefox58/firefox /usr/bin/firefox
|
||||
- restore_cache:
|
||||
key: dependency-cache-{{ checksum "package-lock.json" }}
|
||||
key: dependency-cache-{{ .Revision }}
|
||||
- run:
|
||||
name: Get Scss Cache key
|
||||
# this allows us to checksum against a whole directory
|
||||
@ -190,7 +301,7 @@ jobs:
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: dependency-cache-{{ checksum "package-lock.json" }}
|
||||
key: dependency-cache-{{ .Revision }}
|
||||
- run:
|
||||
name: Get Scss Cache key
|
||||
# this allows us to checksum against a whole directory
|
||||
@ -200,3 +311,11 @@ jobs:
|
||||
- run:
|
||||
name: test:integration:mascara
|
||||
command: npm run test:mascara
|
||||
|
||||
all-tests-pass:
|
||||
docker:
|
||||
- image: circleci/node:8-browsers
|
||||
steps:
|
||||
- run:
|
||||
name: All Tests Passed
|
||||
command: echo 'weew - everything passed!'
|
||||
|
@ -29,7 +29,8 @@
|
||||
"plugins": [
|
||||
"mocha",
|
||||
"chai",
|
||||
"react"
|
||||
"react",
|
||||
"json"
|
||||
],
|
||||
|
||||
"globals": {
|
||||
@ -41,6 +42,7 @@
|
||||
},
|
||||
|
||||
"rules": {
|
||||
"no-restricted-globals": ["error", "event"],
|
||||
"accessor-pairs": 2,
|
||||
"arrow-spacing": [2, { "before": true, "after": true }],
|
||||
"block-spacing": [2, "always"],
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -28,6 +28,8 @@ test/background.js
|
||||
test/bundle.js
|
||||
test/test-bundle.js
|
||||
|
||||
test-artifacts
|
||||
|
||||
#ignore css output and sourcemaps
|
||||
ui/app/css/output/
|
||||
|
||||
|
36
CHANGELOG.md
36
CHANGELOG.md
@ -2,6 +2,42 @@
|
||||
|
||||
## Current Master
|
||||
|
||||
## 4.5.5 Fri Apr 06 2018
|
||||
|
||||
- Graceful handling of unknown keys in txParams
|
||||
- Fixes buggy handling of historical transactions with unknown keys in txParams
|
||||
- Fix link for 'Learn More' in the Add Token Screen to open to a new tab.
|
||||
- Fix Download State Logs button [#3791](https://github.com/MetaMask/metamask-extension/issues/3791)
|
||||
- Enhanced migration error handling + reporting
|
||||
|
||||
## 4.5.4 (aborted) Thu Apr 05 2018
|
||||
|
||||
- Graceful handling of unknown keys in txParams
|
||||
- Fix link for 'Learn More' in the Add Token Screen to open to a new tab.
|
||||
- Fix Download State Logs button [#3791](https://github.com/MetaMask/metamask-extension/issues/3791)
|
||||
- Fix migration error reporting
|
||||
|
||||
## 4.5.3 Wed Apr 04 2018
|
||||
|
||||
- Fix bug where checksum address are messing with balance issue [#3843](https://github.com/MetaMask/metamask-extension/issues/3843)
|
||||
- new ui: fix the confirm transaction screen
|
||||
|
||||
## 4.5.2 Wed Apr 04 2018
|
||||
|
||||
- Fix overly strict validation where transactions were rejected with hex encoded "chainId"
|
||||
|
||||
## 4.5.1 Tue Apr 03 2018
|
||||
|
||||
- Fix default network (should be mainnet not Rinkeby)
|
||||
- Fix Sentry automated error reporting endpoint
|
||||
|
||||
## 4.5.0 Mon Apr 02 2018
|
||||
|
||||
- (beta ui) Internationalization: Select your preferred language in the settings screen
|
||||
- Internationalization: various locale improvements
|
||||
- Fix bug where the "Reset account" feature would not clear the network cache.
|
||||
- Increase maximum gas limit, to allow very gas heavy transactions, since block gas limits have been stable.
|
||||
|
||||
## 4.4.0 Mon Mar 26 2018
|
||||
|
||||
- Internationalization: Taiwanese, Thai, Slovenian
|
||||
|
@ -14,9 +14,15 @@
|
||||
"address": {
|
||||
"message": "Adresse"
|
||||
},
|
||||
"addCustomToken": {
|
||||
"message": "Eigenen Token hinzufügen"
|
||||
},
|
||||
"addToken": {
|
||||
"message": "Token hinzufügen"
|
||||
},
|
||||
"addTokens": {
|
||||
"message": "Token hinzufügen"
|
||||
},
|
||||
"amount": {
|
||||
"message": "Betrag"
|
||||
},
|
||||
@ -31,9 +37,15 @@
|
||||
"message": "MetaMask",
|
||||
"description": "Der Name der Erweiterung"
|
||||
},
|
||||
"approved": {
|
||||
"message": "Genehmigt"
|
||||
},
|
||||
"attemptingConnect": {
|
||||
"message": "Versuch mit der Blockchain zu verbinden."
|
||||
},
|
||||
"attributions": {
|
||||
"message": "Was wir verwenden"
|
||||
},
|
||||
"available": {
|
||||
"message": "Verfügbar"
|
||||
},
|
||||
@ -43,6 +55,9 @@
|
||||
"balance": {
|
||||
"message": "Guthaben:"
|
||||
},
|
||||
"balances": {
|
||||
"message": "Deine Guthaben"
|
||||
},
|
||||
"balanceIsInsufficientGas": {
|
||||
"message": "Guthaben unzureichend für den aktuellen gesamten Gasbetrag"
|
||||
},
|
||||
@ -69,7 +84,7 @@
|
||||
"message": "Auf Coinbase kaufen"
|
||||
},
|
||||
"buyCoinbaseExplainer": {
|
||||
"message": "Coinbase ist die weltweit bekannteste Möglichkeit bitcoin, ethereum und litecoin zu kaufen und verkaufen."
|
||||
"message": "Coinbase ist die weltweit bekannteste Art und Weise um bitcoin, ethereum und litecoin zu kaufen und verkaufen."
|
||||
},
|
||||
"ok": {
|
||||
"message": "Ok"
|
||||
@ -105,7 +120,7 @@
|
||||
"message": "Zu Coinbase fortfahren"
|
||||
},
|
||||
"contractDeployment": {
|
||||
"message": "Smart Contract ausführen"
|
||||
"message": "Smart Contract Ausführung"
|
||||
},
|
||||
"conversionProgress": {
|
||||
"message": "Umtausch in Arbeit"
|
||||
@ -148,7 +163,7 @@
|
||||
"description": "Börsentyp (Kryptowährungen)"
|
||||
},
|
||||
"currentConversion": {
|
||||
"message": "Aktueller Umtausch"
|
||||
"message": "Aktuelle Tauschwährung"
|
||||
},
|
||||
"currentNetwork": {
|
||||
"message": "Aktuelles Netzwerk"
|
||||
@ -185,7 +200,7 @@
|
||||
"description": "Teilt dem Benutzer mit welchen Token er beim Einzahlen mit Shapeshift ausgewählt hat"
|
||||
},
|
||||
"depositEth": {
|
||||
"message": "Eth einzahlen"
|
||||
"message": "Eth kaufen"
|
||||
},
|
||||
"depositEther": {
|
||||
"message": "Ether einzahlen"
|
||||
@ -217,7 +232,7 @@
|
||||
"done": {
|
||||
"message": "Fertig"
|
||||
},
|
||||
"downloadStatelogs": {
|
||||
"downloadStateLogs": {
|
||||
"message": "Statelogs herunterladen"
|
||||
},
|
||||
"dropped": {
|
||||
@ -274,7 +289,7 @@
|
||||
"message": "Folge uns auf Twitter"
|
||||
},
|
||||
"from": {
|
||||
"message": "von"
|
||||
"message": "Von"
|
||||
},
|
||||
"fromToSame": {
|
||||
"message": "Ziel- und Ursprungsadresse dürfen nicht identisch sein"
|
||||
@ -347,14 +362,14 @@
|
||||
"message": "Es erlaubt dir ether & Token zu halten und dient dir als Verbindung zu dezentralisierten Applikationen."
|
||||
},
|
||||
"import": {
|
||||
"message": "Import",
|
||||
"message": "Importieren",
|
||||
"description": "Button um den Account aus einer ausgewählten Datei zu importieren"
|
||||
},
|
||||
"importAccount": {
|
||||
"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"
|
||||
@ -369,7 +384,7 @@
|
||||
"infoHelp": {
|
||||
"message": "Info & Hilfe"
|
||||
},
|
||||
"insufficientFunds": {
|
||||
"insufficientFunds": {
|
||||
"message": "Nicht genügend Guthaben."
|
||||
},
|
||||
"insufficientTokens": {
|
||||
@ -441,10 +456,10 @@
|
||||
"message": "Frei"
|
||||
},
|
||||
"loweCaseWords": {
|
||||
"message": "Die Wörter der Seed Wörterfolgen sind alle kleingeschrieben"
|
||||
"message": "Die Wörter der Seed-Wörterfolgen sind alle kleingeschrieben"
|
||||
},
|
||||
"mainnet": {
|
||||
"message": "Ethereum Hauptnetzwerk (Main Net)"
|
||||
"message": "Ethereum Main Net"
|
||||
},
|
||||
"message": {
|
||||
"message": "Nachricht"
|
||||
@ -541,7 +556,7 @@
|
||||
"description": "Für den Import eine Accounts mit Hilfe eines Private Keys"
|
||||
},
|
||||
"pasteSeed": {
|
||||
"message": "Füge deine Seed Wörterfolge hier ein!"
|
||||
"message": "Füge deine Seed-Wörterfolge hier ein!"
|
||||
},
|
||||
"personalAddressDetected": {
|
||||
"message": "Personalisierte Adresse identifiziert. Bitte füge die Token Contract Adresse ein."
|
||||
@ -557,7 +572,7 @@
|
||||
"description": "Wähle diesen Dateityp um damit einen Account zu importieren"
|
||||
},
|
||||
"privateKeyWarning": {
|
||||
"message": "Warnung: Niemals jemanden deinen Private Key mitteilen. Jeder der im Besitz deines Private Keys ist, kann jegliches Guthaben deines Accounts stehlen."
|
||||
"message": "Warnung: Niemals jemanden deinen Private Key mitteilen. Jeder der im Besitz deines Private Keys ist, kann jegliches Guthaben deines Accounts stehlen."
|
||||
},
|
||||
"privateNetwork": {
|
||||
"message": "Privates Netzwerk"
|
||||
@ -566,7 +581,7 @@
|
||||
"message": "QR Code anzeigen"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "Du kannst diesen Token zukünftig wieder hinzufügen indem du in den Menüpunkt \"Token hinzufügen\" in den Einstellungen deines Accounts gehst."
|
||||
"message": "Du kannst diesen Token immer erneut hinzufügen, indem du in den Menüpunkt \"Token hinzufügen\" in den Einstellungen deines Accounts gehst."
|
||||
},
|
||||
"readMore": {
|
||||
"message": "Hier mehr erfahren."
|
||||
@ -590,7 +605,7 @@
|
||||
"message": "Account zurücksetzten"
|
||||
},
|
||||
"restoreFromSeed": {
|
||||
"message": "Mit Hilfe der Seed Wörterfolge wiederherstellen."
|
||||
"message": "Mit Hilfe der Seed-Wörterfolge wiederherstellen."
|
||||
},
|
||||
"restoreVault": {
|
||||
"message": "Vault wiederherstellen"
|
||||
@ -605,13 +620,13 @@
|
||||
"message": "Wallet Seed"
|
||||
},
|
||||
"revealSeedWords": {
|
||||
"message": "Seed Wörterfolge anzeigen"
|
||||
"message": "Seed-Wörterfolge anzeigen"
|
||||
},
|
||||
"revealSeedWordsWarning": {
|
||||
"message": "Bitte niemals deine Seed Wörterfolge an einem öffentlichen Ort kenntlich machen. Mit diesen Wörtern können alle deine Accounts gestohlen werden."
|
||||
"message": "Bitte niemals deine Seed-Wörterfolge an einem öffentlichen Ort kenntlich machen. Mit diesen Wörtern können alle deine Accounts gestohlen werden."
|
||||
},
|
||||
"revert": {
|
||||
"message": "Zurück gehen"
|
||||
"message": "Rückgängig machen"
|
||||
},
|
||||
"rinkeby": {
|
||||
"message": "Rinkeby Testnetzwerk"
|
||||
@ -623,7 +638,7 @@
|
||||
"message": "Aktueller RPC"
|
||||
},
|
||||
"connectingToMainnet": {
|
||||
"message": "Verbinde zum Ethereum Hauptnetzwerk (Main Net)"
|
||||
"message": "Verbinde zum Ethereum Main Net"
|
||||
},
|
||||
"connectingToRopsten": {
|
||||
"message": " Verbinde zum Ropsten Testnetzwerk"
|
||||
@ -649,7 +664,7 @@
|
||||
"description": "Prozess des Exportieren eines Accounts"
|
||||
},
|
||||
"saveSeedAsFile": {
|
||||
"message": "Seed Wörterfolge als Datei speichern"
|
||||
"message": "Seed-Wörterfolge als Datei speichern"
|
||||
},
|
||||
"search": {
|
||||
"message": "Suche"
|
||||
@ -661,7 +676,7 @@
|
||||
"message": "Neues Passwort (min. 8 Zeichen)"
|
||||
},
|
||||
"seedPhraseReq": {
|
||||
"message": "Seed Wörterfolgen bestehen aus 12 Wörtern"
|
||||
"message": "Seed-Wörterfolgen bestehen aus 12 Wörtern"
|
||||
},
|
||||
"select": {
|
||||
"message": "Auswählen"
|
||||
@ -685,7 +700,7 @@
|
||||
"message": "Token senden"
|
||||
},
|
||||
"onlySendToEtherAddress": {
|
||||
"message": "ETH nur zu einer Ethereum Adresse senden."
|
||||
"message": "ETH unbedingt nur zu einer Ethereum Adresse senden."
|
||||
},
|
||||
"sendTokensAnywhere": {
|
||||
"message": "Token zu einer beliebigen Person mit einem Ethereumaccount senden"
|
||||
@ -742,7 +757,7 @@
|
||||
"message": "Einreichen"
|
||||
},
|
||||
"submitted": {
|
||||
"message": "Eingereicht"
|
||||
"message": "Abgeschickt"
|
||||
},
|
||||
"supportCenter": {
|
||||
"message": "Gehe zu unserem Support Center"
|
||||
@ -782,7 +797,7 @@
|
||||
"message": "Tokensymbol"
|
||||
},
|
||||
"tokenWarning1": {
|
||||
"message": "Behalte die Token die du mit deinem MetaMask Account gekauft hast im Auge. Wenn du Token mit einem anderen Account gekauft hast, werden diese hier nicht angezeigt."
|
||||
"message": "Behalte die Token die du mit deinem MetaMask Account gekauft hast im Blick. Wenn du Token mit einem anderen Account gekauft hast, werden diese hier nicht angezeigt."
|
||||
},
|
||||
"total": {
|
||||
"message": "Gesamt"
|
||||
@ -853,7 +868,7 @@
|
||||
"message": " Account einsehen"
|
||||
},
|
||||
"visitWebSite": {
|
||||
"message": "Gehe zu unsere Webseite"
|
||||
"message": "Gehe zu unserer Webseite"
|
||||
},
|
||||
"warning": {
|
||||
"message": "Warnung"
|
||||
|
@ -56,7 +56,7 @@
|
||||
"message": "Balance:"
|
||||
},
|
||||
"balances": {
|
||||
"message": "Your balances"
|
||||
"message": "Token balance(s)"
|
||||
},
|
||||
"balanceIsInsufficientGas": {
|
||||
"message": "Insufficient balance for current gas total"
|
||||
@ -185,7 +185,7 @@
|
||||
},
|
||||
"decimal": {
|
||||
"message": "Decimals of Precision"
|
||||
},
|
||||
},
|
||||
"defaultNetwork": {
|
||||
"message": "The default network for Ether transactions is Main Net."
|
||||
},
|
||||
@ -235,7 +235,7 @@
|
||||
"done": {
|
||||
"message": "Done"
|
||||
},
|
||||
"downloadStatelogs": {
|
||||
"downloadStateLogs": {
|
||||
"message": "Download State Logs"
|
||||
},
|
||||
"dropped": {
|
||||
@ -671,6 +671,12 @@
|
||||
"save": {
|
||||
"message": "Save"
|
||||
},
|
||||
"reprice_title": {
|
||||
"message": "Reprice Transaction"
|
||||
},
|
||||
"reprice_subtitle": {
|
||||
"message": "Increase your gas price to attempt to overwrite and speed up your transaction"
|
||||
},
|
||||
"saveAsFile": {
|
||||
"message": "Save as File",
|
||||
"description": "Account export process"
|
||||
@ -820,6 +826,9 @@
|
||||
"transactions": {
|
||||
"message": "transactions"
|
||||
},
|
||||
"transactionError": {
|
||||
"message": "Transaction Error. Exception thrown in contract code."
|
||||
},
|
||||
"transactionMemo": {
|
||||
"message": "Transaction memo (optional)"
|
||||
},
|
||||
@ -884,7 +893,7 @@
|
||||
},
|
||||
"visitWebSite": {
|
||||
"message": "Visit our web site"
|
||||
},
|
||||
},
|
||||
"warning": {
|
||||
"message": "Warning"
|
||||
},
|
||||
|
@ -247,7 +247,7 @@
|
||||
"done": {
|
||||
"message": "Completo"
|
||||
},
|
||||
"downloadStatelogs": {
|
||||
"downloadStateLogs": {
|
||||
"message": "Descargar logs de estado"
|
||||
},
|
||||
"dropped": {
|
||||
|
@ -223,7 +223,7 @@
|
||||
"done": {
|
||||
"message": "संपन्न"
|
||||
},
|
||||
"downloadStatelogs": {
|
||||
"downloadStateLogs": {
|
||||
"message": "राज्य लॉग डाउनलोड करें"
|
||||
},
|
||||
"edit": {
|
||||
|
19
app/_locales/index.json
Normal file
19
app/_locales/index.json
Normal file
@ -0,0 +1,19 @@
|
||||
[
|
||||
{ "code": "de", "name": "German" },
|
||||
{ "code": "en", "name": "English" },
|
||||
{ "code": "es", "name": "Spanish" },
|
||||
{ "code": "fr", "name": "French" },
|
||||
{ "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": "vi", "name": "Vietnamese" },
|
||||
{ "code": "zh_CN", "name": "Mandarin" },
|
||||
{ "code": "zh_TW", "name": "Taiwanese" }
|
||||
]
|
@ -223,7 +223,7 @@
|
||||
"done": {
|
||||
"message": "Finito"
|
||||
},
|
||||
"downloadStatelogs": {
|
||||
"downloadStateLogs": {
|
||||
"message": "Scarica i log di Stato"
|
||||
},
|
||||
"edit": {
|
||||
|
@ -49,6 +49,9 @@
|
||||
"balance": {
|
||||
"message": "残高:"
|
||||
},
|
||||
"balances": {
|
||||
"message": "トークン残高"
|
||||
},
|
||||
"balanceIsInsufficientGas": {
|
||||
"message": "現在のガス総量に対して残高が不足しています"
|
||||
},
|
||||
@ -160,20 +163,20 @@
|
||||
"message": "DENとは、あなたのパスワードが暗号化されたMetaMask内のストレージです。"
|
||||
},
|
||||
"deposit": {
|
||||
"message": "お振込み"
|
||||
"message": "振込"
|
||||
},
|
||||
"depositBTC": {
|
||||
"message": "BTCを下記のアドレスへ振込んでください:"
|
||||
},
|
||||
"depositCoin": {
|
||||
"message": "あなたの $1を下記のアドレスへ振込んでください",
|
||||
"message": "$1を下記のアドレスへ振込んでください",
|
||||
"description": "Tells the user what coin they have selected to deposit with shapeshift"
|
||||
},
|
||||
"depositEth": {
|
||||
"message": "ETHを入金"
|
||||
},
|
||||
"depositEther": {
|
||||
"message": "Etherを入金"
|
||||
"message": "Etherを振込"
|
||||
},
|
||||
"depositFiat": {
|
||||
"message": "法定通貨でデポジット"
|
||||
@ -215,7 +218,7 @@
|
||||
"message": "パスワードを入力"
|
||||
},
|
||||
"etherscanView": {
|
||||
"message": "Etherscanでアカウントを参照"
|
||||
"message": "Etherscanでアカウントを確認"
|
||||
},
|
||||
"exchangeRate": {
|
||||
"message": "交換レート"
|
||||
@ -293,7 +296,7 @@
|
||||
"message": "トークンを隠す"
|
||||
},
|
||||
"hideTokenPrompt": {
|
||||
"message": "トークンを隠しますか??"
|
||||
"message": "トークンを隠しますか?"
|
||||
},
|
||||
"howToDeposit": {
|
||||
"message": "どのようにEtherをデポジットしますか?"
|
||||
@ -306,7 +309,7 @@
|
||||
"message": "アカウントのインポート"
|
||||
},
|
||||
"importAccountMsg": {
|
||||
"message":"追加したアカウントはMetaMaskのアカウントシードフレーズとは関連付けられません。インポートしたアカウントについての詳細は"
|
||||
"message":"追加したアカウントはMetaMaskのアカウントパスフレーズとは関連付けられません。インポートしたアカウントについての詳細は"
|
||||
},
|
||||
"importAnAccount": {
|
||||
"message": "アカウントをインポート"
|
||||
@ -341,7 +344,7 @@
|
||||
"description": "format for importing an account"
|
||||
},
|
||||
"keepTrackTokens": {
|
||||
"message": "MetaMaskアカウントで入手したトークンを追跡でできます。"
|
||||
"message": "MetaMaskアカウントで入手したトークンを検索できます。"
|
||||
},
|
||||
"kovan": {
|
||||
"message": "Kovanテストネットワーク"
|
||||
@ -386,6 +389,9 @@
|
||||
"myAccounts": {
|
||||
"message": "マイアカウント"
|
||||
},
|
||||
"mustSelectOne": {
|
||||
"message": "一つ以上のトークンを選択してください。"
|
||||
},
|
||||
"needEtherInWallet": {
|
||||
"message": "MetaMaskで分散型アプリケーションを使用するためには、このウォレットにEtherが必要です。"
|
||||
},
|
||||
@ -426,7 +432,7 @@
|
||||
"message": "この名前にはアドレスが設定されていません。"
|
||||
},
|
||||
"noDeposits": {
|
||||
"message": "お振込みがありません。"
|
||||
"message": "振込みがありません。"
|
||||
},
|
||||
"noTransactionHistory": {
|
||||
"message": "トランザクション履歴がありません。"
|
||||
@ -460,11 +466,14 @@
|
||||
"description": "For importing an account from a private key"
|
||||
},
|
||||
"pasteSeed": {
|
||||
"message": "シードをここにペーストして下さい!"
|
||||
"message": "パスフレーズをここにペーストして下さい!"
|
||||
},
|
||||
"pleaseReviewTransaction": {
|
||||
"message": "トランザクションを確認して下さい。"
|
||||
},
|
||||
"popularTokens": {
|
||||
"message": "人気のトークン"
|
||||
},
|
||||
"privateKey": {
|
||||
"message": "秘密鍵",
|
||||
"description": "select this type of file to use to import an account"
|
||||
@ -485,13 +494,13 @@
|
||||
"message": "もっと読む"
|
||||
},
|
||||
"receive": {
|
||||
"message": "お受取り"
|
||||
"message": "受取"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "受取人アドレス"
|
||||
},
|
||||
"refundAddress": {
|
||||
"message": "お受取りアドレス"
|
||||
"message": "受取アドレス"
|
||||
},
|
||||
"rejected": {
|
||||
"message": "拒否されました"
|
||||
@ -502,12 +511,18 @@
|
||||
"restoreFromSeed": {
|
||||
"message": "パスフレーズから復元する"
|
||||
},
|
||||
"restoreVault": {
|
||||
"message": "ウォレットを復元する"
|
||||
},
|
||||
"required": {
|
||||
"message": "必要です。"
|
||||
},
|
||||
"retryWithMoreGas": {
|
||||
"message": "より高いガスプライスで再度試して下さい。"
|
||||
},
|
||||
"walletSeed": {
|
||||
"message": "ウォレットのパスフレーズ"
|
||||
},
|
||||
"revealSeedWords": {
|
||||
"message": "パスフレーズを表示"
|
||||
},
|
||||
@ -534,6 +549,9 @@
|
||||
"selectService": {
|
||||
"message": "サービスを選択"
|
||||
},
|
||||
"selectType": {
|
||||
"message": "キーの種類"
|
||||
},
|
||||
"send": {
|
||||
"message": "送信"
|
||||
},
|
||||
@ -555,8 +573,11 @@
|
||||
"settings": {
|
||||
"message": "設定"
|
||||
},
|
||||
"info": {
|
||||
"message": "情報"
|
||||
},
|
||||
"shapeshiftBuy": {
|
||||
"message": "Shapeshiftで取引"
|
||||
"message": "Shapeshiftで交換"
|
||||
},
|
||||
"showPrivateKeys": {
|
||||
"message": "秘密鍵を表示"
|
||||
@ -616,6 +637,9 @@
|
||||
"total": {
|
||||
"message": "合計"
|
||||
},
|
||||
"transactions": {
|
||||
"message": "トランザクション"
|
||||
},
|
||||
"transactionMemo": {
|
||||
"message": "トランザクションメモ (オプション)"
|
||||
},
|
||||
|
@ -223,7 +223,7 @@
|
||||
"done": {
|
||||
"message": "Gedaan"
|
||||
},
|
||||
"downloadStatelogs": {
|
||||
"downloadStateLogs": {
|
||||
"message": "Staatslogboeken downloaden"
|
||||
},
|
||||
"edit": {
|
||||
@ -299,7 +299,7 @@
|
||||
"message": "De gaslimiet moet minstens 21000 zijn"
|
||||
},
|
||||
"generatingSeed": {
|
||||
"message": "Zaad produceren ..."
|
||||
"message": "Back-up woorden produceren ..."
|
||||
},
|
||||
"gasPrice": {
|
||||
"message": "Gasprijs (GWEI)"
|
||||
@ -432,7 +432,7 @@
|
||||
"message": "Los"
|
||||
},
|
||||
"loweCaseWords": {
|
||||
"message": "zaadwoorden hebben alleen kleine letters"
|
||||
"message": "back-up woorden hebben alleen kleine letters"
|
||||
},
|
||||
"mainnet": {
|
||||
"message": "belangrijkste ethereum-netwerk"
|
||||
@ -532,7 +532,7 @@
|
||||
"description": "Voor het importeren van een account vanaf een privésleutel"
|
||||
},
|
||||
"pasteSeed": {
|
||||
"message": "Plak je zaadzin hier!"
|
||||
"message": "Plak je back-up woorden hier!"
|
||||
},
|
||||
"personalAddressDetected": {
|
||||
"message": "Persoonlijk adres gedetecteerd. Voer het tokencontractadres in."
|
||||
@ -581,7 +581,7 @@
|
||||
"message": "Account opnieuw instellen"
|
||||
},
|
||||
"restoreFromSeed": {
|
||||
"message": "Herstel van zaaduitdrukking"
|
||||
"message": "Herstel vanuit back-up woorden"
|
||||
},
|
||||
"required": {
|
||||
"message": "Verplicht"
|
||||
@ -590,10 +590,10 @@
|
||||
"message": "Probeer hier opnieuw met een hogere gasprijs"
|
||||
},
|
||||
"revealSeedWords": {
|
||||
"message": "Onthul zaadwoorden"
|
||||
"message": "Onthul back-up woorden"
|
||||
},
|
||||
"revealSeedWordsWarning": {
|
||||
"message": "Herstel je zaadwoorden niet op een openbare plaats! Deze woorden kunnen worden gebruikt om al uw accounts te stelen."
|
||||
"message": "Zorg dat je back-up woorden niet op een openbare plaats bekijkt! Deze woorden kunnen worden gebruikt om al uw accounts opnieuw te genereren (en dus uw account te stelen)."
|
||||
},
|
||||
"revert": {
|
||||
"message": "terugkeren"
|
||||
@ -616,7 +616,7 @@
|
||||
"description": "Account export proces"
|
||||
},
|
||||
"saveSeedAsFile": {
|
||||
"message": "Bewaar zaadwoorden als bestand"
|
||||
"message": "Bewaar back-up woorden als bestand"
|
||||
},
|
||||
"search": {
|
||||
"message": "Zoeken"
|
||||
@ -625,7 +625,7 @@
|
||||
"message": "Voer hier je geheime twaalfwoordfrase in om je kluis te herstellen."
|
||||
},
|
||||
"seedPhraseReq": {
|
||||
"message": "zaadzinnen zijn 12 woorden lang"
|
||||
"message": "Back-up woorden zijn 12 woorden lang"
|
||||
},
|
||||
"select": {
|
||||
"message": "kiezen"
|
||||
|
@ -223,7 +223,7 @@
|
||||
"done": {
|
||||
"message": "Finalizado"
|
||||
},
|
||||
"downloadStatelogs": {
|
||||
"downloadStateLogs": {
|
||||
"message": "Descarregar Registos de Estado"
|
||||
},
|
||||
"edit": {
|
||||
|
@ -3,13 +3,13 @@
|
||||
"message": "Принять"
|
||||
},
|
||||
"account": {
|
||||
"message": "Аккаунт"
|
||||
"message": "Счет"
|
||||
},
|
||||
"accountDetails": {
|
||||
"message": "Детали Аккаунта"
|
||||
"message": "Детали счета"
|
||||
},
|
||||
"accountName": {
|
||||
"message": "Имя Пользователя"
|
||||
"message": "Название счета"
|
||||
},
|
||||
"address": {
|
||||
"message": "Адрес"
|
||||
@ -21,13 +21,13 @@
|
||||
"message": "Добавить токен"
|
||||
},
|
||||
"addTokens": {
|
||||
"message": "Добавить Токены"
|
||||
"message": "Добавить токены"
|
||||
},
|
||||
"amount": {
|
||||
"message": "Количество"
|
||||
"message": "Сумма"
|
||||
},
|
||||
"amountPlusGas": {
|
||||
"message": "Количество + газ"
|
||||
"message": "Сумма + газ"
|
||||
},
|
||||
"appDescription": {
|
||||
"message": "Расширение браузера для Ethereum",
|
||||
@ -37,11 +37,14 @@
|
||||
"message": "MetaMask",
|
||||
"description": "The name of the application"
|
||||
},
|
||||
"approved": {
|
||||
"message": "Одобрена"
|
||||
},
|
||||
"attemptingConnect": {
|
||||
"message": "Попытка подключиться к блокчейн сети."
|
||||
},
|
||||
"attributions": {
|
||||
"message": "Опознания"
|
||||
"message": "Атрибуция"
|
||||
},
|
||||
"available": {
|
||||
"message": "Доступный"
|
||||
@ -53,13 +56,13 @@
|
||||
"message": "Баланс:"
|
||||
},
|
||||
"balances": {
|
||||
"message": "Ваши балансы"
|
||||
"message": "Ваш баланс"
|
||||
},
|
||||
"balanceIsInsufficientGas": {
|
||||
"message": "Недостаточный баланс для текущего объема газа"
|
||||
},
|
||||
"beta": {
|
||||
"message": "БЕТА"
|
||||
"message": "BETA"
|
||||
},
|
||||
"betweenMinAndMax": {
|
||||
"message": "должно быть больше или равно $1 и меньше или равно $2.",
|
||||
@ -69,10 +72,10 @@
|
||||
"message": "Использовать Blockies Identicon"
|
||||
},
|
||||
"borrowDharma": {
|
||||
"message": "Заимствовать с Dharma (бета)"
|
||||
"message": "Взять в долг на Dharma (Beta)"
|
||||
},
|
||||
"builtInCalifornia": {
|
||||
"message": "MetaMask спроектирован и построен в Калифорнии."
|
||||
"message": "MetaMask спроектирован и разработан в Калифорнии."
|
||||
},
|
||||
"buy": {
|
||||
"message": "Купить"
|
||||
@ -81,7 +84,10 @@
|
||||
"message": "Купить на Coinbase"
|
||||
},
|
||||
"buyCoinbaseExplainer": {
|
||||
"message": "Coinbase - самый популярный в мире способ купить и продать биткойн, ethereum и litecoin."
|
||||
"message": "Биржа Coinbase – это наиболее популярный способ купить или продать bitcoin, ethereum и litecoin."
|
||||
},
|
||||
"ok": {
|
||||
"message": "ОК"
|
||||
},
|
||||
"cancel": {
|
||||
"message": "Отмена"
|
||||
@ -95,14 +101,17 @@
|
||||
"confirm": {
|
||||
"message": "Подтвердить"
|
||||
},
|
||||
"confirmed": {
|
||||
"message": "Подтверждена"
|
||||
},
|
||||
"confirmContract": {
|
||||
"message": "Подтвердить Контракт"
|
||||
"message": "Подтвердить контракт"
|
||||
},
|
||||
"confirmPassword": {
|
||||
"message": "Подтвердите Пароль"
|
||||
"message": "Подтвердите пароль"
|
||||
},
|
||||
"confirmTransaction": {
|
||||
"message": "Подтвердить Транзакцию"
|
||||
"message": "Подтвердить транзакцию"
|
||||
},
|
||||
"continue": {
|
||||
"message": "Продолжить"
|
||||
@ -114,7 +123,7 @@
|
||||
"message": "Развертывание контракта"
|
||||
},
|
||||
"conversionProgress": {
|
||||
"message": "Выполняется конверсия"
|
||||
"message": "Выполняется конвертация"
|
||||
},
|
||||
"copiedButton": {
|
||||
"message": "Скопировано"
|
||||
@ -126,7 +135,7 @@
|
||||
"message": "Скопировано!"
|
||||
},
|
||||
"copiedSafe": {
|
||||
"message": "Я скопировал его где-то в безопасности"
|
||||
"message": "Я скопировал это в безопасное место"
|
||||
},
|
||||
"copy": {
|
||||
"message": "Скопировать"
|
||||
@ -138,29 +147,32 @@
|
||||
"message": " Скопировать "
|
||||
},
|
||||
"copyPrivateKey": {
|
||||
"message": "Это ваш личный ключ (нажмите, чтобы скопировать)"
|
||||
"message": "Это ваш закрытый ключ (нажмите, чтобы скопировать)"
|
||||
},
|
||||
"create": {
|
||||
"message": "Создать"
|
||||
},
|
||||
"createAccount": {
|
||||
"message": "Регистрация"
|
||||
"message": "Создать счет"
|
||||
},
|
||||
"createDen": {
|
||||
"message": "Создать"
|
||||
},
|
||||
"crypto": {
|
||||
"message": "Крипто",
|
||||
"message": "Криптовалюта",
|
||||
"description": "Exchange type (cryptocurrencies)"
|
||||
},
|
||||
"currentConversion": {
|
||||
"message": "Текущая конверсия"
|
||||
"message": "Текущая конвертация"
|
||||
},
|
||||
"currentNetwork": {
|
||||
"message": "Текущая сеть"
|
||||
},
|
||||
"customGas": {
|
||||
"message": "Настроить Газ"
|
||||
"message": "Настроить газ"
|
||||
},
|
||||
"customToken": {
|
||||
"message": "Пользовательский токен"
|
||||
},
|
||||
"customize": {
|
||||
"message": "Настроить"
|
||||
@ -169,112 +181,115 @@
|
||||
"message": "Пользовательский RPC"
|
||||
},
|
||||
"decimalsMustZerotoTen": {
|
||||
"message": "Десятичные числа должны быть не менее 0, и не более 36."
|
||||
"message": "Количество десятичных разрядов должно быть минимум 0 и максимум 36."
|
||||
},
|
||||
"decimal": {
|
||||
"message": "Десятичные значения точности"
|
||||
"message": "Количество десятичных разрядов"
|
||||
},
|
||||
"defaultNetwork": {
|
||||
"message": "Сеть по умолчанию для транзакций Ether - это Main Net."
|
||||
"message": "Основная сеть Ethereum – это сеть по умолчанию для Ether транзакций."
|
||||
},
|
||||
"denExplainer": {
|
||||
"message": "Ваш DEN - это ваше зашифрованное паролем хранилище в MetaMask."
|
||||
"message": "DEN – это зашифрованное паролем хранилище внутри MetaMask."
|
||||
},
|
||||
"deposit": {
|
||||
"message": "Депозит"
|
||||
"message": "Пополнить"
|
||||
},
|
||||
"depositBTC": {
|
||||
"message": "Депозит BTC по адресу:"
|
||||
"message": "Отправьте ваш BTC на адрес ниже:"
|
||||
},
|
||||
"depositCoin": {
|
||||
"message": "Депозит $1 по указанному ниже адресу",
|
||||
"message": "Отправьте ваш $1 на адрес ниже",
|
||||
"description": "Tells the user what coin they have selected to deposit with shapeshift"
|
||||
},
|
||||
"depositEth": {
|
||||
"message": "Депозит Eth"
|
||||
"message": "Пополнить Eth"
|
||||
},
|
||||
"depositEther": {
|
||||
"message": "Депозит Эфир"
|
||||
"message": "Пополнить Ether"
|
||||
},
|
||||
"depositFiat": {
|
||||
"message": "Депозит с деньгами"
|
||||
"message": "Пополнить деньгами"
|
||||
},
|
||||
"depositFromAccount": {
|
||||
"message": "Депозит с другого счета"
|
||||
"message": "Пополнить с другого счета"
|
||||
},
|
||||
"depositShapeShift": {
|
||||
"message": "Депозит с ShapeShift"
|
||||
"message": "Пополнить через ShapeShift"
|
||||
},
|
||||
"depositShapeShiftExplainer": {
|
||||
"message": "Если у вас есть другие крипторесурсы, вы можете торговать и вносить Эфир непосредственно в кошелек MetaMask. Нет необходимости в аккаунте."
|
||||
"message": "Если у вас есть другие криптовалюты, вы можете торговать и пополнять Ether напрямую в ваш MetaMask кошелек. Нет необходимости в счете."
|
||||
},
|
||||
"details": {
|
||||
"message": "Детали"
|
||||
},
|
||||
"directDeposit": {
|
||||
"message": "Прямой Депозит"
|
||||
"message": "Прямое пополнение"
|
||||
},
|
||||
"directDepositEther": {
|
||||
"message": "Прямой Депозит Эфира"
|
||||
"message": "Прямое пополнение Ether"
|
||||
},
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Если у вас уже есть Эфир, самый быстрый способ получить Эфир в вашем новом кошельке это прямым депозитом."
|
||||
"message": "Если у вас уже есть Ether, то самый быстрый способ получить Ether в ваш новый кошелек – это прямое пополнение."
|
||||
},
|
||||
"done": {
|
||||
"message": "Готово"
|
||||
},
|
||||
"downloadStatelogs": {
|
||||
"message": "Загрузить логи статус"
|
||||
"downloadStateLogs": {
|
||||
"message": "Скачать журнал состояния"
|
||||
},
|
||||
"dropped": {
|
||||
"message": "Отброшена"
|
||||
},
|
||||
"edit": {
|
||||
"message": "Редактировать"
|
||||
},
|
||||
"editAccountName": {
|
||||
"message": "Изменить Имя Аккаунта"
|
||||
"message": "Редактировать название счета"
|
||||
},
|
||||
"emailUs": {
|
||||
"message": "Свяжитесь с нами по электронной почте!"
|
||||
},
|
||||
"encryptNewDen": {
|
||||
"message": "Шифруйте новый DEN"
|
||||
"message": "Зашифровать ваш новый DEN"
|
||||
},
|
||||
"enterPassword": {
|
||||
"message": "Введите пароль"
|
||||
},
|
||||
"enterPasswordConfirm": {
|
||||
"message": "Введите свой пароль для подтверждения"
|
||||
"message": "Введите ваш пароль для подтверждения"
|
||||
},
|
||||
"etherscanView": {
|
||||
"message": "Просмотреть аккаунт на Etherscan"
|
||||
"message": "Просмотреть счет на Etherscan"
|
||||
},
|
||||
"exchangeRate": {
|
||||
"message": "Обменный Курс"
|
||||
"message": "Обменный курс"
|
||||
},
|
||||
"exportPrivateKey": {
|
||||
"message": "Экспорт закрытого ключа"
|
||||
"message": "Экспортировать закрытый ключ"
|
||||
},
|
||||
"exportPrivateKeyWarning": {
|
||||
"message": "Экспорт секретных ключей на свой страх и риск."
|
||||
"message": "Вы экспортируете закрытые ключи на свой страх и риск."
|
||||
},
|
||||
"failed": {
|
||||
"message": "Не смогли"
|
||||
"message": "Неудачна"
|
||||
},
|
||||
"fiat": {
|
||||
"message": "Бумажные деньги",
|
||||
"message": "Валюта",
|
||||
"description": "Exchange type"
|
||||
},
|
||||
"fileImportFail": {
|
||||
"message": "Ошибка импорта файлов? Кликните сюда!",
|
||||
"message": "Не работает импорт файла? Нажмите тут!",
|
||||
"description": "Helps user import their account from a JSON file"
|
||||
},
|
||||
"followTwitter": {
|
||||
"message": "Следуйте за нами на Twitter"
|
||||
"message": "Читайте нас в Twitter"
|
||||
},
|
||||
"from": {
|
||||
"message": "Из"
|
||||
"message": "Отправитель"
|
||||
},
|
||||
"fromToSame": {
|
||||
"message": "От и до адреса не могут быть одинаковым"
|
||||
"message": "Адрес отправителя и получателя не могут быть одинаковыми"
|
||||
},
|
||||
"fromShapeShift": {
|
||||
"message": "Из ShapeShift"
|
||||
@ -284,37 +299,37 @@
|
||||
"description": "Short indication of gas cost"
|
||||
},
|
||||
"gasFee": {
|
||||
"message": "Плата за Газ"
|
||||
"message": "Комиссия за газ"
|
||||
},
|
||||
"gasLimit": {
|
||||
"message": "Газовый Предел"
|
||||
"message": "Лимит газа"
|
||||
},
|
||||
"gasLimitCalculation": {
|
||||
"message": "Мы рассчитываем предполагаемый предел газа на основе коэффициентов успешности сети."
|
||||
"message": "Мы расчитываем предлагаемый лимит газа на основании успешных ставок в сети."
|
||||
},
|
||||
"gasLimitRequired": {
|
||||
"message": "Требуется ограничение на Газ"
|
||||
"message": "Установите лимит газа"
|
||||
},
|
||||
"gasLimitTooLow": {
|
||||
"message": "Предел газа должен быть не менее 21000"
|
||||
"message": "Лимит газа должен быть как минимум 21000"
|
||||
},
|
||||
"generatingSeed": {
|
||||
"message": "Создание Семян ..."
|
||||
"message": "Генерируем фразу..."
|
||||
},
|
||||
"gasPrice": {
|
||||
"message": "Цена на Газ (GWEI)"
|
||||
"message": "Цена за газ (GWEI)"
|
||||
},
|
||||
"gasPriceCalculation": {
|
||||
"message": "Мы вычисляем предлагаемые цены на газ на основе коэффициентов успеха сети."
|
||||
"message": "Мы расчитываем предлагаемые цены за газ на основании успешных ставок в сети."
|
||||
},
|
||||
"gasPriceRequired": {
|
||||
"message": "Требуется цена на Газ"
|
||||
"message": "Установите стоимость газа"
|
||||
},
|
||||
"getEther": {
|
||||
"message": "Получить Эфир"
|
||||
"message": "Получить Ether"
|
||||
},
|
||||
"getEtherFromFaucet": {
|
||||
"message": "Получите Эфир из крана $1",
|
||||
"message": "Получить Ether из крана для $1",
|
||||
"description": "Displays network name for Ether faucet"
|
||||
},
|
||||
"greaterThanMin": {
|
||||
@ -322,14 +337,14 @@
|
||||
"description": "helper for inputting hex as decimal input"
|
||||
},
|
||||
"here": {
|
||||
"message": "здесь",
|
||||
"message": "тут",
|
||||
"description": "as in -click here- for more information (goes with troubleTokenBalances)"
|
||||
},
|
||||
"hereList": {
|
||||
"message": "Вот список !!!!"
|
||||
"message": "Вот список!!!!"
|
||||
},
|
||||
"hide": {
|
||||
"message": "Спрятать"
|
||||
"message": "Скрыть"
|
||||
},
|
||||
"hideToken": {
|
||||
"message": "Скрыть токен"
|
||||
@ -338,33 +353,33 @@
|
||||
"message": "Скрыть токен?"
|
||||
},
|
||||
"howToDeposit": {
|
||||
"message": "Как бы вы хотели поместить Эфир?"
|
||||
"message": "Как бы вы хотели пополнить Ether?"
|
||||
},
|
||||
"holdEther": {
|
||||
"message": "Это позволяет вам использовать эфир и токены и служит мостом для децентрализованных приложений."
|
||||
"message": "Позволяет вам хранить ether и токены и служит в качестве моста в децентрализированные приложения."
|
||||
},
|
||||
"import": {
|
||||
"message": "Импортировать",
|
||||
"description": "Button to import an account from a selected file"
|
||||
},
|
||||
"importAccount": {
|
||||
"message": "Импорт Аккаунта"
|
||||
"message": "Импортировать счет"
|
||||
},
|
||||
"importAccountMsg": {
|
||||
"message": " Импортированные аккаунты не будут связаны с вашей первоначально созданным аккаунтом MetaMask. Подробнее о импортированных аккаунтах "
|
||||
"message":" Импортированные счета не будут ассоциированы с вашей ключевой фразой, созданной MetaMask. Узнать больше про импорт счетов "
|
||||
},
|
||||
"importAnAccount": {
|
||||
"message": "Импортировать аккаунт"
|
||||
},
|
||||
"importDen": {
|
||||
"message": "Импорт существующих DEN"
|
||||
"message": "Импортировать существующий DEN"
|
||||
},
|
||||
"imported": {
|
||||
"message": "Импортирован",
|
||||
"description": "status showing that an account has been fully loaded into the keyring"
|
||||
},
|
||||
"infoHelp": {
|
||||
"message": "Информация и Помощь"
|
||||
"message": "Информация и помощь"
|
||||
},
|
||||
"insufficientFunds": {
|
||||
"message": "Недостаточно средств."
|
||||
@ -373,35 +388,44 @@
|
||||
"message": "Недостаточно токенов."
|
||||
},
|
||||
"invalidAddress": {
|
||||
"message": "Недействительный адрес"
|
||||
"message": "Неверный адрес"
|
||||
},
|
||||
"invalidAddressRecipient": {
|
||||
"message": "Недопустимый адрес получателя."
|
||||
"message": "Неверный адрес получателя"
|
||||
},
|
||||
"invalidGasParams": {
|
||||
"message": "Недопустимые параметры Газа"
|
||||
"message": "Неверные параметры газа"
|
||||
},
|
||||
"invalidInput": {
|
||||
"message": "Неправильный ввод."
|
||||
"message": "Неверный ввод."
|
||||
},
|
||||
"invalidRequest": {
|
||||
"message": "Неверный Запрос"
|
||||
"message": "Неверный запрос"
|
||||
},
|
||||
"invalidRPC": {
|
||||
"message": "Недопустимый URI RPC"
|
||||
"message": "Неверный RPC URI"
|
||||
},
|
||||
"jsonFail": {
|
||||
"message": "Что-то пошло не так. Убедитесь, что ваш файл JSON правильно отформатирован."
|
||||
"message": "Что-то пошло не так. Убедитесь, что ваш JSON файл правильно отформатирован."
|
||||
},
|
||||
"jsonFile": {
|
||||
"message": "Файл JSON",
|
||||
"message": "JSON файл",
|
||||
"description": "format for importing an account"
|
||||
},
|
||||
"keepTrackTokens": {
|
||||
"message": "Следите за купленными вами токенами с помощью аккаунта MetaMask."
|
||||
},
|
||||
"kovan": {
|
||||
"message": "Kovan тестовая сеть"
|
||||
"message": "Тестовая сеть Kovan"
|
||||
},
|
||||
"knowledgeDataBase": {
|
||||
"message": "Посетите нашу базу знаний"
|
||||
"message": "Посмотрите нашу Базу Знаний"
|
||||
},
|
||||
"max": {
|
||||
"message": "Максимум"
|
||||
},
|
||||
"learnMore": {
|
||||
"message": "Узнать больше."
|
||||
},
|
||||
"lessThanMax": {
|
||||
"message": "должно быть меньше или равно $1.",
|
||||
@ -410,29 +434,32 @@
|
||||
"likeToAddTokens": {
|
||||
"message": "Вы хотите добавить эти токены?"
|
||||
},
|
||||
"links": {
|
||||
"message": "Ссылки"
|
||||
},
|
||||
"limit": {
|
||||
"message": "Предел"
|
||||
"message": "Лимит"
|
||||
},
|
||||
"loading": {
|
||||
"message": "Загрузка..."
|
||||
},
|
||||
"loadingTokens": {
|
||||
"message": "Загрузка токенов ..."
|
||||
"message": "Загрузка токенов..."
|
||||
},
|
||||
"localhost": {
|
||||
"message": "Локальный адрес 8545"
|
||||
"message": "Localhost 8545"
|
||||
},
|
||||
"login": {
|
||||
"message": "Авторизоваться"
|
||||
"message": "Вход"
|
||||
},
|
||||
"logout": {
|
||||
"message": "Выйти"
|
||||
"message": "Выход"
|
||||
},
|
||||
"loose": {
|
||||
"message": "Рыхлый"
|
||||
"message": "Несвязанный"
|
||||
},
|
||||
"loweCaseWords": {
|
||||
"message": "семенные слова имеют только символы нижнего регистра"
|
||||
"message": "ключевая фраза может содержать только символы нижнего регистра"
|
||||
},
|
||||
"mainnet": {
|
||||
"message": "Основная сеть Ethereum"
|
||||
@ -441,19 +468,19 @@
|
||||
"message": "Сообщение"
|
||||
},
|
||||
"metamaskDescription": {
|
||||
"message": "MetaMask - это безопасное хранилище для Ethereum."
|
||||
"message": "MetaMask – безопасный кошелек для Ethereum."
|
||||
},
|
||||
"min": {
|
||||
"message": "Минимум"
|
||||
},
|
||||
"myAccounts": {
|
||||
"message": "Мои Аккаунты"
|
||||
"message": "Мои счета"
|
||||
},
|
||||
"mustSelectOne": {
|
||||
"message": "Необходимо выбрать не менее 1 токена."
|
||||
"message": "Необходимо выбрать как минимум 1 токен."
|
||||
},
|
||||
"needEtherInWallet": {
|
||||
"message": "Чтобы взаимодействовать с децентрализованными приложениями с помощью MetaMask, вам понадобится Эфир в вашем кошельке."
|
||||
"message": "Для взаимодействия с децентрализованными приложениями с помощью MetaMask нужен Ether в вашем кошельке."
|
||||
},
|
||||
"needImportFile": {
|
||||
"message": "Вы должны выбрать файл для импорта.",
|
||||
@ -464,60 +491,60 @@
|
||||
"description": "Password and file needed to import an account"
|
||||
},
|
||||
"negativeETH": {
|
||||
"message": "Невозможно отправить отрицательные количества ETH."
|
||||
"message": "Невозможно отправить отрицательную сумму ETH."
|
||||
},
|
||||
"networks": {
|
||||
"message": "Сети"
|
||||
},
|
||||
"newAccount": {
|
||||
"message": "Новый Аккаунт"
|
||||
"message": "Новый счет"
|
||||
},
|
||||
"newAccountNumberName": {
|
||||
"message": "Аккаунт $1",
|
||||
"message": "Счет $1",
|
||||
"description": "Default name of next account to be created on create account screen"
|
||||
},
|
||||
"newContract": {
|
||||
"message": "Новый Контракт"
|
||||
"message": "Новый контракт"
|
||||
},
|
||||
"newPassword": {
|
||||
"message": "Новый пароль (мин. 8 символов)"
|
||||
},
|
||||
"newRecipient": {
|
||||
"message": "Новый Получатель"
|
||||
"message": "Новый получатель"
|
||||
},
|
||||
"newRPC": {
|
||||
"message": "Новый URL-адрес RPC"
|
||||
"message": "Новый RPC URL"
|
||||
},
|
||||
"next": {
|
||||
"message": "Далее"
|
||||
},
|
||||
"noAddressForName": {
|
||||
"message": "Для этого имени не задан адрес."
|
||||
"message": "Дла этого названия не установлен адрес."
|
||||
},
|
||||
"noDeposits": {
|
||||
"message": "Не было получено никаких депозитов"
|
||||
"message": "Пополнения не получены"
|
||||
},
|
||||
"noTransactionHistory": {
|
||||
"message": "Нет истории транзакций."
|
||||
},
|
||||
"noTransactions": {
|
||||
"message": "Нет Транзакций"
|
||||
"message": "Нет транзакций"
|
||||
},
|
||||
"notStarted": {
|
||||
"message": "Не Начался"
|
||||
"message": "Не запущен"
|
||||
},
|
||||
"oldUI": {
|
||||
"message": "Старый Интерфейс"
|
||||
"message": "Старая версия интерфейса"
|
||||
},
|
||||
"oldUIMessage": {
|
||||
"message": "Вы вернулись к старому интерфейсу. Вы можете вернуться к новому с помощью опции в раскрывающемся меню в правом верхнем углу."
|
||||
"message": "Вы вернулись к старой версии интерфейса пользователя. Вы можете переключиться на новую с помощью опции выпадающего меню в правом верхнем углу."
|
||||
},
|
||||
"or": {
|
||||
"message": "или",
|
||||
"description": "choice between creating or importing a new account"
|
||||
},
|
||||
"passwordCorrect": {
|
||||
"message": "Убедитесь, что ваш пароль правильный."
|
||||
"message": "Убедитесь, что ваш пароль верный."
|
||||
},
|
||||
"passwordMismatch": {
|
||||
"message": "пароли не совпадают",
|
||||
@ -528,27 +555,30 @@
|
||||
"description": "in password creation process, the password is not long enough to be secure"
|
||||
},
|
||||
"pastePrivateKey": {
|
||||
"message": "Вставьте свою личную строку:",
|
||||
"message": "Вставьте ваш закрытый ключ тут:",
|
||||
"description": "For importing an account from a private key"
|
||||
},
|
||||
"pasteSeed": {
|
||||
"message": "Вставьте здесь свою семенную фразу!"
|
||||
"message": "Вставьте вашу ключевую фразу!"
|
||||
},
|
||||
"personalAddressDetected": {
|
||||
"message": "Персональный адрес обнаружен. Введите адрес контракта токена."
|
||||
"message": "Обнаружен персональный адрес. Введите адрес контракта токена."
|
||||
},
|
||||
"pleaseReviewTransaction": {
|
||||
"message": "Проверьте транзакцию."
|
||||
},
|
||||
"popularTokens": {
|
||||
"message": "Популярные токены"
|
||||
},
|
||||
"privacyMsg": {
|
||||
"message": "Политика Конфиденциальности"
|
||||
"message": "Политика конфиденциальности"
|
||||
},
|
||||
"privateKey": {
|
||||
"message": "Закрытый ключ",
|
||||
"description": "select this type of file to use to import an account"
|
||||
},
|
||||
"privateKeyWarning": {
|
||||
"message": "Предупреждение: никогда не раскрывайте этот ключ. Любой, у кого есть ваши личные ключи, может украсть любые активы, хранящиеся в вашем аккаунте."
|
||||
"message": "Предупреждение: Никогда не раскрывайте этот ключ. Любой, у кого есть ваши закрытые ключи, может украсть любые активы, хранящиеся на счету."
|
||||
},
|
||||
"privateNetwork": {
|
||||
"message": "Частная сеть"
|
||||
@ -557,126 +587,165 @@
|
||||
"message": "Показать QR-код"
|
||||
},
|
||||
"readdToken": {
|
||||
"message": "Вы можете добавить этот токен в будущем, перейдя в “Добавить токен” в меню параметров вашего аккаунта."
|
||||
"message": "Вы можете в будущем добавить обратно этот токен, выбрав пункт меню “Добавить токен”."
|
||||
},
|
||||
"readMore": {
|
||||
"message": "Подробнее читайте здесь."
|
||||
"message": "Узнать больше тут."
|
||||
},
|
||||
"readMore2": {
|
||||
"message": "Прочитайте больше."
|
||||
"message": "Узнать больше."
|
||||
},
|
||||
"receive": {
|
||||
"message": "Получить"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Адрес Получателя"
|
||||
"message": "Адрес получателя"
|
||||
},
|
||||
"refundAddress": {
|
||||
"message": "Ваш Адрес Возврата"
|
||||
"message": "Ваш адрес для возврата средств"
|
||||
},
|
||||
"rejected": {
|
||||
"message": "Отклонено"
|
||||
"message": "Отклонена"
|
||||
},
|
||||
"resetAccount": {
|
||||
"message": "Сбросить аккаунт"
|
||||
},
|
||||
"restoreFromSeed": {
|
||||
"message": "Восстановить от семенной фразы"
|
||||
"message": "Восстановить из ключевой фразы"
|
||||
},
|
||||
"restoreVault": {
|
||||
"message": "Восстановить кошелек"
|
||||
},
|
||||
"required": {
|
||||
"message": "Необходимо"
|
||||
"message": "Обязательное поле"
|
||||
},
|
||||
"retryWithMoreGas": {
|
||||
"message": "Повторите попытку с более высокой ценой на газ здесь"
|
||||
"message": "Повторите попытку с большей ценой за газRetry with a higher gas price here"
|
||||
},
|
||||
"walletSeed": {
|
||||
"message": "Ключевая фраза кошелька"
|
||||
},
|
||||
"revealSeedWords": {
|
||||
"message": "Раскрыть семенные слова"
|
||||
"message": "Показать ключевую фразу"
|
||||
},
|
||||
"revealSeedWordsWarning": {
|
||||
"message": "Не восстанавливайте семенные слова в общественном месте! Эти слова могут использоваться для кражи всех ваших аккаунтах."
|
||||
"message": "Не восстанавливайте ключевую фразу в общественном месте! Она может быть использована для кражи всех ваших счетов."
|
||||
},
|
||||
"revert": {
|
||||
"message": "Откат"
|
||||
"message": "Восстановить"
|
||||
},
|
||||
"rinkeby": {
|
||||
"message": "Rinkeby тестовая сеть"
|
||||
"message": "Тестовая сеть Rinkeby"
|
||||
},
|
||||
"ropsten": {
|
||||
"message": "Ropsten тестовая сеть"
|
||||
"message": "Тестовая сеть Ropsten"
|
||||
},
|
||||
"currentRpc": {
|
||||
"message": "Current RPC"
|
||||
},
|
||||
"connectingToMainnet": {
|
||||
"message": "Соединение с основной сетью Ethereum"
|
||||
},
|
||||
"connectingToRopsten": {
|
||||
"message": "Соединение с тестовой сетью Ropsten"
|
||||
},
|
||||
"connectingToKovan": {
|
||||
"message": "Соединение с тестовой сетью Kovan"
|
||||
},
|
||||
"connectingToRinkeby": {
|
||||
"message": "Соединение с тестовой сетью Rinkeby"
|
||||
},
|
||||
"connectingToUnknown": {
|
||||
"message": "Соединение с неизвестной сетью"
|
||||
},
|
||||
"sampleAccountName": {
|
||||
"message": "Например, Мой новый аккаунт",
|
||||
"message": "Например, Мой новый счет",
|
||||
"description": "Help user understand concept of adding a human-readable name to their account"
|
||||
},
|
||||
"save": {
|
||||
"message": "Сохранить"
|
||||
},
|
||||
"saveAsFile": {
|
||||
"message": "Сохранить как Файл",
|
||||
"message": "Сохранить в виде файла",
|
||||
"description": "Account export process"
|
||||
},
|
||||
"saveSeedAsFile": {
|
||||
"message": "Сохранить Семенные Слова Как Файл"
|
||||
"message": "Сохранить ключевую фразу в виде файла"
|
||||
},
|
||||
"search": {
|
||||
"message": "Поиск"
|
||||
},
|
||||
"secretPhrase": {
|
||||
"message": "Введите свою секретную двенадцатисловную фразу здесь, чтобы восстановить хранилище."
|
||||
"message": "Введите вашу ключевую фразу из 12 слов, чтобы восстановить кошелек."
|
||||
},
|
||||
"newPassword8Chars": {
|
||||
"message": "Новый пароль (мин. 8 символов)"
|
||||
},
|
||||
"seedPhraseReq": {
|
||||
"message": "семенные фразы длиной 12 слов"
|
||||
"message": "ключевые фразы имеют длину 12 слов"
|
||||
},
|
||||
"select": {
|
||||
"message": "Выбрать"
|
||||
},
|
||||
"selectCurrency": {
|
||||
"message": "Выберите Валюту"
|
||||
"message": "Выберите валюту"
|
||||
},
|
||||
"selectService": {
|
||||
"message": "Выберите Сервис"
|
||||
"message": "Выберите сервис"
|
||||
},
|
||||
"selectType": {
|
||||
"message": "Выберите Тип"
|
||||
"message": "Выберите тип"
|
||||
},
|
||||
"send": {
|
||||
"message": "Послать"
|
||||
"message": "Отправить"
|
||||
},
|
||||
"sendETH": {
|
||||
"message": "Отправить ETH"
|
||||
},
|
||||
"sendTokens": {
|
||||
"message": "Отправить Токены"
|
||||
"message": "Отправить токены"
|
||||
},
|
||||
"onlySendToEtherAddress": {
|
||||
"message": "Отправляйте ETH только на Ethereum адреса."
|
||||
},
|
||||
"searchTokens": {
|
||||
"message": "Поиск токенов"
|
||||
},
|
||||
"sendTokensAnywhere": {
|
||||
"message": "Отправить Токены кому-либо с аккаунтом Ethereum"
|
||||
"message": "Отправить токены любому, у кого есть счет Ethereum"
|
||||
},
|
||||
"settings": {
|
||||
"message": "Настройки"
|
||||
},
|
||||
"info": {
|
||||
"message": "Информация"
|
||||
},
|
||||
"shapeshiftBuy": {
|
||||
"message": "Купить с помощью Shapeshift"
|
||||
"message": "Купить через Shapeshift"
|
||||
},
|
||||
"showPrivateKeys": {
|
||||
"message": "Показать приватные ключи"
|
||||
"message": "Показать закрытые ключи"
|
||||
},
|
||||
"showQRCode": {
|
||||
"message": "Показать QR-код"
|
||||
},
|
||||
"sign": {
|
||||
"message": "Знак"
|
||||
"message": "Подпись"
|
||||
},
|
||||
"signed": {
|
||||
"message": "Подписана"
|
||||
},
|
||||
"signMessage": {
|
||||
"message": "Нодписать сообщение"
|
||||
"message": "Подписать сообщение"
|
||||
},
|
||||
"signNotice": {
|
||||
"message": "Подписание этого сообщения может иметь \nопасные побочные эффекты. Только подписывайте сообщения \nс сайтов, которым вы полностью доверяете своим аккаунтом. Этот опасный метод будет удален в будущей версии."
|
||||
"message": "Подпись этого сообщения может иметь \nопасные побочные эффекты. Подписывайте только сообщения \nс сайтов, которым вы полностью доверяете свой аккаунт. Этот опасный метод будет удален в будущей версии."
|
||||
},
|
||||
"sigRequest": {
|
||||
"message": "Запрос на подпись"
|
||||
"message": "Запрос подписи"
|
||||
},
|
||||
"sigRequested": {
|
||||
"message": "Подпись Запрошена"
|
||||
"message": "Подпись запрошена"
|
||||
},
|
||||
"spaceBetween": {
|
||||
"message": "между словами может быть только пробел"
|
||||
@ -685,53 +754,59 @@
|
||||
"message": "Статус"
|
||||
},
|
||||
"stateLogs": {
|
||||
"message": "Логи Статуса"
|
||||
"message": "Журнал состояния"
|
||||
},
|
||||
"stateLogsDescription": {
|
||||
"message": "Логи статуса содержат ваши общедоступные адреса и отправленные транзакции."
|
||||
"message": "Журнал состояния содержит ваши публичные адреса счетов и совершенные транзакции."
|
||||
},
|
||||
"stateLogError": {
|
||||
"message": "Ошибка при получении журнала состояния."
|
||||
},
|
||||
"submit": {
|
||||
"message": "Отправить"
|
||||
},
|
||||
"submitted": {
|
||||
"message": "Отправлена"
|
||||
},
|
||||
"supportCenter": {
|
||||
"message": "Посетите наш Центр поддержки"
|
||||
"message": "Перейти в наш Центр поддержки"
|
||||
},
|
||||
"symbolBetweenZeroTen": {
|
||||
"message": "Символ должен быть от 0 до 10 символов."
|
||||
},
|
||||
"takesTooLong": {
|
||||
"message": "Занимает слишком долго?"
|
||||
"message": "Слишком долго?"
|
||||
},
|
||||
"terms": {
|
||||
"message": "Условия Эксплуатации"
|
||||
"message": "Условия пользования"
|
||||
},
|
||||
"testFaucet": {
|
||||
"message": "Тестовый Кран"
|
||||
"message": "Тестовый кран"
|
||||
},
|
||||
"to": {
|
||||
"message": "К"
|
||||
"message": "Получатель: "
|
||||
},
|
||||
"toETHviaShapeShift": {
|
||||
"message": "$1 в ETH через ShapeShift",
|
||||
"description": "system will fill in deposit type in start of message"
|
||||
},
|
||||
"tokenAddress": {
|
||||
"message": "Адрес Токена"
|
||||
"message": "Адрес токена"
|
||||
},
|
||||
"tokenAlreadyAdded": {
|
||||
"message": "Токен уже добавлен."
|
||||
"message": "Токен уже был добавлен."
|
||||
},
|
||||
"tokenBalance": {
|
||||
"message": "Баланс Вашых Tокенов:"
|
||||
"message": "Баланс ваших токенов:"
|
||||
},
|
||||
"tokenSelection": {
|
||||
"message": "Поиск токенов или выбор из нашего списка популярных токенов."
|
||||
"message": "Поищите токен или выберите из нашего списка популярных токенов."
|
||||
},
|
||||
"tokenSymbol": {
|
||||
"message": "Символ Токена"
|
||||
"message": "Символ токена"
|
||||
},
|
||||
"tokenWarning1": {
|
||||
"message": "Следите за токенами, которые вы купили с помощью аккаунта MetaMask. Если вы купили токены, используя другой аккаунт, эти токены здесь не появятся."
|
||||
"message": "Отслеживаются токены, купленные на счет в MetaMask. Если вы купили токены, используя другой счет, такие токены не будут тут отображены."
|
||||
},
|
||||
"total": {
|
||||
"message": "Всего"
|
||||
@ -740,35 +815,38 @@
|
||||
"message": "транзакции"
|
||||
},
|
||||
"transactionMemo": {
|
||||
"message": "Транзакционная записка (необязательно)"
|
||||
"message": "Транзакционные данные (необязательный)"
|
||||
},
|
||||
"transactionNumber": {
|
||||
"message": "Номер Транзакции"
|
||||
"message": "Номер транзакции"
|
||||
},
|
||||
"transfers": {
|
||||
"message": "Переводы"
|
||||
},
|
||||
"troubleTokenBalances": {
|
||||
"message": "У нас были проблемы с загрузкой ваших токенов. Вы можете просмотреть их ",
|
||||
"message": "Возникли проблемы при загрузке балансов токенов. Вы можете посмотреть их ",
|
||||
"description": "Followed by a link (here) to view token balances"
|
||||
},
|
||||
"twelveWords": {
|
||||
"message": "Эти 12 слов - единственный способ восстановить ваши учетные записи MetaMask.\nСохраните их где-нибудь в безопасности и в тайне."
|
||||
"message": "Эти 12 слов являются единственной возможностью восстановить ваши счета в MetaMask.\nСохраните из в надежном секретном месте."
|
||||
},
|
||||
"typePassword": {
|
||||
"message": "Введите Пароль"
|
||||
"message": "Введите пароль"
|
||||
},
|
||||
"uiWelcome": {
|
||||
"message": "Добро пожаловать в новый интерфейс (бета-версия)"
|
||||
"message": "Новый интерфейс (Beta)"
|
||||
},
|
||||
"uiWelcomeMessage": {
|
||||
"message": "Теперь вы используете новый интерфейс Metamask. Осмотритесь, попробуйте новые функции, такие как отправку токенов, и сообщите нам, есть ли у вас какие-либо проблемы."
|
||||
"message": "Теперь вы используете новый интерфейс пользователя MetaMask. Осмотритесь, попробуйте новые функции, например, отправить токены и, если возникнут проблемы, сообщите нам."
|
||||
},
|
||||
"unapproved": {
|
||||
"message": "Не одобрена"
|
||||
},
|
||||
"unavailable": {
|
||||
"message": "Недоступен"
|
||||
"message": "Недоступный"
|
||||
},
|
||||
"unknown": {
|
||||
"message": "Неизвестный"
|
||||
"message": "Неизвестно"
|
||||
},
|
||||
"unknownNetwork": {
|
||||
"message": "Неизвестная частная сеть"
|
||||
@ -777,7 +855,7 @@
|
||||
"message": "Неизвестный идентификатор сети"
|
||||
},
|
||||
"uriErrorMsg": {
|
||||
"message": "Для URI требуется соответствующий префикс HTTP / HTTPS."
|
||||
"message": "Для URI требуется соответствующий префикс HTTP/HTTPS."
|
||||
},
|
||||
"usaOnly": {
|
||||
"message": "Только США",
|
||||
@ -787,19 +865,19 @@
|
||||
"message": "Используется различными клиентами"
|
||||
},
|
||||
"useOldUI": {
|
||||
"message": "Использовать старый интерфейс"
|
||||
"message": "Использовать старый интерфейс пользователя"
|
||||
},
|
||||
"validFileImport": {
|
||||
"message": "Вы должны выбрать действительный файл для импорта."
|
||||
"message": "Вам нужно выбрать правильный файл для импорта."
|
||||
},
|
||||
"vaultCreated": {
|
||||
"message": "Создано хранилище"
|
||||
"message": "Кошелек был создан"
|
||||
},
|
||||
"viewAccount": {
|
||||
"message": "Посмотреть аккаунт"
|
||||
"message": "Посмотреть счет"
|
||||
},
|
||||
"visitWebSite": {
|
||||
"message": "Посетите наш сайт"
|
||||
"message": "Перейти на наш сайт"
|
||||
},
|
||||
"warning": {
|
||||
"message": "Предупреждение"
|
||||
@ -811,7 +889,7 @@
|
||||
"message": "Что это?"
|
||||
},
|
||||
"yourSigRequested": {
|
||||
"message": "Ваша подпись запрашивается"
|
||||
"message": "Запрашивается ваша подпись"
|
||||
},
|
||||
"youSign": {
|
||||
"message": "Вы подписываете"
|
||||
|
@ -223,7 +223,7 @@
|
||||
"done": {
|
||||
"message": "Končano"
|
||||
},
|
||||
"downloadStatelogs": {
|
||||
"downloadStateLogs": {
|
||||
"message": "Prenesi state dnevnike"
|
||||
},
|
||||
"edit": {
|
||||
|
@ -223,7 +223,7 @@
|
||||
"done": {
|
||||
"message": "เสร็จสิ้น"
|
||||
},
|
||||
"downloadStatelogs": {
|
||||
"downloadStateLogs": {
|
||||
"message": "ดาวน์โหลดล็อกสถานะ"
|
||||
},
|
||||
"edit": {
|
||||
|
@ -171,6 +171,9 @@
|
||||
"customGas": {
|
||||
"message": "自訂 Gas"
|
||||
},
|
||||
"customToken": {
|
||||
"message": "自訂代幣"
|
||||
},
|
||||
"customize": {
|
||||
"message": "自訂"
|
||||
},
|
||||
@ -184,7 +187,7 @@
|
||||
"message": "小數點精度"
|
||||
},
|
||||
"defaultNetwork": {
|
||||
"message": "預設Ether交易網路為主網(Main Net)。"
|
||||
"message": "預設 Ether 交易網路為主網路(Main Net)。"
|
||||
},
|
||||
"denExplainer": {
|
||||
"message": "你的 DEN 是在你的 MetaMask 中的加密密碼儲存庫。"
|
||||
@ -215,7 +218,7 @@
|
||||
"message": "從 ShapeShift 存入"
|
||||
},
|
||||
"depositShapeShiftExplainer": {
|
||||
"message": "如果你擁有其他加密貨幣,你可以直接交易並存入 Ether 到你的MetaMask錢包。不需要開帳戶。"
|
||||
"message": "如果你擁有其他加密貨幣,你可以直接交易並存入 Ether 到你的 MetaMask 錢包。不需要開帳戶。"
|
||||
},
|
||||
"details": {
|
||||
"message": "詳情"
|
||||
@ -227,12 +230,12 @@
|
||||
"message": "直接存入 Ether"
|
||||
},
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "如果你已經擁有了一些Ether,使用直接存入功能是讓你的新錢包最快取得Ether的方式。"
|
||||
"message": "如果你已經擁有了一些 Ether,使用直接存入功能是讓你的新錢包最快取得 Ether 的方式。"
|
||||
},
|
||||
"done": {
|
||||
"message": "完成"
|
||||
},
|
||||
"downloadStatelogs": {
|
||||
"downloadStateLogs": {
|
||||
"message": "下載狀態紀錄"
|
||||
},
|
||||
"dropped": {
|
||||
@ -285,6 +288,9 @@
|
||||
"message": "檔案導入失敗?點擊這裡!",
|
||||
"description": "Helps user import their account from a JSON file"
|
||||
},
|
||||
"followTwitter": {
|
||||
"message": "追蹤 Twitter"
|
||||
},
|
||||
"from": {
|
||||
"message": "來源地址"
|
||||
},
|
||||
@ -313,6 +319,9 @@
|
||||
"gasLimitTooLow": {
|
||||
"message": "Gas 上限至少為 21000"
|
||||
},
|
||||
"generatingSeed": {
|
||||
"message": "產生助憶詞中..."
|
||||
},
|
||||
"gasPrice": {
|
||||
"message": "Gas 價格 (GWEI)"
|
||||
},
|
||||
@ -362,6 +371,9 @@
|
||||
"importAccount": {
|
||||
"message": "導入帳戶"
|
||||
},
|
||||
"importAccountMsg": {
|
||||
"message":" 匯入的帳戶與您原有 MetaMask 帳戶的助憶詞並無關聯. 請查看與導入帳戶相關的資料 "
|
||||
},
|
||||
"importAnAccount": {
|
||||
"message": "導入一個帳戶"
|
||||
},
|
||||
@ -400,12 +412,15 @@
|
||||
"message": "無效的 RPC URI"
|
||||
},
|
||||
"jsonFail": {
|
||||
"message": "有東西出錯了. 請確認你的 JSON 檔案格式正確."
|
||||
"message": "有東西出錯了. 請確認你的 JSON 檔案格式正確。"
|
||||
},
|
||||
"jsonFile": {
|
||||
"message": "JSON 檔案",
|
||||
"description": "format for importing an account"
|
||||
},
|
||||
"keepTrackTokens": {
|
||||
"message": "持續追蹤您 MetaMask 帳戶中的代幣。"
|
||||
},
|
||||
"kovan": {
|
||||
"message": "Kovan 測試網路"
|
||||
},
|
||||
@ -415,6 +430,9 @@
|
||||
"max": {
|
||||
"message": "最大值"
|
||||
},
|
||||
"learnMore": {
|
||||
"message": "了解更多。"
|
||||
},
|
||||
"lessThanMax": {
|
||||
"message": "必須小於等於 $1.",
|
||||
"description": "helper for inputting hex as decimal input"
|
||||
@ -437,17 +455,20 @@
|
||||
"localhost": {
|
||||
"message": "Localhost 8545"
|
||||
},
|
||||
"login": {
|
||||
"message": "登入"
|
||||
},
|
||||
"logout": {
|
||||
"message": "登出"
|
||||
},
|
||||
"loose": {
|
||||
"message": "非Metamask帳號"
|
||||
"message": "非 MetaMask 帳號"
|
||||
},
|
||||
"loweCaseWords": {
|
||||
"message": "助憶詞僅包含小寫字元"
|
||||
},
|
||||
"mainnet": {
|
||||
"message": "主乙太坊網路"
|
||||
"message": "乙太坊 主網路"
|
||||
},
|
||||
"message": {
|
||||
"message": "訊息"
|
||||
@ -465,7 +486,7 @@
|
||||
"message": "必須選擇至少 1 代幣."
|
||||
},
|
||||
"needEtherInWallet": {
|
||||
"message": "要使用 MetaMask 存取 DAPP時,您的錢包中需要有 Ether。"
|
||||
"message": "要使用 MetaMask 存取 DAPP 時,您的錢包中需要有 Ether。"
|
||||
},
|
||||
"needImportFile": {
|
||||
"message": "您必須選擇一個檔案來導入。",
|
||||
@ -475,6 +496,9 @@
|
||||
"message": "您必須為選擇好的檔案輸入密碼。",
|
||||
"description": "Password and file needed to import an account"
|
||||
},
|
||||
"negativeETH": {
|
||||
"message": "不能送出負值的 ETH。"
|
||||
},
|
||||
"networks": {
|
||||
"message": "網路"
|
||||
},
|
||||
@ -525,6 +549,9 @@
|
||||
"message": "或",
|
||||
"description": "choice between creating or importing a new account"
|
||||
},
|
||||
"passwordCorrect": {
|
||||
"message": "請確認您的密碼是正確的。"
|
||||
},
|
||||
"passwordMismatch": {
|
||||
"message": "密碼不一致",
|
||||
"description": "in password creation process, the two new password fields did not match"
|
||||
@ -546,6 +573,12 @@
|
||||
"pleaseReviewTransaction": {
|
||||
"message": "請檢查你的交易。"
|
||||
},
|
||||
"popularTokens": {
|
||||
"message": "常見的代幣"
|
||||
},
|
||||
"privacyMsg": {
|
||||
"message": "隱私政策"
|
||||
},
|
||||
"privateKey": {
|
||||
"message": "私鑰",
|
||||
"description": "select this type of file to use to import an account"
|
||||
@ -681,6 +714,9 @@
|
||||
"onlySendToEtherAddress": {
|
||||
"message": "只發送 ETH 到乙太坊地址."
|
||||
},
|
||||
"searchTokens": {
|
||||
"message": "搜尋代幣"
|
||||
},
|
||||
"sendTokensAnywhere": {
|
||||
"message": "發送代幣給擁有乙太坊帳戶的任何人"
|
||||
},
|
||||
@ -700,13 +736,16 @@
|
||||
"message": "顯示 QR Code"
|
||||
},
|
||||
"sign": {
|
||||
"message": "簽名"
|
||||
"message": "簽署"
|
||||
},
|
||||
"signed": {
|
||||
"message": "已簽署"
|
||||
},
|
||||
"signMessage": {
|
||||
"message": "簽署訊息"
|
||||
},
|
||||
"signNotice": {
|
||||
"message": "簽署此訊息可能會產生危險的副作用。 \n只從你完全信任的網站上簽名。這種危險的方法;將在未來的版本中被移除。"
|
||||
"message": "簽署此訊息可能會產生危險地副作用。 \n只從你完全信任的網站上簽署。這種危險的方法;將在未來的版本中被移除。"
|
||||
},
|
||||
"sigRequest": {
|
||||
"message": "請求簽署"
|
||||
@ -767,7 +806,7 @@
|
||||
"message": "代幣餘額:"
|
||||
},
|
||||
"tokenSelection": {
|
||||
"message": "搜尋代幣或是從熱門代幣列表中選擇。"
|
||||
"message": "搜尋代幣或是從常見代幣列表中選擇。"
|
||||
},
|
||||
"tokenSymbol": {
|
||||
"message": "代幣代號"
|
||||
@ -804,7 +843,7 @@
|
||||
"message": "歡迎使用新版界面 (Beta)"
|
||||
},
|
||||
"uiWelcomeMessage": {
|
||||
"message": "你現在正在使用新的 Metamask 界面。試試諸如發送代幣等新功能,有任何問題請告知我們。"
|
||||
"message": "你現在正在使用新版 MetaMask 界面。試試諸如發送代幣等新功能吧,有任何問題請告知我們。"
|
||||
},
|
||||
"unapproved": {
|
||||
"message": "未同意"
|
||||
|
@ -3,10 +3,10 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1 user-scalable=no">
|
||||
<title>MetaMask Plugin</title>
|
||||
<title>MetaMask</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app-content"></div>
|
||||
<script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="./ui.js" type="text/javascript" charset="utf-8"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "__MSG_appName__",
|
||||
"short_name": "__MSG_appName__",
|
||||
"version": "4.4.0",
|
||||
"version": "4.5.5",
|
||||
"manifest_version": 2,
|
||||
"author": "https://metamask.io",
|
||||
"description": "__MSG_appDescription__",
|
||||
@ -27,8 +27,8 @@
|
||||
"default_locale": "en",
|
||||
"background": {
|
||||
"scripts": [
|
||||
"scripts/chromereload.js",
|
||||
"scripts/background.js"
|
||||
"chromereload.js",
|
||||
"background.js"
|
||||
],
|
||||
"persistent": true
|
||||
},
|
||||
@ -48,7 +48,7 @@
|
||||
"https://*/*"
|
||||
],
|
||||
"js": [
|
||||
"scripts/contentscript.js"
|
||||
"contentscript.js"
|
||||
],
|
||||
"run_at": "document_start",
|
||||
"all_frames": true
|
||||
@ -62,7 +62,7 @@
|
||||
"https://*.infura.io/"
|
||||
],
|
||||
"web_accessible_resources": [
|
||||
"scripts/inpage.js"
|
||||
"inpage.js"
|
||||
],
|
||||
"externally_connectable": {
|
||||
"matches": [
|
||||
|
@ -11,6 +11,6 @@
|
||||
</head>
|
||||
<body class="notification" style="height:600px;">
|
||||
<div id="app-content"></div>
|
||||
<script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="./ui.js" type="text/javascript" charset="utf-8"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,10 +3,10 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1 user-scalable=no">
|
||||
<title>MetaMask Plugin</title>
|
||||
<title>MetaMask</title>
|
||||
</head>
|
||||
<body style="width:357px; height:600px;">
|
||||
<div id="app-content"></div>
|
||||
<script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="./ui.js" type="text/javascript" charset="utf-8"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -19,10 +19,11 @@ const setupRaven = require('./lib/setupRaven')
|
||||
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 STORAGE_KEY = 'metamask-config'
|
||||
const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
|
||||
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
|
||||
|
||||
window.log = log
|
||||
log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn')
|
||||
@ -58,7 +59,8 @@ setupMetamaskMeshMetrics()
|
||||
|
||||
async function initialize () {
|
||||
const initState = await loadStateFromPersistence()
|
||||
await setupController(initState)
|
||||
const initLangCode = await getFirstPreferredLangCode()
|
||||
await setupController(initState, initLangCode)
|
||||
log.debug('MetaMask initialization complete.')
|
||||
}
|
||||
|
||||
@ -76,6 +78,16 @@ async function loadStateFromPersistence () {
|
||||
diskStore.getState() ||
|
||||
migrator.generateInitialState(firstTimeState)
|
||||
|
||||
// report migration errors to sentry
|
||||
migrator.on('error', (err) => {
|
||||
// get vault structure without secrets
|
||||
const vaultStructure = getObjStructure(versionedData)
|
||||
raven.captureException(err, {
|
||||
// "extra" key is required by Sentry
|
||||
extra: { vaultStructure },
|
||||
})
|
||||
})
|
||||
|
||||
// migrate data
|
||||
versionedData = await migrator.migrateData(versionedData)
|
||||
if (!versionedData) {
|
||||
@ -83,13 +95,20 @@ async function loadStateFromPersistence () {
|
||||
}
|
||||
|
||||
// write to disk
|
||||
if (localStore.isSupported) localStore.set(versionedData)
|
||||
if (localStore.isSupported) {
|
||||
localStore.set(versionedData)
|
||||
} else {
|
||||
// throw in setTimeout so as to not block boot
|
||||
setTimeout(() => {
|
||||
throw new Error('MetaMask - Localstore not supported')
|
||||
})
|
||||
}
|
||||
|
||||
// return just the data
|
||||
return versionedData.data
|
||||
}
|
||||
|
||||
function setupController (initState) {
|
||||
function setupController (initState, initLangCode) {
|
||||
//
|
||||
// MetaMask Controller
|
||||
//
|
||||
@ -101,6 +120,8 @@ function setupController (initState) {
|
||||
showUnapprovedTx: triggerUi,
|
||||
// initial state
|
||||
initState,
|
||||
// initial locale code
|
||||
initLangCode,
|
||||
// platform specific api
|
||||
platform,
|
||||
encryptor: isEdge ? new EdgeEncryptor() : undefined,
|
||||
|
@ -13,7 +13,7 @@ const DEFAULT_RPC = 'rinkeby'
|
||||
const OLD_UI_NETWORK_TYPE = 'network'
|
||||
const BETA_UI_NETWORK_TYPE = 'networkBeta'
|
||||
|
||||
global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
|
||||
global.METAMASK_DEBUG = process.env.METAMASK_DEBUG
|
||||
|
||||
module.exports = {
|
||||
network: {
|
||||
|
@ -7,8 +7,8 @@ const ObjectMultiplex = require('obj-multiplex')
|
||||
const extension = require('extensionizer')
|
||||
const PortStream = require('./lib/port-stream.js')
|
||||
|
||||
const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'scripts', 'inpage.js')).toString()
|
||||
const inpageSuffix = '//# sourceURL=' + extension.extension.getURL('scripts/inpage.js') + '\n'
|
||||
const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js')).toString()
|
||||
const inpageSuffix = '//# sourceURL=' + extension.extension.getURL('inpage.js') + '\n'
|
||||
const inpageBundle = inpageContent + inpageSuffix
|
||||
|
||||
// Eventually this streaming injection could be replaced with:
|
||||
@ -96,7 +96,7 @@ function logStreamDisconnectWarning (remoteLabel, err) {
|
||||
}
|
||||
|
||||
function shouldInjectWeb3 () {
|
||||
return doctypeCheck() && suffixCheck()
|
||||
return doctypeCheck() && suffixCheck()
|
||||
&& documentElementCheck() && !blacklistedDomainCheck()
|
||||
}
|
||||
|
||||
@ -131,7 +131,11 @@ function documentElementCheck () {
|
||||
}
|
||||
|
||||
function blacklistedDomainCheck () {
|
||||
var blacklistedDomains = ['uscourts.gov', 'dropbox.com']
|
||||
var blacklistedDomains = [
|
||||
'uscourts.gov',
|
||||
'dropbox.com',
|
||||
'webbyawards.com',
|
||||
]
|
||||
var currentUrl = window.location.href
|
||||
var currentRegex
|
||||
for (let i = 0; i < blacklistedDomains.length; i++) {
|
||||
|
@ -41,9 +41,9 @@ class BlacklistController {
|
||||
|
||||
scheduleUpdates () {
|
||||
if (this._phishingUpdateIntervalRef) return
|
||||
this.updatePhishingList()
|
||||
this.updatePhishingList().catch(log.warn)
|
||||
this._phishingUpdateIntervalRef = setInterval(() => {
|
||||
this.updatePhishingList()
|
||||
this.updatePhishingList().catch(log.warn)
|
||||
}, POLLING_INTERVAL)
|
||||
}
|
||||
|
||||
@ -57,4 +57,3 @@ class BlacklistController {
|
||||
}
|
||||
|
||||
module.exports = BlacklistController
|
||||
|
||||
|
@ -43,20 +43,19 @@ class CurrencyController {
|
||||
this.store.updateState({ conversionDate })
|
||||
}
|
||||
|
||||
updateConversionRate () {
|
||||
const currentCurrency = this.getCurrentCurrency()
|
||||
return fetch(`https://api.infura.io/v1/ticker/eth${currentCurrency.toLowerCase()}`)
|
||||
.then(response => response.json())
|
||||
.then((parsedResponse) => {
|
||||
async updateConversionRate () {
|
||||
let currentCurrency
|
||||
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))
|
||||
}).catch((err) => {
|
||||
if (err) {
|
||||
console.warn('MetaMask - Failed to query currency conversion.')
|
||||
this.setConversionRate(0)
|
||||
this.setConversionDate('N/A')
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
log.warn(`MetaMask - Failed to query currency conversion:`, currentCurrency, err)
|
||||
this.setConversionRate(0)
|
||||
this.setConversionDate('N/A')
|
||||
}
|
||||
}
|
||||
|
||||
scheduleConversionInterval () {
|
||||
|
@ -19,15 +19,13 @@ class InfuraController {
|
||||
|
||||
// Responsible for retrieving the status of Infura's nodes. Can return either
|
||||
// ok, degraded, or down.
|
||||
checkInfuraNetworkStatus () {
|
||||
return fetch('https://api.infura.io/v1/status/metamask')
|
||||
.then(response => response.json())
|
||||
.then((parsedResponse) => {
|
||||
this.store.updateState({
|
||||
infuraNetworkStatus: parsedResponse,
|
||||
})
|
||||
return parsedResponse
|
||||
})
|
||||
async checkInfuraNetworkStatus () {
|
||||
const response = await fetch('https://api.infura.io/v1/status/metamask')
|
||||
const parsedResponse = await response.json()
|
||||
this.store.updateState({
|
||||
infuraNetworkStatus: parsedResponse,
|
||||
})
|
||||
return parsedResponse
|
||||
}
|
||||
|
||||
scheduleInfuraNetworkCheck () {
|
||||
@ -35,7 +33,7 @@ class InfuraController {
|
||||
clearInterval(this.conversionInterval)
|
||||
}
|
||||
this.conversionInterval = setInterval(() => {
|
||||
this.checkInfuraNetworkStatus()
|
||||
this.checkInfuraNetworkStatus().catch(log.warn)
|
||||
}, POLLING_INTERVAL)
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ class PreferencesController {
|
||||
tokens: [],
|
||||
useBlockie: false,
|
||||
featureFlags: {},
|
||||
currentLocale: opts.initLangCode,
|
||||
}, opts.initState)
|
||||
this.store = new ObservableStore(initState)
|
||||
}
|
||||
@ -24,6 +25,10 @@ class PreferencesController {
|
||||
return this.store.getState().useBlockie
|
||||
}
|
||||
|
||||
setCurrentLocale (key) {
|
||||
this.store.updateState({ currentLocale: key })
|
||||
}
|
||||
|
||||
setSelectedAddress (_address) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const address = normalizeAddress(_address)
|
||||
|
@ -45,18 +45,19 @@ class ShapeshiftController {
|
||||
})
|
||||
}
|
||||
|
||||
updateTx (tx) {
|
||||
const url = `https://shapeshift.io/txStat/${tx.depositAddress}`
|
||||
return fetch(url)
|
||||
.then((response) => {
|
||||
return response.json()
|
||||
}).then((json) => {
|
||||
async updateTx (tx) {
|
||||
try {
|
||||
const url = `https://shapeshift.io/txStat/${tx.depositAddress}`
|
||||
const response = await fetch(url)
|
||||
const json = await response.json()
|
||||
tx.response = json
|
||||
if (tx.response.status === 'complete') {
|
||||
tx.time = new Date().getTime()
|
||||
}
|
||||
return tx
|
||||
})
|
||||
} catch (err) {
|
||||
log.warn(err)
|
||||
}
|
||||
}
|
||||
|
||||
saveTx (tx) {
|
||||
|
@ -161,9 +161,11 @@ module.exports = class TransactionController extends EventEmitter {
|
||||
this.emit(`${txMeta.id}:unapproved`, txMeta)
|
||||
}
|
||||
|
||||
async newUnapprovedTransaction (txParams) {
|
||||
async newUnapprovedTransaction (txParams, opts = {}) {
|
||||
log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`)
|
||||
const initialTxMeta = await this.addUnapprovedTransaction(txParams)
|
||||
initialTxMeta.origin = opts.origin
|
||||
this.txStateManager.updateTx(initialTxMeta, '#newUnapprovedTransaction - adding the origin')
|
||||
// listen for tx completion (success, fail)
|
||||
return new Promise((resolve, reject) => {
|
||||
this.txStateManager.once(`${initialTxMeta.id}:finished`, (finishedTxMeta) => {
|
||||
@ -183,14 +185,15 @@ module.exports = class TransactionController extends EventEmitter {
|
||||
|
||||
async addUnapprovedTransaction (txParams) {
|
||||
// validate
|
||||
await this.txGasUtil.validateTxParams(txParams)
|
||||
const normalizedTxParams = this._normalizeTxParams(txParams)
|
||||
this._validateTxParams(normalizedTxParams)
|
||||
// construct txMeta
|
||||
const txMeta = this.txStateManager.generateTxMeta({txParams})
|
||||
let txMeta = this.txStateManager.generateTxMeta({ txParams: normalizedTxParams })
|
||||
this.addTx(txMeta)
|
||||
this.emit('newUnapprovedTx', txMeta)
|
||||
// add default tx params
|
||||
try {
|
||||
await this.addTxDefaults(txMeta)
|
||||
txMeta = await this.addTxDefaults(txMeta)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
this.txStateManager.setTxStatusFailed(txMeta.id, error)
|
||||
@ -250,7 +253,7 @@ module.exports = class TransactionController extends EventEmitter {
|
||||
// wait for a nonce
|
||||
nonceLock = await this.nonceTracker.getNonceLock(fromAddress)
|
||||
// add nonce to txParams
|
||||
// if txMeta has lastGasPrice then it is a retry at same nonce with higher
|
||||
// if txMeta has lastGasPrice then it is a retry at same nonce with higher
|
||||
// gas price transaction and their for the nonce should not be calculated
|
||||
const nonce = txMeta.lastGasPrice ? txMeta.txParams.nonce : nonceLock.nextNonce
|
||||
txMeta.txParams.nonce = ethUtil.addHexPrefix(nonce.toString(16))
|
||||
@ -273,12 +276,14 @@ module.exports = class TransactionController extends EventEmitter {
|
||||
|
||||
async signTransaction (txId) {
|
||||
const txMeta = this.txStateManager.getTx(txId)
|
||||
const txParams = txMeta.txParams
|
||||
const fromAddress = txParams.from
|
||||
// add network/chain id
|
||||
txParams.chainId = ethUtil.addHexPrefix(this.getChainId().toString(16))
|
||||
const chainId = this.getChainId()
|
||||
const txParams = Object.assign({}, txMeta.txParams, { chainId })
|
||||
// sign tx
|
||||
const fromAddress = txParams.from
|
||||
const ethTx = new Transaction(txParams)
|
||||
await this.signEthTx(ethTx, fromAddress)
|
||||
// set state to signed
|
||||
this.txStateManager.setTxStatusSigned(txMeta.id)
|
||||
const rawTx = ethUtil.bufferToHex(ethTx.serialize())
|
||||
return rawTx
|
||||
@ -309,6 +314,60 @@ module.exports = class TransactionController extends EventEmitter {
|
||||
// PRIVATE METHODS
|
||||
//
|
||||
|
||||
_normalizeTxParams (txParams) {
|
||||
// functions that handle normalizing of that key in txParams
|
||||
const whiteList = {
|
||||
from: from => ethUtil.addHexPrefix(from).toLowerCase(),
|
||||
to: to => ethUtil.addHexPrefix(txParams.to).toLowerCase(),
|
||||
nonce: nonce => ethUtil.addHexPrefix(nonce),
|
||||
value: value => ethUtil.addHexPrefix(value),
|
||||
data: data => ethUtil.addHexPrefix(data),
|
||||
gas: gas => ethUtil.addHexPrefix(gas),
|
||||
gasPrice: gasPrice => ethUtil.addHexPrefix(gasPrice),
|
||||
}
|
||||
|
||||
// apply only keys in the whiteList
|
||||
const normalizedTxParams = {}
|
||||
Object.keys(whiteList).forEach((key) => {
|
||||
if (txParams[key]) normalizedTxParams[key] = whiteList[key](txParams[key])
|
||||
})
|
||||
|
||||
return normalizedTxParams
|
||||
}
|
||||
|
||||
_validateTxParams (txParams) {
|
||||
this._validateFrom(txParams)
|
||||
this._validateRecipient(txParams)
|
||||
if ('value' in txParams) {
|
||||
const value = txParams.value.toString()
|
||||
if (value.includes('-')) {
|
||||
throw new Error(`Invalid transaction value of ${txParams.value} not a positive number.`)
|
||||
}
|
||||
|
||||
if (value.includes('.')) {
|
||||
throw new Error(`Invalid transaction value of ${txParams.value} number must be in wei`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_validateFrom (txParams) {
|
||||
if ( !(typeof txParams.from === 'string') ) throw new Error(`Invalid from address ${txParams.from} not a string`)
|
||||
if (!ethUtil.isValidAddress(txParams.from)) throw new Error('Invalid from address')
|
||||
}
|
||||
|
||||
_validateRecipient (txParams) {
|
||||
if (txParams.to === '0x' || txParams.to === null ) {
|
||||
if (txParams.data) {
|
||||
delete txParams.to
|
||||
} else {
|
||||
throw new Error('Invalid recipient address')
|
||||
}
|
||||
} else if ( txParams.to !== undefined && !ethUtil.isValidAddress(txParams.to) ) {
|
||||
throw new Error('Invalid recipient address')
|
||||
}
|
||||
return txParams
|
||||
}
|
||||
|
||||
_markNonceDuplicatesDropped (txId) {
|
||||
this.txStateManager.setTxStatusConfirmed(txId)
|
||||
// get the confirmed transactions nonce and from address
|
||||
|
@ -1,6 +1,6 @@
|
||||
// test and development environment variables
|
||||
const env = process.env.METAMASK_ENV
|
||||
const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
|
||||
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
|
||||
|
||||
//
|
||||
// The default state of MetaMask
|
||||
|
@ -9,7 +9,7 @@ const setupDappAutoReload = require('./lib/auto-reload.js')
|
||||
const MetamaskInpageProvider = require('./lib/inpage-provider.js')
|
||||
restoreContextAfterImports()
|
||||
|
||||
const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
|
||||
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
|
||||
window.log = log
|
||||
log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn')
|
||||
|
||||
|
27
app/scripts/lib/extractEthjsErrorMessage.js
Normal file
27
app/scripts/lib/extractEthjsErrorMessage.js
Normal file
@ -0,0 +1,27 @@
|
||||
const ethJsRpcSlug = 'Error: [ethjs-rpc] rpc error with payload '
|
||||
const errorLabelPrefix = 'Error: '
|
||||
|
||||
module.exports = extractEthjsErrorMessage
|
||||
|
||||
|
||||
//
|
||||
// ethjs-rpc provides overly verbose error messages
|
||||
// if we detect this type of message, we extract the important part
|
||||
// Below is an example input and output
|
||||
//
|
||||
// Error: [ethjs-rpc] rpc error with payload {"id":3947817945380,"jsonrpc":"2.0","params":["0xf8eb8208708477359400830398539406012c8cf97bead5deae237070f9587f8e7a266d80b8843d7d3f5a0000000000000000000000000000000000000000000000000000000000081d1a000000000000000000000000000000000000000000000000001ff973cafa800000000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000000000000000000000000000000000000003f48025a04c32a9b630e0d9e7ff361562d850c86b7a884908135956a7e4a336fa0300d19ca06830776423f25218e8d19b267161db526e66895567147015b1f3fc47aef9a3c7"],"method":"eth_sendRawTransaction"} Error: replacement transaction underpriced
|
||||
//
|
||||
// Transaction Failed: replacement transaction underpriced
|
||||
//
|
||||
|
||||
|
||||
function extractEthjsErrorMessage(errorMessage) {
|
||||
const isEthjsRpcError = errorMessage.includes(ethJsRpcSlug)
|
||||
if (isEthjsRpcError) {
|
||||
const payloadAndError = errorMessage.slice(ethJsRpcSlug.length)
|
||||
const originalError = payloadAndError.slice(payloadAndError.indexOf(errorLabelPrefix) + errorLabelPrefix.length)
|
||||
return originalError
|
||||
} else {
|
||||
return errorMessage
|
||||
}
|
||||
}
|
18
app/scripts/lib/get-first-preferred-lang-code.js
Normal file
18
app/scripts/lib/get-first-preferred-lang-code.js
Normal file
@ -0,0 +1,18 @@
|
||||
const extension = require('extensionizer')
|
||||
const promisify = require('pify')
|
||||
const allLocales = require('../../_locales/index.json')
|
||||
|
||||
const existingLocaleCodes = allLocales.map(locale => locale.code.toLowerCase().replace('_', '-'))
|
||||
|
||||
async function getFirstPreferredLangCode () {
|
||||
const userPreferredLocaleCodes = await promisify(
|
||||
extension.i18n.getAcceptLanguages,
|
||||
{ errorFirst: false }
|
||||
)()
|
||||
const firstPreferredLangCode = userPreferredLocaleCodes
|
||||
.map(code => code.toLowerCase())
|
||||
.find(code => existingLocaleCodes.includes(code))
|
||||
return firstPreferredLangCode || 'en'
|
||||
}
|
||||
|
||||
module.exports = getFirstPreferredLangCode
|
33
app/scripts/lib/getObjStructure.js
Normal file
33
app/scripts/lib/getObjStructure.js
Normal file
@ -0,0 +1,33 @@
|
||||
const clone = require('clone')
|
||||
|
||||
module.exports = getObjStructure
|
||||
|
||||
// This will create an object that represents the structure of the given object
|
||||
// it replaces all values with the result of their type
|
||||
|
||||
// {
|
||||
// "data": {
|
||||
// "CurrencyController": {
|
||||
// "conversionDate": "number",
|
||||
// "conversionRate": "number",
|
||||
// "currentCurrency": "string"
|
||||
// }
|
||||
// }
|
||||
|
||||
function getObjStructure(obj) {
|
||||
const structure = clone(obj)
|
||||
return deepMap(structure, (value) => {
|
||||
return value === null ? 'null' : typeof value
|
||||
})
|
||||
}
|
||||
|
||||
function deepMap(target = {}, visit) {
|
||||
Object.entries(target).forEach(([key, value]) => {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
target[key] = deepMap(value, visit)
|
||||
} else {
|
||||
target[key] = visit(value)
|
||||
}
|
||||
})
|
||||
return target
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
class Migrator {
|
||||
const EventEmitter = require('events')
|
||||
|
||||
class Migrator extends EventEmitter {
|
||||
|
||||
constructor (opts = {}) {
|
||||
super()
|
||||
const migrations = opts.migrations || []
|
||||
// sort migrations by version
|
||||
this.migrations = migrations.sort((a, b) => a.version - b.version)
|
||||
@ -12,13 +15,29 @@ class Migrator {
|
||||
|
||||
// run all pending migrations on meta in place
|
||||
async migrateData (versionedData = this.generateInitialState()) {
|
||||
// get all migrations that have not yet been run
|
||||
const pendingMigrations = this.migrations.filter(migrationIsPending)
|
||||
|
||||
// perform each migration
|
||||
for (const index in pendingMigrations) {
|
||||
const migration = pendingMigrations[index]
|
||||
versionedData = await migration.migrate(versionedData)
|
||||
if (!versionedData.data) throw new Error('Migrator - migration returned empty data')
|
||||
if (versionedData.version !== undefined && versionedData.meta.version !== migration.version) throw new Error('Migrator - Migration did not update version number correctly')
|
||||
try {
|
||||
// attempt migration and validate
|
||||
const migratedData = await migration.migrate(versionedData)
|
||||
if (!migratedData.data) throw new Error('Migrator - migration returned empty data')
|
||||
if (migratedData.version !== undefined && migratedData.meta.version !== migration.version) throw new Error('Migrator - Migration did not update version number correctly')
|
||||
// accept the migration as good
|
||||
versionedData = migratedData
|
||||
} catch (err) {
|
||||
// rewrite error message to add context without clobbering stack
|
||||
const originalErrorMessage = err.message
|
||||
err.message = `MetaMask Migration Error #${migration.version}: ${originalErrorMessage}`
|
||||
console.warn(err.stack)
|
||||
// emit error instead of throw so as to not break the run (gracefully fail)
|
||||
this.emit('error', err)
|
||||
// stop migrating and use state as is
|
||||
return versionedData
|
||||
}
|
||||
}
|
||||
|
||||
return versionedData
|
||||
|
@ -31,14 +31,13 @@ class NonceTracker {
|
||||
const networkNonceResult = await this._getNetworkNextNonce(address)
|
||||
const highestLocallyConfirmed = this._getHighestLocallyConfirmed(address)
|
||||
const nextNetworkNonce = networkNonceResult.nonce
|
||||
const highestLocalNonce = highestLocallyConfirmed
|
||||
const highestSuggested = Math.max(nextNetworkNonce, highestLocalNonce)
|
||||
const highestSuggested = Math.max(nextNetworkNonce, highestLocallyConfirmed)
|
||||
|
||||
const pendingTxs = this.getPendingTransactions(address)
|
||||
const localNonceResult = this._getHighestContinuousFrom(pendingTxs, highestSuggested) || 0
|
||||
|
||||
nonceDetails.params = {
|
||||
highestLocalNonce,
|
||||
highestLocallyConfirmed,
|
||||
highestSuggested,
|
||||
nextNetworkNonce,
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
const ethJsRpcSlug = 'Error: [ethjs-rpc] rpc error with payload '
|
||||
const errorLabelPrefix = 'Error: '
|
||||
const extractEthjsErrorMessage = require('./extractEthjsErrorMessage')
|
||||
|
||||
module.exports = reportFailedTxToSentry
|
||||
|
||||
@ -9,30 +8,9 @@ module.exports = reportFailedTxToSentry
|
||||
//
|
||||
|
||||
function reportFailedTxToSentry({ raven, txMeta }) {
|
||||
const errorMessage = extractErrorMessage(txMeta.err.message)
|
||||
const errorMessage = 'Transaction Failed: ' + extractEthjsErrorMessage(txMeta.err.message)
|
||||
raven.captureMessage(errorMessage, {
|
||||
// "extra" key is required by Sentry
|
||||
extra: txMeta,
|
||||
})
|
||||
}
|
||||
|
||||
//
|
||||
// ethjs-rpc provides overly verbose error messages
|
||||
// if we detect this type of message, we extract the important part
|
||||
// Below is an example input and output
|
||||
//
|
||||
// Error: [ethjs-rpc] rpc error with payload {"id":3947817945380,"jsonrpc":"2.0","params":["0xf8eb8208708477359400830398539406012c8cf97bead5deae237070f9587f8e7a266d80b8843d7d3f5a0000000000000000000000000000000000000000000000000000000000081d1a000000000000000000000000000000000000000000000000001ff973cafa800000000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000000000000000000000000000000000000003f48025a04c32a9b630e0d9e7ff361562d850c86b7a884908135956a7e4a336fa0300d19ca06830776423f25218e8d19b267161db526e66895567147015b1f3fc47aef9a3c7"],"method":"eth_sendRawTransaction"} Error: replacement transaction underpriced
|
||||
//
|
||||
// Transaction Failed: replacement transaction underpriced
|
||||
//
|
||||
|
||||
function extractErrorMessage(errorMessage) {
|
||||
const isEthjsRpcError = errorMessage.includes(ethJsRpcSlug)
|
||||
if (isEthjsRpcError) {
|
||||
const payloadAndError = errorMessage.slice(ethJsRpcSlug.length)
|
||||
const originalError = payloadAndError.slice(payloadAndError.indexOf(errorLabelPrefix) + errorLabelPrefix.length)
|
||||
return `Transaction Failed: ${originalError}`
|
||||
} else {
|
||||
return `Transaction Failed: ${errorMessage}`
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
const Raven = require('raven-js')
|
||||
const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
|
||||
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'
|
||||
|
||||
@ -21,8 +22,22 @@ function setupRaven(opts) {
|
||||
const client = Raven.config(ravenTarget, {
|
||||
release,
|
||||
transport: function(opts) {
|
||||
// modify report urls
|
||||
const report = opts.data
|
||||
// simplify certain complex error messages
|
||||
report.exception.values.forEach(item => {
|
||||
let errorMessage = item.value
|
||||
// simplify ethjs error messages
|
||||
errorMessage = extractEthjsErrorMessage(errorMessage)
|
||||
// simplify 'Transaction Failed: known transaction'
|
||||
if (errorMessage.indexOf('Transaction Failed: known transaction') === 0) {
|
||||
// cut the hash from the error message
|
||||
errorMessage = 'Transaction Failed: known transaction'
|
||||
}
|
||||
// finalize
|
||||
item.value = errorMessage
|
||||
})
|
||||
|
||||
// modify report urls
|
||||
rewriteReportUrls(report)
|
||||
// make request normally
|
||||
client._makeRequest(opts)
|
||||
|
@ -4,7 +4,7 @@ const {
|
||||
BnMultiplyByFraction,
|
||||
bnToHex,
|
||||
} = require('./util')
|
||||
const { addHexPrefix, isValidAddress } = require('ethereumjs-util')
|
||||
const { addHexPrefix } = require('ethereumjs-util')
|
||||
const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send.
|
||||
|
||||
/*
|
||||
@ -52,7 +52,9 @@ module.exports = class TxGasUtil {
|
||||
// if recipient has no code, gas is 21k max:
|
||||
const recipient = txParams.to
|
||||
const hasRecipient = Boolean(recipient)
|
||||
const code = await this.query.getCode(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
|
||||
@ -98,30 +100,4 @@ module.exports = class TxGasUtil {
|
||||
// otherwise use blockGasLimit
|
||||
return bnToHex(upperGasLimitBn)
|
||||
}
|
||||
|
||||
async validateTxParams (txParams) {
|
||||
this.validateRecipient(txParams)
|
||||
if ('value' in txParams) {
|
||||
const value = txParams.value.toString()
|
||||
if (value.includes('-')) {
|
||||
throw new Error(`Invalid transaction value of ${txParams.value} not a positive number.`)
|
||||
}
|
||||
|
||||
if (value.includes('.')) {
|
||||
throw new Error(`Invalid transaction value of ${txParams.value} number must be in wei`)
|
||||
}
|
||||
}
|
||||
}
|
||||
validateRecipient (txParams) {
|
||||
if (txParams.to === '0x' || txParams.to === null ) {
|
||||
if (txParams.data) {
|
||||
delete txParams.to
|
||||
} else {
|
||||
throw new Error('Invalid recipient address')
|
||||
}
|
||||
} else if ( txParams.to !== undefined && !isValidAddress(txParams.to) ) {
|
||||
throw new Error('Invalid recipient address')
|
||||
}
|
||||
return txParams
|
||||
}
|
||||
}
|
||||
}
|
@ -38,11 +38,6 @@ module.exports = class TransactionStateManager extends EventEmitter {
|
||||
}, opts)
|
||||
}
|
||||
|
||||
// Returns the number of txs for the current network.
|
||||
getTxCount () {
|
||||
return this.getTxList().length
|
||||
}
|
||||
|
||||
getTxList () {
|
||||
const network = this.getNetwork()
|
||||
const fullTxList = this.getFullTxList()
|
||||
@ -88,7 +83,7 @@ module.exports = class TransactionStateManager extends EventEmitter {
|
||||
txMeta.history.push(snapshot)
|
||||
|
||||
const transactions = this.getFullTxList()
|
||||
const txCount = this.getTxCount()
|
||||
const txCount = transactions.length
|
||||
const txHistoryLimit = this.txHistoryLimit
|
||||
|
||||
// checks if the length of the tx history is
|
||||
@ -111,12 +106,13 @@ module.exports = class TransactionStateManager extends EventEmitter {
|
||||
}
|
||||
|
||||
updateTx (txMeta, note) {
|
||||
// validate txParams
|
||||
if (txMeta.txParams) {
|
||||
Object.keys(txMeta.txParams).forEach((key) => {
|
||||
const value = txMeta.txParams[key]
|
||||
if (typeof value !== 'string') console.error(`${key}: ${value} in txParams is not a string`)
|
||||
if (!ethUtil.isHexPrefixed(value)) console.error('is not hex prefixed, anything on txParams must be hex prefixed')
|
||||
})
|
||||
if (typeof txMeta.txParams.data === 'undefined') {
|
||||
delete txMeta.txParams.data
|
||||
}
|
||||
|
||||
this.validateTxParams(txMeta.txParams)
|
||||
}
|
||||
|
||||
// create txMeta snapshot for history
|
||||
@ -144,6 +140,23 @@ module.exports = class TransactionStateManager extends EventEmitter {
|
||||
this.updateTx(txMeta, `txStateManager#updateTxParams`)
|
||||
}
|
||||
|
||||
// validates txParams members by type
|
||||
validateTxParams(txParams) {
|
||||
Object.keys(txParams).forEach((key) => {
|
||||
const value = txParams[key]
|
||||
// validate types
|
||||
switch (key) {
|
||||
case 'chainId':
|
||||
if (typeof value !== 'number' && typeof value !== 'string') throw new Error(`${key} in txParams is not a Number or hex string. got: (${value})`)
|
||||
break
|
||||
default:
|
||||
if (typeof value !== 'string') throw new Error(`${key} in txParams is not a string. got: (${value})`)
|
||||
if (!ethUtil.isHexPrefixed(value)) throw new Error(`${key} in txParams is not hex prefixed. got: (${value})`)
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
Takes an object of fields to search for eg:
|
||||
let thingsToLookFor = {
|
||||
|
@ -49,7 +49,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {Object} opts
|
||||
* @param {Object} opts
|
||||
*/
|
||||
constructor (opts) {
|
||||
super()
|
||||
@ -57,7 +57,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
this.defaultMaxListeners = 20
|
||||
|
||||
this.sendUpdate = debounce(this.privateSendUpdate.bind(this), 200)
|
||||
|
||||
this.opts = opts
|
||||
const initState = opts.initState || {}
|
||||
this.recordFirstTimeInfo(initState)
|
||||
@ -82,6 +81,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
// preferences controller
|
||||
this.preferencesController = new PreferencesController({
|
||||
initState: initState.PreferencesController,
|
||||
initLangCode: opts.initLangCode,
|
||||
})
|
||||
|
||||
// currency controller
|
||||
@ -241,6 +241,11 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
static: {
|
||||
eth_syncing: false,
|
||||
web3_clientVersion: `MetaMask/v${version}`,
|
||||
eth_sendTransaction: (payload, next, end) => {
|
||||
const origin = payload.origin
|
||||
const txParams = payload.params[0]
|
||||
nodeify(this.txController.newUnapprovedTransaction, this.txController)(txParams, { origin }, end)
|
||||
},
|
||||
},
|
||||
// account mgmt
|
||||
getAccounts: (cb) => {
|
||||
@ -255,7 +260,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
cb(null, result)
|
||||
},
|
||||
// tx signing
|
||||
processTransaction: nodeify(async (txParams) => await this.txController.newUnapprovedTransaction(txParams), this),
|
||||
// old style msg signing
|
||||
processMessage: this.newUnsignedMessage.bind(this),
|
||||
// personal_sign msg signing
|
||||
@ -296,8 +300,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
/**
|
||||
* The metamask-state of the various controllers, made available to the UI
|
||||
*
|
||||
* @returns {Object} status
|
||||
*
|
||||
* @returns {Object} status
|
||||
*/
|
||||
getState () {
|
||||
const wallet = this.configManager.getWallet()
|
||||
@ -335,8 +339,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Returns an api-object which is consumed by the UI
|
||||
*
|
||||
* @returns {Object}
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
getApi () {
|
||||
const keyringController = this.keyringController
|
||||
@ -351,6 +355,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
getState: (cb) => cb(null, this.getState()),
|
||||
setCurrentCurrency: this.setCurrentCurrency.bind(this),
|
||||
setUseBlockie: this.setUseBlockie.bind(this),
|
||||
setCurrentLocale: this.setCurrentLocale.bind(this),
|
||||
markAccountsFound: this.markAccountsFound.bind(this),
|
||||
markPasswordForgotten: this.markPasswordForgotten.bind(this),
|
||||
unMarkPasswordForgotten: this.unMarkPasswordForgotten.bind(this),
|
||||
@ -365,7 +370,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
placeSeedWords: this.placeSeedWords.bind(this),
|
||||
verifySeedPhrase: nodeify(this.verifySeedPhrase, this),
|
||||
clearSeedWordCache: this.clearSeedWordCache.bind(this),
|
||||
resetAccount: this.resetAccount.bind(this),
|
||||
resetAccount: nodeify(this.resetAccount, this),
|
||||
importAccountWithStrategy: this.importAccountWithStrategy.bind(this),
|
||||
|
||||
// vault management
|
||||
@ -426,14 +431,14 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Creates a new Vault(?) and create a new keychain(?)
|
||||
*
|
||||
*
|
||||
* A vault is ...
|
||||
*
|
||||
*
|
||||
* A keychain is ...
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param {} password
|
||||
*
|
||||
*
|
||||
* @returns {} vault
|
||||
*/
|
||||
async createNewVaultAndKeychain (password) {
|
||||
@ -479,9 +484,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Retrieves the first Identiy from the passed Vault and selects the related address
|
||||
*
|
||||
*
|
||||
* An Identity is ...
|
||||
*
|
||||
*
|
||||
* @param {} vault
|
||||
*/
|
||||
selectFirstIdentity (vault) {
|
||||
@ -495,8 +500,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
//
|
||||
|
||||
/**
|
||||
* Adds a new account to ...
|
||||
*
|
||||
* Adds a new account to ...
|
||||
*
|
||||
* @returns {} keyState
|
||||
*/
|
||||
async addNewAccount () {
|
||||
@ -522,10 +527,10 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Adds the current vault's seed words to the UI's state tree.
|
||||
*
|
||||
*
|
||||
* Used when creating a first vault, to allow confirmation.
|
||||
* Also used when revealing the seed words in the confirmation view.
|
||||
*/
|
||||
*/
|
||||
placeSeedWords (cb) {
|
||||
|
||||
this.verifySeedPhrase()
|
||||
@ -540,7 +545,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Verifies the validity of the current vault's seed phrase.
|
||||
*
|
||||
*
|
||||
* Validity: seed phrase restores the accounts belonging to the current vault.
|
||||
*
|
||||
* Called when the first account is created and on unlocking the vault.
|
||||
@ -571,27 +576,32 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Remove the primary account seed phrase from the UI's state tree.
|
||||
*
|
||||
*
|
||||
* The seed phrase remains available in the background process.
|
||||
*
|
||||
*
|
||||
*/
|
||||
clearSeedWordCache (cb) {
|
||||
this.configManager.setSeedWords(null)
|
||||
cb(null, this.preferencesController.getSelectedAddress())
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ?
|
||||
*/
|
||||
resetAccount (cb) {
|
||||
async resetAccount (cb) {
|
||||
const selectedAddress = this.preferencesController.getSelectedAddress()
|
||||
this.txController.wipeTransactions(selectedAddress)
|
||||
cb(null, selectedAddress)
|
||||
|
||||
const networkController = this.networkController
|
||||
const oldType = networkController.getProviderConfig().type
|
||||
await networkController.setProviderType(oldType, true)
|
||||
|
||||
return selectedAddress
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports an account ... ?
|
||||
*
|
||||
*
|
||||
* @param {} strategy
|
||||
* @param {} args
|
||||
* @param {} cb
|
||||
@ -634,9 +644,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
}
|
||||
|
||||
// Prefixed Style Message Signing Methods:
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {} msgParams
|
||||
* @param {} cb
|
||||
*/
|
||||
@ -655,7 +665,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {} msgParams
|
||||
*/
|
||||
@ -676,7 +686,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
return this.getState()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {} msgParams
|
||||
*/
|
||||
@ -697,13 +707,13 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
return this.getState()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Account Restauration
|
||||
|
||||
/**
|
||||
* ?
|
||||
*
|
||||
*
|
||||
* @param {} migratorOutput
|
||||
*/
|
||||
restoreOldVaultAccounts (migratorOutput) {
|
||||
@ -714,7 +724,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
/**
|
||||
* ?
|
||||
*
|
||||
*
|
||||
* @param {} migratorOutput
|
||||
*/
|
||||
restoreOldLostAccounts (migratorOutput) {
|
||||
@ -728,9 +738,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Import (lost) Accounts
|
||||
*
|
||||
*
|
||||
* @param {Object} {lostAccounts} @Array accounts <{ address, privateKey }>
|
||||
*
|
||||
*
|
||||
* Uses the array's private keys to create a new Simple Key Pair keychain
|
||||
* and add it to the keyring controller.
|
||||
*/
|
||||
@ -823,7 +833,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
if (cb && typeof cb === 'function') {
|
||||
cb(null, this.getState())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cancelPersonalMessage (msgId, cb) {
|
||||
const messageManager = this.personalMessageManager
|
||||
@ -978,7 +988,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
const percentileNum = percentile(50, lowestPrices)
|
||||
const percentileNumBn = new BN(percentileNum)
|
||||
return '0x' + percentileNumBn.mul(GWEI_BN).toString(16)
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// CONFIG
|
||||
@ -1029,6 +1039,15 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
setCurrentLocale (key, cb) {
|
||||
try {
|
||||
this.preferencesController.setCurrentLocale(key)
|
||||
cb(null)
|
||||
} catch (err) {
|
||||
cb(err)
|
||||
}
|
||||
}
|
||||
|
||||
recordFirstTimeInfo (initState) {
|
||||
if (!('firstTimeInfo' in initState)) {
|
||||
initState.firstTimeInfo = {
|
||||
|
@ -27,8 +27,11 @@ module.exports = {
|
||||
|
||||
function transformState (state) {
|
||||
const newState = state
|
||||
if (newState.config.provider.type === 'testnet') {
|
||||
newState.config.provider.type = 'ropsten'
|
||||
const { config } = newState
|
||||
if ( config && config.provider ) {
|
||||
if (config.provider.type === 'testnet') {
|
||||
newState.config.provider.type = 'ropsten'
|
||||
}
|
||||
}
|
||||
return newState
|
||||
}
|
||||
|
@ -28,11 +28,14 @@ module.exports = {
|
||||
|
||||
function transformState (state) {
|
||||
const newState = state
|
||||
const transactions = newState.TransactionController.transactions
|
||||
newState.TransactionController.transactions = transactions.map((txMeta) => {
|
||||
if (!txMeta.err) return txMeta
|
||||
else if (txMeta.err.message === 'Gave up submitting tx.') txMeta.status = 'failed'
|
||||
return txMeta
|
||||
})
|
||||
const { TransactionController } = newState
|
||||
if (TransactionController && TransactionController.transactions) {
|
||||
const transactions = TransactionController.transactions
|
||||
newState.TransactionController.transactions = transactions.map((txMeta) => {
|
||||
if (!txMeta.err) return txMeta
|
||||
else if (txMeta.err.message === 'Gave up submitting tx.') txMeta.status = 'failed'
|
||||
return txMeta
|
||||
})
|
||||
}
|
||||
return newState
|
||||
}
|
||||
|
@ -28,14 +28,18 @@ module.exports = {
|
||||
|
||||
function transformState (state) {
|
||||
const newState = state
|
||||
const transactions = newState.TransactionController.transactions
|
||||
newState.TransactionController.transactions = transactions.map((txMeta) => {
|
||||
if (!txMeta.err) return txMeta
|
||||
if (txMeta.err === 'transaction with the same hash was already imported.') {
|
||||
txMeta.status = 'submitted'
|
||||
delete txMeta.err
|
||||
}
|
||||
return txMeta
|
||||
})
|
||||
const { TransactionController } = newState
|
||||
if (TransactionController && TransactionController.transactions) {
|
||||
const transactions = newState.TransactionController.transactions
|
||||
|
||||
newState.TransactionController.transactions = transactions.map((txMeta) => {
|
||||
if (!txMeta.err) return txMeta
|
||||
if (txMeta.err === 'transaction with the same hash was already imported.') {
|
||||
txMeta.status = 'submitted'
|
||||
delete txMeta.err
|
||||
}
|
||||
return txMeta
|
||||
})
|
||||
}
|
||||
return newState
|
||||
}
|
||||
|
@ -27,14 +27,17 @@ module.exports = {
|
||||
|
||||
function transformState (state) {
|
||||
const newState = state
|
||||
const transactions = newState.TransactionController.transactions
|
||||
newState.TransactionController.transactions = transactions.map((txMeta) => {
|
||||
if (!txMeta.status === 'failed') return txMeta
|
||||
if (txMeta.retryCount > 0 && txMeta.retryCount < 2) {
|
||||
txMeta.status = 'submitted'
|
||||
delete txMeta.err
|
||||
}
|
||||
return txMeta
|
||||
})
|
||||
const { TransactionController } = newState
|
||||
if (TransactionController && TransactionController.transactions) {
|
||||
const transactions = newState.TransactionController.transactions
|
||||
newState.TransactionController.transactions = transactions.map((txMeta) => {
|
||||
if (!txMeta.status === 'failed') return txMeta
|
||||
if (txMeta.retryCount > 0 && txMeta.retryCount < 2) {
|
||||
txMeta.status = 'submitted'
|
||||
delete txMeta.err
|
||||
}
|
||||
return txMeta
|
||||
})
|
||||
}
|
||||
return newState
|
||||
}
|
||||
|
@ -29,24 +29,27 @@ module.exports = {
|
||||
|
||||
function transformState (state) {
|
||||
const newState = state
|
||||
const transactions = newState.TransactionController.transactions
|
||||
newState.TransactionController.transactions = transactions.map((txMeta) => {
|
||||
// no history: initialize
|
||||
if (!txMeta.history || txMeta.history.length === 0) {
|
||||
const snapshot = txStateHistoryHelper.snapshotFromTxMeta(txMeta)
|
||||
txMeta.history = [snapshot]
|
||||
const { TransactionController } = newState
|
||||
if (TransactionController && TransactionController.transactions) {
|
||||
const transactions = newState.TransactionController.transactions
|
||||
newState.TransactionController.transactions = transactions.map((txMeta) => {
|
||||
// no history: initialize
|
||||
if (!txMeta.history || txMeta.history.length === 0) {
|
||||
const snapshot = txStateHistoryHelper.snapshotFromTxMeta(txMeta)
|
||||
txMeta.history = [snapshot]
|
||||
return txMeta
|
||||
}
|
||||
// has history: migrate
|
||||
const newHistory = (
|
||||
txStateHistoryHelper.migrateFromSnapshotsToDiffs(txMeta.history)
|
||||
// remove empty diffs
|
||||
.filter((entry) => {
|
||||
return !Array.isArray(entry) || entry.length > 0
|
||||
})
|
||||
)
|
||||
txMeta.history = newHistory
|
||||
return txMeta
|
||||
}
|
||||
// has history: migrate
|
||||
const newHistory = (
|
||||
txStateHistoryHelper.migrateFromSnapshotsToDiffs(txMeta.history)
|
||||
// remove empty diffs
|
||||
.filter((entry) => {
|
||||
return !Array.isArray(entry) || entry.length > 0
|
||||
})
|
||||
)
|
||||
txMeta.history = newHistory
|
||||
return txMeta
|
||||
})
|
||||
})
|
||||
}
|
||||
return newState
|
||||
}
|
||||
|
@ -29,32 +29,36 @@ module.exports = {
|
||||
|
||||
function transformState (state) {
|
||||
const newState = state
|
||||
const transactions = newState.TransactionController.transactions
|
||||
const { TransactionController } = newState
|
||||
if (TransactionController && TransactionController.transactions) {
|
||||
|
||||
newState.TransactionController.transactions = transactions.map((txMeta, _, txList) => {
|
||||
if (txMeta.status !== 'submitted') return txMeta
|
||||
const transactions = newState.TransactionController.transactions
|
||||
|
||||
const confirmedTxs = txList.filter((tx) => tx.status === 'confirmed')
|
||||
.filter((tx) => tx.txParams.from === txMeta.txParams.from)
|
||||
.filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from)
|
||||
const highestConfirmedNonce = getHighestNonce(confirmedTxs)
|
||||
newState.TransactionController.transactions = transactions.map((txMeta, _, txList) => {
|
||||
if (txMeta.status !== 'submitted') return txMeta
|
||||
|
||||
const pendingTxs = txList.filter((tx) => tx.status === 'submitted')
|
||||
.filter((tx) => tx.txParams.from === txMeta.txParams.from)
|
||||
.filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from)
|
||||
const highestContinuousNonce = getHighestContinuousFrom(pendingTxs, highestConfirmedNonce)
|
||||
const confirmedTxs = txList.filter((tx) => tx.status === 'confirmed')
|
||||
.filter((tx) => tx.txParams.from === txMeta.txParams.from)
|
||||
.filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from)
|
||||
const highestConfirmedNonce = getHighestNonce(confirmedTxs)
|
||||
|
||||
const maxNonce = Math.max(highestContinuousNonce, highestConfirmedNonce)
|
||||
const pendingTxs = txList.filter((tx) => tx.status === 'submitted')
|
||||
.filter((tx) => tx.txParams.from === txMeta.txParams.from)
|
||||
.filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from)
|
||||
const highestContinuousNonce = getHighestContinuousFrom(pendingTxs, highestConfirmedNonce)
|
||||
|
||||
if (parseInt(txMeta.txParams.nonce, 16) > maxNonce + 1) {
|
||||
txMeta.status = 'failed'
|
||||
txMeta.err = {
|
||||
message: 'nonce too high',
|
||||
note: 'migration 019 custom error',
|
||||
const maxNonce = Math.max(highestContinuousNonce, highestConfirmedNonce)
|
||||
|
||||
if (parseInt(txMeta.txParams.nonce, 16) > maxNonce + 1) {
|
||||
txMeta.status = 'failed'
|
||||
txMeta.err = {
|
||||
message: 'nonce too high',
|
||||
note: 'migration 019 custom error',
|
||||
}
|
||||
}
|
||||
}
|
||||
return txMeta
|
||||
})
|
||||
return txMeta
|
||||
})
|
||||
}
|
||||
return newState
|
||||
}
|
||||
|
||||
|
@ -28,12 +28,15 @@ module.exports = {
|
||||
|
||||
function transformState (state) {
|
||||
const newState = state
|
||||
const transactions = newState.TransactionController.transactions
|
||||
const { TransactionController } = newState
|
||||
if (TransactionController && TransactionController.transactions) {
|
||||
const transactions = newState.TransactionController.transactions
|
||||
|
||||
newState.TransactionController.transactions = transactions.map((txMeta) => {
|
||||
if (txMeta.status !== 'submitted' || txMeta.submittedTime) return txMeta
|
||||
txMeta.submittedTime = (new Date()).getTime()
|
||||
return txMeta
|
||||
})
|
||||
newState.TransactionController.transactions = transactions.map((txMeta) => {
|
||||
if (txMeta.status !== 'submitted' || txMeta.submittedTime) return txMeta
|
||||
txMeta.submittedTime = (new Date()).getTime()
|
||||
return txMeta
|
||||
})
|
||||
}
|
||||
return newState
|
||||
}
|
||||
|
54
app/scripts/migrations/023.js
Normal file
54
app/scripts/migrations/023.js
Normal file
@ -0,0 +1,54 @@
|
||||
|
||||
const version = 23
|
||||
|
||||
/*
|
||||
|
||||
This migration removes transactions that are no longer usefull down to 40 total
|
||||
|
||||
*/
|
||||
|
||||
const clone = require('clone')
|
||||
|
||||
module.exports = {
|
||||
version,
|
||||
|
||||
migrate: function (originalVersionedData) {
|
||||
const versionedData = clone(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
const state = versionedData.data
|
||||
const newState = transformState(state)
|
||||
versionedData.data = newState
|
||||
} catch (err) {
|
||||
console.warn(`MetaMask Migration #${version}` + err.stack)
|
||||
}
|
||||
return Promise.resolve(versionedData)
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
const newState = state
|
||||
|
||||
const { TransactionController } = newState
|
||||
if (TransactionController && TransactionController.transactions) {
|
||||
const transactions = newState.TransactionController.transactions
|
||||
|
||||
if (transactions.length <= 40) return newState
|
||||
|
||||
let reverseTxList = transactions.reverse()
|
||||
let stripping = true
|
||||
while (reverseTxList.length > 40 && stripping) {
|
||||
let txIndex = reverseTxList.findIndex((txMeta) => {
|
||||
return (txMeta.status === 'failed' ||
|
||||
txMeta.status === 'rejected' ||
|
||||
txMeta.status === 'confirmed' ||
|
||||
txMeta.status === 'dropped')
|
||||
})
|
||||
if (txIndex < 0) stripping = false
|
||||
else reverseTxList.splice(txIndex, 1)
|
||||
}
|
||||
|
||||
newState.TransactionController.transactions = reverseTxList.reverse()
|
||||
}
|
||||
return newState
|
||||
}
|
41
app/scripts/migrations/024.js
Normal file
41
app/scripts/migrations/024.js
Normal file
@ -0,0 +1,41 @@
|
||||
|
||||
const version = 24
|
||||
|
||||
/*
|
||||
|
||||
This migration ensures that the from address in txParams is to lower case for
|
||||
all unapproved transactions
|
||||
|
||||
*/
|
||||
|
||||
const clone = require('clone')
|
||||
|
||||
module.exports = {
|
||||
version,
|
||||
|
||||
migrate: async function (originalVersionedData) {
|
||||
const versionedData = clone(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
const state = versionedData.data
|
||||
const newState = transformState(state)
|
||||
versionedData.data = newState
|
||||
return versionedData
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
const newState = state
|
||||
if (!newState.TransactionController) return newState
|
||||
const transactions = newState.TransactionController.transactions
|
||||
newState.TransactionController.transactions = transactions.map((txMeta, _, txList) => {
|
||||
if (
|
||||
txMeta.status === 'unapproved' &&
|
||||
txMeta.txParams &&
|
||||
txMeta.txParams.from
|
||||
) {
|
||||
txMeta.txParams.from = txMeta.txParams.from.toLowerCase()
|
||||
}
|
||||
return txMeta
|
||||
})
|
||||
return newState
|
||||
}
|
61
app/scripts/migrations/025.js
Normal file
61
app/scripts/migrations/025.js
Normal file
@ -0,0 +1,61 @@
|
||||
// next version number
|
||||
const version = 25
|
||||
|
||||
/*
|
||||
|
||||
normalizes txParams on unconfirmed txs
|
||||
|
||||
*/
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const clone = require('clone')
|
||||
|
||||
module.exports = {
|
||||
version,
|
||||
|
||||
migrate: async function (originalVersionedData) {
|
||||
const versionedData = clone(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
const state = versionedData.data
|
||||
const newState = transformState(state)
|
||||
versionedData.data = newState
|
||||
return versionedData
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
const newState = state
|
||||
|
||||
if (newState.TransactionController) {
|
||||
if (newState.TransactionController.transactions) {
|
||||
const transactions = newState.TransactionController.transactions
|
||||
newState.TransactionController.transactions = transactions.map((txMeta) => {
|
||||
if (txMeta.status !== 'unapproved') return txMeta
|
||||
txMeta.txParams = normalizeTxParams(txMeta.txParams)
|
||||
return txMeta
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return newState
|
||||
}
|
||||
|
||||
function normalizeTxParams (txParams) {
|
||||
// functions that handle normalizing of that key in txParams
|
||||
const whiteList = {
|
||||
from: from => ethUtil.addHexPrefix(from).toLowerCase(),
|
||||
to: to => ethUtil.addHexPrefix(txParams.to).toLowerCase(),
|
||||
nonce: nonce => ethUtil.addHexPrefix(nonce),
|
||||
value: value => ethUtil.addHexPrefix(value),
|
||||
data: data => ethUtil.addHexPrefix(data),
|
||||
gas: gas => ethUtil.addHexPrefix(gas),
|
||||
gasPrice: gasPrice => ethUtil.addHexPrefix(gasPrice),
|
||||
}
|
||||
|
||||
// apply only keys in the whiteList
|
||||
const normalizedTxParams = {}
|
||||
Object.keys(whiteList).forEach((key) => {
|
||||
if (txParams[key]) normalizedTxParams[key] = whiteList[key](txParams[key])
|
||||
})
|
||||
|
||||
return normalizedTxParams
|
||||
}
|
@ -33,4 +33,7 @@ module.exports = [
|
||||
require('./020'),
|
||||
require('./021'),
|
||||
require('./022'),
|
||||
require('./023'),
|
||||
require('./024'),
|
||||
require('./025'),
|
||||
]
|
||||
|
29
app/scripts/migrations/template.js
Normal file
29
app/scripts/migrations/template.js
Normal file
@ -0,0 +1,29 @@
|
||||
// next version number
|
||||
const version = 0
|
||||
|
||||
/*
|
||||
|
||||
description of migration and what it does
|
||||
|
||||
*/
|
||||
|
||||
const clone = require('clone')
|
||||
|
||||
module.exports = {
|
||||
version,
|
||||
|
||||
migrate: async function (originalVersionedData) {
|
||||
const versionedData = clone(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
const state = versionedData.data
|
||||
const newState = transformState(state)
|
||||
versionedData.data = newState
|
||||
return versionedData
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
const newState = state
|
||||
// transform state here
|
||||
return newState
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
const injectCss = require('inject-css')
|
||||
const OldMetaMaskUiCss = require('../../old-ui/css')
|
||||
const NewMetaMaskUiCss = require('../../ui/css')
|
||||
const startPopup = require('./popup-core')
|
||||
const PortStream = require('./lib/port-stream.js')
|
||||
const isPopupOrNotification = require('./lib/is-popup-or-notification')
|
||||
const extension = require('extensionizer')
|
||||
const ExtensionPlatform = require('./platforms/extension')
|
||||
const NotificationManager = require('./lib/notification-manager')
|
||||
const notificationManager = new NotificationManager()
|
||||
const setupRaven = require('./lib/setupRaven')
|
||||
|
||||
// create platform global
|
||||
global.platform = new ExtensionPlatform()
|
||||
|
||||
// setup sentry error reporting
|
||||
const release = global.platform.getVersion()
|
||||
setupRaven({ release })
|
||||
|
||||
// inject css
|
||||
// const css = MetaMaskUiCss()
|
||||
// injectCss(css)
|
||||
|
||||
// identify window type (popup, notification)
|
||||
const windowType = isPopupOrNotification()
|
||||
global.METAMASK_UI_TYPE = windowType
|
||||
closePopupIfOpen(windowType)
|
||||
|
||||
// setup stream to background
|
||||
const extensionPort = extension.runtime.connect({ name: windowType })
|
||||
const connectionStream = new PortStream(extensionPort)
|
||||
|
||||
// start ui
|
||||
const container = document.getElementById('app-content')
|
||||
startPopup({ container, connectionStream }, (err, store) => {
|
||||
if (err) return displayCriticalError(err)
|
||||
|
||||
// Code commented out until we begin auto adding users to NewUI
|
||||
// const { isMascara, identities = {}, featureFlags = {} } = store.getState().metamask
|
||||
// const firstTime = Object.keys(identities).length === 0
|
||||
const { isMascara, featureFlags = {} } = store.getState().metamask
|
||||
let betaUIState = featureFlags.betaUI
|
||||
|
||||
// Code commented out until we begin auto adding users to NewUI
|
||||
// const useBetaCss = isMascara || firstTime || betaUIState
|
||||
const useBetaCss = isMascara || betaUIState
|
||||
|
||||
let css = useBetaCss ? NewMetaMaskUiCss() : OldMetaMaskUiCss()
|
||||
let deleteInjectedCss = injectCss(css)
|
||||
let newBetaUIState
|
||||
|
||||
store.subscribe(() => {
|
||||
const state = store.getState()
|
||||
newBetaUIState = state.metamask.featureFlags.betaUI
|
||||
if (newBetaUIState !== betaUIState) {
|
||||
deleteInjectedCss()
|
||||
betaUIState = newBetaUIState
|
||||
css = betaUIState ? NewMetaMaskUiCss() : OldMetaMaskUiCss()
|
||||
deleteInjectedCss = injectCss(css)
|
||||
}
|
||||
if (state.appState.shouldClose) notificationManager.closePopup()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
function closePopupIfOpen (windowType) {
|
||||
if (windowType !== 'notification') {
|
||||
// should close only chrome popup
|
||||
notificationManager.closePopup()
|
||||
}
|
||||
}
|
||||
|
||||
function displayCriticalError (err) {
|
||||
container.innerHTML = '<div class="critical-error">The MetaMask app failed to load: please open and close MetaMask again to restart.</div>'
|
||||
container.style.height = '80px'
|
||||
log.error(err.stack)
|
||||
throw err
|
||||
}
|
84
app/scripts/ui.js
Normal file
84
app/scripts/ui.js
Normal file
@ -0,0 +1,84 @@
|
||||
const injectCss = require('inject-css')
|
||||
const OldMetaMaskUiCss = require('../../old-ui/css')
|
||||
const NewMetaMaskUiCss = require('../../ui/css')
|
||||
const startPopup = require('./popup-core')
|
||||
const PortStream = require('./lib/port-stream.js')
|
||||
const isPopupOrNotification = require('./lib/is-popup-or-notification')
|
||||
const extension = require('extensionizer')
|
||||
const ExtensionPlatform = require('./platforms/extension')
|
||||
const NotificationManager = require('./lib/notification-manager')
|
||||
const notificationManager = new NotificationManager()
|
||||
const setupRaven = require('./lib/setupRaven')
|
||||
|
||||
start().catch(log.error)
|
||||
|
||||
async function start() {
|
||||
|
||||
// create platform global
|
||||
global.platform = new ExtensionPlatform()
|
||||
|
||||
// setup sentry error reporting
|
||||
const release = global.platform.getVersion()
|
||||
setupRaven({ release })
|
||||
|
||||
// inject css
|
||||
// const css = MetaMaskUiCss()
|
||||
// injectCss(css)
|
||||
|
||||
// identify window type (popup, notification)
|
||||
const windowType = isPopupOrNotification()
|
||||
global.METAMASK_UI_TYPE = windowType
|
||||
closePopupIfOpen(windowType)
|
||||
|
||||
// setup stream to background
|
||||
const extensionPort = extension.runtime.connect({ name: windowType })
|
||||
const connectionStream = new PortStream(extensionPort)
|
||||
|
||||
// start ui
|
||||
const container = document.getElementById('app-content')
|
||||
startPopup({ container, connectionStream }, (err, store) => {
|
||||
if (err) return displayCriticalError(err)
|
||||
|
||||
// Code commented out until we begin auto adding users to NewUI
|
||||
// const { isMascara, identities = {}, featureFlags = {} } = store.getState().metamask
|
||||
// const firstTime = Object.keys(identities).length === 0
|
||||
const { isMascara, featureFlags = {} } = store.getState().metamask
|
||||
let betaUIState = featureFlags.betaUI
|
||||
|
||||
// Code commented out until we begin auto adding users to NewUI
|
||||
// const useBetaCss = isMascara || firstTime || betaUIState
|
||||
const useBetaCss = isMascara || betaUIState
|
||||
|
||||
let css = useBetaCss ? NewMetaMaskUiCss() : OldMetaMaskUiCss()
|
||||
let deleteInjectedCss = injectCss(css)
|
||||
let newBetaUIState
|
||||
|
||||
store.subscribe(() => {
|
||||
const state = store.getState()
|
||||
newBetaUIState = state.metamask.featureFlags.betaUI
|
||||
if (newBetaUIState !== betaUIState) {
|
||||
deleteInjectedCss()
|
||||
betaUIState = newBetaUIState
|
||||
css = betaUIState ? NewMetaMaskUiCss() : OldMetaMaskUiCss()
|
||||
deleteInjectedCss = injectCss(css)
|
||||
}
|
||||
if (state.appState.shouldClose) notificationManager.closePopup()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
function closePopupIfOpen (windowType) {
|
||||
if (windowType !== 'notification') {
|
||||
// should close only chrome popup
|
||||
notificationManager.closePopup()
|
||||
}
|
||||
}
|
||||
|
||||
function displayCriticalError (err) {
|
||||
container.innerHTML = '<div class="critical-error">The MetaMask app failed to load: please open and close MetaMask again to restart.</div>'
|
||||
container.style.height = '80px'
|
||||
log.error(err.stack)
|
||||
throw err
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const promisify = require('pify')
|
||||
const enLocaleMessages = require('../app/_locales/en/messages.json')
|
||||
|
||||
|
||||
start().catch(console.error)
|
||||
|
||||
@ -12,6 +14,9 @@ async function start () {
|
||||
const stateFilePath = path.join(__dirname, 'states', stateFileName)
|
||||
const stateFileContent = await promisify(fs.readFile)(stateFilePath, 'utf8')
|
||||
const state = JSON.parse(stateFileContent)
|
||||
|
||||
state.localeMessages = { en: enLocaleMessages, current: {} }
|
||||
|
||||
const stateName = stateFileName.split('.')[0].replace(/-/g, ' ', 'g')
|
||||
states[stateName] = state
|
||||
}))
|
||||
|
62
development/metamaskbot-build-announce.js
Executable file
62
development/metamaskbot-build-announce.js
Executable file
@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env node
|
||||
const request = require('request-promise')
|
||||
const VERSION = require('../dist/chrome/manifest.json').version
|
||||
|
||||
start().catch(console.error)
|
||||
|
||||
async function start() {
|
||||
|
||||
const GITHUB_COMMENT_TOKEN = process.env.GITHUB_COMMENT_TOKEN
|
||||
const CIRCLE_PULL_REQUEST = process.env.CIRCLE_PULL_REQUEST
|
||||
console.log('CIRCLE_PULL_REQUEST', CIRCLE_PULL_REQUEST)
|
||||
const CIRCLE_SHA1 = process.env.CIRCLE_SHA1
|
||||
console.log('CIRCLE_SHA1', CIRCLE_SHA1)
|
||||
const CIRCLE_BUILD_NUM = process.env.CIRCLE_BUILD_NUM
|
||||
console.log('CIRCLE_BUILD_NUM', CIRCLE_BUILD_NUM)
|
||||
|
||||
if (!CIRCLE_PULL_REQUEST) {
|
||||
console.warn(`No pull request detected for commit "${CIRCLE_SHA1}"`)
|
||||
return
|
||||
}
|
||||
|
||||
const CIRCLE_PR_NUMBER = CIRCLE_PULL_REQUEST.split('/').pop()
|
||||
const SHORT_SHA1 = CIRCLE_SHA1.slice(0,7)
|
||||
const BUILD_LINK_BASE = `https://${CIRCLE_BUILD_NUM}-42009758-gh.circle-artifacts.com/0`
|
||||
|
||||
const MASCARA = `${BUILD_LINK_BASE}/builds/mascara/home.html`
|
||||
const CHROME = `${BUILD_LINK_BASE}/builds/metamask-chrome-${VERSION}.zip`
|
||||
const FIREFOX = `${BUILD_LINK_BASE}/builds/metamask-firefox-${VERSION}.zip`
|
||||
const EDGE = `${BUILD_LINK_BASE}/builds/metamask-edge-${VERSION}.zip`
|
||||
const OPERA = `${BUILD_LINK_BASE}/builds/metamask-opera-${VERSION}.zip`
|
||||
const WALKTHROUGH = `${BUILD_LINK_BASE}/test-artifacts/screens/walkthrough%20%28en%29.gif`
|
||||
|
||||
const commentBody = `
|
||||
<details>
|
||||
<summary>
|
||||
Builds ready [${SHORT_SHA1}]:
|
||||
<a href="${MASCARA}">mascara</a>,
|
||||
<a href="${CHROME}">chrome</a>,
|
||||
<a href="${FIREFOX}">firefox</a>,
|
||||
<a href="${EDGE}">edge</a>,
|
||||
<a href="${OPERA}">opera</a>
|
||||
</summary>
|
||||
<image src="${WALKTHROUGH}">
|
||||
</details>
|
||||
`
|
||||
|
||||
const JSON_PAYLOAD = JSON.stringify({ body: commentBody })
|
||||
const POST_COMMENT_URI = `https://api.github.com/repos/metamask/metamask-extension/issues/${CIRCLE_PR_NUMBER}/comments`
|
||||
console.log(`Announcement:\n${commentBody}`)
|
||||
console.log(`Posting to: ${POST_COMMENT_URI}`)
|
||||
|
||||
await request({
|
||||
method: 'POST',
|
||||
uri: POST_COMMENT_URI,
|
||||
body: JSON_PAYLOAD,
|
||||
headers: {
|
||||
'User-Agent': 'metamaskbot',
|
||||
'Authorization': `token ${GITHUB_COMMENT_TOKEN}`,
|
||||
},
|
||||
})
|
||||
|
||||
}
|
55
development/sentry-publish.js
Normal file
55
development/sentry-publish.js
Normal file
@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env node
|
||||
const pify = require('pify')
|
||||
const exec = pify(require('child_process').exec, { multiArgs: true })
|
||||
const VERSION = require('../dist/chrome/manifest.json').version
|
||||
|
||||
start().catch(console.error)
|
||||
|
||||
async function start(){
|
||||
const authWorked = await checkIfAuthWorks()
|
||||
if (!authWorked) {
|
||||
console.log(`Sentry auth failed...`)
|
||||
}
|
||||
// check if version exists or not
|
||||
const versionAlreadyExists = await checkIfVersionExists()
|
||||
// abort if versions exists
|
||||
if (versionAlreadyExists) {
|
||||
console.log(`Version "${VERSION}" already exists on Sentry, aborting sourcemap upload.`)
|
||||
return
|
||||
}
|
||||
|
||||
// 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!')
|
||||
}
|
||||
|
||||
async function checkIfAuthWorks() {
|
||||
const itWorked = await doesNotFail(async () => {
|
||||
await exec(`sentry-cli releases --org 'metamask' --project 'metamask' list`)
|
||||
})
|
||||
return itWorked
|
||||
}
|
||||
|
||||
async function checkIfVersionExists() {
|
||||
const versionAlreadyExists = await doesNotFail(async () => {
|
||||
await exec(`sentry-cli releases --org 'metamask' --project 'metamask' info ${VERSION}`)
|
||||
})
|
||||
return versionAlreadyExists
|
||||
}
|
||||
|
||||
async function doesNotFail(asyncFn) {
|
||||
try {
|
||||
await asyncFn()
|
||||
return true
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
}
|
@ -106,7 +106,8 @@
|
||||
"errors": {},
|
||||
"maxModeOn": false,
|
||||
"editingTransactionId": null
|
||||
}
|
||||
},
|
||||
"currentLocale": "en"
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
|
@ -128,7 +128,8 @@
|
||||
"errors": {},
|
||||
"maxModeOn": false,
|
||||
"editingTransactionId": null
|
||||
}
|
||||
},
|
||||
"currentLocale": "en"
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
|
@ -149,7 +149,8 @@
|
||||
"errors": {},
|
||||
"maxModeOn": false,
|
||||
"editingTransactionId": null
|
||||
}
|
||||
},
|
||||
"currentLocale": "en"
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
|
@ -36,7 +36,8 @@
|
||||
},
|
||||
"shapeShiftTxList": [],
|
||||
"lostAccounts": [],
|
||||
"tokens": []
|
||||
"tokens": [],
|
||||
"currentLocale": "en"
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
|
@ -128,7 +128,8 @@
|
||||
"errors": {},
|
||||
"maxModeOn": false,
|
||||
"editingTransactionId": null
|
||||
}
|
||||
},
|
||||
"currentLocale": "en"
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
|
@ -107,7 +107,8 @@
|
||||
"errors": {},
|
||||
"maxModeOn": false,
|
||||
"editingTransactionId": null
|
||||
}
|
||||
},
|
||||
"currentLocale": "en"
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
|
128
development/states/tx-list-items.js
Normal file
128
development/states/tx-list-items.js
Normal file
File diff suppressed because one or more lines are too long
@ -10,87 +10,88 @@
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var fs = require('fs')
|
||||
var path = require('path')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const localeIndex = require('../app/_locales/index.json')
|
||||
|
||||
console.log('Locale Verification')
|
||||
|
||||
var locale = process.argv[2]
|
||||
if (!locale || locale == '') {
|
||||
console.log('Must enter a locale as argument. exitting')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log("verifying for locale " + locale)
|
||||
|
||||
localeFilePath = path.join(process.cwd(), 'app', '_locales', locale, 'messages.json')
|
||||
try {
|
||||
localeObj = JSON.parse(fs.readFileSync(localeFilePath, 'utf8'));
|
||||
} catch (e) {
|
||||
if(e.code == 'ENOENT') {
|
||||
console.log('Locale file not found')
|
||||
} else {
|
||||
console.log('Error opening your locale file: ', e)
|
||||
}
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
englishFilePath = path.join(process.cwd(), 'app', '_locales', 'en', 'messages.json')
|
||||
try {
|
||||
englishObj = JSON.parse(fs.readFileSync(englishFilePath, 'utf8'));
|
||||
} catch (e) {
|
||||
if(e.code == 'ENOENT') {
|
||||
console.log("English File not found")
|
||||
} else {
|
||||
console.log("Error opening english locale file: ", e)
|
||||
}
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log('\tverifying whether all your locale strings are contained in the english one')
|
||||
|
||||
var counter = 0
|
||||
var foundErrorA = false
|
||||
var notFound = [];
|
||||
Object.keys(localeObj).forEach(function(key){
|
||||
if (!englishObj[key]) {
|
||||
foundErrorA = true
|
||||
notFound.push(key)
|
||||
}
|
||||
counter++
|
||||
})
|
||||
|
||||
if (foundErrorA) {
|
||||
console.log('\nThe following string(s) is(are) not found in the english locale:')
|
||||
notFound.forEach(function(key) {
|
||||
console.log(key)
|
||||
})
|
||||
const specifiedLocale = process.argv[2]
|
||||
if (specifiedLocale) {
|
||||
console.log(`Verifying selected locale "${specifiedLocale}":\n\n`)
|
||||
const locale = localeIndex.find(localeMeta => localeMeta.code === specifiedLocale)
|
||||
verifyLocale({ localeMeta })
|
||||
} else {
|
||||
console.log('\tall ' + counter +' strings declared in your locale were found in the english one')
|
||||
}
|
||||
|
||||
console.log('\n\tverifying whether your locale contains all english strings')
|
||||
|
||||
var counter = 0
|
||||
var foundErrorB = false
|
||||
var notFound = [];
|
||||
Object.keys(englishObj).forEach(function(key){
|
||||
if (!localeObj[key]) {
|
||||
foundErrorB = true
|
||||
notFound.push(key)
|
||||
}
|
||||
counter++
|
||||
})
|
||||
|
||||
if (foundErrorB) {
|
||||
console.log('\nThe following string(s) is(are) not found in the your locale:')
|
||||
notFound.forEach(function(key) {
|
||||
console.log(key)
|
||||
console.log('Verifying all locales:\n\n')
|
||||
localeIndex.forEach(localeMeta => {
|
||||
verifyLocale({ localeMeta })
|
||||
console.log('\n')
|
||||
})
|
||||
} else {
|
||||
console.log('\tall ' + counter +' english strings were found in your locale!')
|
||||
}
|
||||
|
||||
if (!foundErrorA && !foundErrorB) {
|
||||
console.log('You are good to go')
|
||||
}
|
||||
|
||||
|
||||
function verifyLocale({ localeMeta }) {
|
||||
const localeCode = localeMeta.code
|
||||
const localeName = localeMeta.name
|
||||
|
||||
try {
|
||||
const localeFilePath = path.join(process.cwd(), 'app', '_locales', localeCode, 'messages.json')
|
||||
targetLocale = JSON.parse(fs.readFileSync(localeFilePath, 'utf8'));
|
||||
} catch (e) {
|
||||
if (e.code == 'ENOENT') {
|
||||
console.log('Locale file not found')
|
||||
} else {
|
||||
console.log(`Error opening your locale ("${localeCode}") file: `, e)
|
||||
}
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
try {
|
||||
const englishFilePath = path.join(process.cwd(), 'app', '_locales', 'en', 'messages.json')
|
||||
englishLocale = JSON.parse(fs.readFileSync(englishFilePath, 'utf8'));
|
||||
} catch (e) {
|
||||
if(e.code == 'ENOENT') {
|
||||
console.log('English File not found')
|
||||
} else {
|
||||
console.log('Error opening english locale file: ', e)
|
||||
}
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// console.log(' verifying whether all your locale ("${localeCode}") strings are contained in the english one')
|
||||
const extraItems = compareLocalesForMissingItems({ base: targetLocale, subject: englishLocale })
|
||||
// console.log('\n verifying whether your locale ("${localeCode}") contains all english strings')
|
||||
const missingItems = compareLocalesForMissingItems({ base: englishLocale, subject: targetLocale })
|
||||
|
||||
const englishEntryCount = Object.keys(englishLocale).length
|
||||
const coveragePercent = 100 * (englishEntryCount - missingItems.length) / englishEntryCount
|
||||
|
||||
console.log(`Status of **${localeName} (${localeCode})** ${coveragePercent.toFixed(2)}% coverage:`)
|
||||
|
||||
if (extraItems.length) {
|
||||
console.log('\nMissing from english locale:')
|
||||
extraItems.forEach(function(key) {
|
||||
console.log(` - [ ] ${key}`)
|
||||
})
|
||||
} else {
|
||||
// console.log(` all ${counter} strings declared in your locale ("${localeCode}") were found in the english one`)
|
||||
}
|
||||
|
||||
if (missingItems.length) {
|
||||
console.log(`\nMissing:`)
|
||||
missingItems.forEach(function(key) {
|
||||
console.log(` - [ ] ${key}`)
|
||||
})
|
||||
} else {
|
||||
// console.log(` all ${counter} english strings were found in your locale ("${localeCode}")!`)
|
||||
}
|
||||
|
||||
if (!extraItems.length && !missingItems.length) {
|
||||
console.log('Full coverage : )')
|
||||
}
|
||||
}
|
||||
|
||||
function compareLocalesForMissingItems({ base, subject }) {
|
||||
return Object.keys(base).filter((key) => !subject[key])
|
||||
}
|
||||
|
48
docs/QA_Guide.md
Normal file
48
docs/QA_Guide.md
Normal file
@ -0,0 +1,48 @@
|
||||
# QA Guide
|
||||
|
||||
Steps to mark a full pass of QA complete.
|
||||
* Browsers: Opera, Chrome, Firefox, Edge.
|
||||
* OS: Ubuntu, Mac OSX, Windows
|
||||
* Load older version of MetaMask and attempt to simulate updating the extension.
|
||||
* Open Developer Console in background and popup, inspect errors.
|
||||
* Watch the state logs
|
||||
* Transactions (unapproved txs -> rejected/submitted -> confirmed)
|
||||
* Nonces/LocalNonces
|
||||
* Vault integrity
|
||||
* create vault
|
||||
* Log out
|
||||
* Log in again
|
||||
* Log out
|
||||
* Restore from seed
|
||||
* Create a second account
|
||||
* Import a loose account (not related to HD Wallet)
|
||||
* Import old existing vault seed phrase (pref with test Ether)
|
||||
* Download State Logs, Priv key file, seed phrase file.
|
||||
* Send Ether
|
||||
* by address
|
||||
* by ens name
|
||||
* Web3 API Stability
|
||||
* Create a contract from a Ðapp (remix)
|
||||
* Load a Ðapp that reads using events/logs (ENS)
|
||||
* Connect to MEW/MyCypto
|
||||
* Send a transaction from any Ðapp
|
||||
- MEW
|
||||
- EtherDelta
|
||||
- Leeroy
|
||||
- Aragon
|
||||
- (https://tmashuang.github.io/demo-dapp)
|
||||
* Check account balances
|
||||
* Token Management
|
||||
* create a token with tokenfactory (http://tokenfactory.surge.sh/#/factory)
|
||||
* Add that token to the token view
|
||||
* Send that token to another metamask address.
|
||||
* confirm the token arrived.
|
||||
* Send a transaction and sign a message (https://danfinlay.github.io/js-eth-personal-sign-examples/) for each keyring type
|
||||
* hd keyring
|
||||
* imported keyring
|
||||
* Change network from mainnet → ropsten → rinkeby → localhost (ganache)
|
||||
* Ganache set blocktime to simulate retryTx in MetaMask
|
||||
* Copy public key to clipboard
|
||||
* Export private key
|
||||
|
||||
* Explore changes in master, target features that have been changed and break.
|
@ -6,9 +6,12 @@ The MetaMask browser extension supports new translations added in the form of ne
|
||||
|
||||
## Adding a new Language
|
||||
|
||||
Each supported language is represented by a folder in `app/_locales` whose name is that language's subtag ([look up a language subtag using this tool](https://r12a.github.io/app-subtags/)).
|
||||
- Each supported language is represented by a folder in `app/_locales` whose name is that language's subtag (example: `app/_locales/es/`). (look up a language subtag using the [r12a "Find" tool](https://r12a.github.io/app-subtags/) or this [wikipedia list](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)).
|
||||
- Inside that folder there should be a `messages.json`.
|
||||
- An easy way to start your translation is to first **make a copy** of `app/_locales/en/messages.json` (the english translation), and then **translate the `message` key** for each in-app message.
|
||||
- **The `description` key** is just to add context for what the translation is about, it **does not need to be translated**.
|
||||
- Add the language to the [locales index](https://github.com/MetaMask/metamask-extension/blob/master/app/_locales/index.json) `app/_locales/index.json`
|
||||
|
||||
Inside that folder there should be a `messages.json` file that follows the specified format. An easy way to start your translation is to first duplicate `app/_locales/en/messages.json` (the english translation), and then update the `message` key for each in-app message.
|
||||
|
||||
That's it! When MetaMask is loaded on a computer with that language set as the system language, they will see your translation instead of the default one.
|
||||
|
||||
@ -20,7 +23,7 @@ To automatically see if you are missing any phrases to translate, we have a scri
|
||||
node development/verify-locale-strings.js $YOUR_LOCALE
|
||||
```
|
||||
|
||||
Where `$YOUR_LOCALE` is your [locale string](https://r12a.github.io/app-subtags/), i.e. the name of your language folder.
|
||||
Where `$YOUR_LOCALE` is your locale string (example: `es`), i.e. the name of your language folder.
|
||||
|
||||
To verify that your translation works in the app, you will need to [build a local copy](https://github.com/MetaMask/metamask-extension#building-locally) of MetaMask. You will need to change your browser language, your operating system language, and restart your browser (sorry it's so much work!).
|
||||
|
||||
|
625
gulpfile.js
625
gulpfile.js
@ -1,37 +1,55 @@
|
||||
var watchify = require('watchify')
|
||||
var browserify = require('browserify')
|
||||
var disc = require('disc')
|
||||
var gulp = require('gulp')
|
||||
var source = require('vinyl-source-stream')
|
||||
var buffer = require('vinyl-buffer')
|
||||
var gutil = require('gulp-util')
|
||||
var watch = require('gulp-watch')
|
||||
var sourcemaps = require('gulp-sourcemaps')
|
||||
var jsoneditor = require('gulp-json-editor')
|
||||
var zip = require('gulp-zip')
|
||||
var assign = require('lodash.assign')
|
||||
var livereload = require('gulp-livereload')
|
||||
var del = require('del')
|
||||
var eslint = require('gulp-eslint')
|
||||
var fs = require('fs')
|
||||
var path = require('path')
|
||||
var manifest = require('./app/manifest.json')
|
||||
var gulpif = require('gulp-if')
|
||||
var replace = require('gulp-replace')
|
||||
var mkdirp = require('mkdirp')
|
||||
var asyncEach = require('async/each')
|
||||
var exec = require('child_process').exec
|
||||
var sass = require('gulp-sass')
|
||||
var autoprefixer = require('gulp-autoprefixer')
|
||||
var gulpStylelint = require('gulp-stylelint')
|
||||
var stylefmt = require('gulp-stylefmt')
|
||||
var uglify = require('gulp-uglify-es').default
|
||||
var babel = require('gulp-babel')
|
||||
const watchify = require('watchify')
|
||||
const browserify = require('browserify')
|
||||
const envify = require('envify/custom')
|
||||
const disc = require('disc')
|
||||
const gulp = require('gulp')
|
||||
const source = require('vinyl-source-stream')
|
||||
const buffer = require('vinyl-buffer')
|
||||
const gutil = require('gulp-util')
|
||||
const watch = require('gulp-watch')
|
||||
const sourcemaps = require('gulp-sourcemaps')
|
||||
const jsoneditor = require('gulp-json-editor')
|
||||
const zip = require('gulp-zip')
|
||||
const assign = require('lodash.assign')
|
||||
const livereload = require('gulp-livereload')
|
||||
const del = require('del')
|
||||
const eslint = require('gulp-eslint')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const manifest = require('./app/manifest.json')
|
||||
const replace = require('gulp-replace')
|
||||
const mkdirp = require('mkdirp')
|
||||
const asyncEach = require('async/each')
|
||||
const exec = require('child_process').exec
|
||||
const sass = require('gulp-sass')
|
||||
const autoprefixer = require('gulp-autoprefixer')
|
||||
const gulpStylelint = require('gulp-stylelint')
|
||||
const stylefmt = require('gulp-stylefmt')
|
||||
const uglify = require('gulp-uglify-es').default
|
||||
const babel = require('gulp-babel')
|
||||
const debug = require('gulp-debug')
|
||||
const pify = require('pify')
|
||||
const gulpMultiProcess = require('gulp-multi-process')
|
||||
const endOfStream = pify(require('end-of-stream'))
|
||||
|
||||
function gulpParallel (...args) {
|
||||
return function spawnGulpChildProcess(cb) {
|
||||
return gulpMultiProcess(args, cb, true)
|
||||
}
|
||||
}
|
||||
|
||||
var disableDebugTools = gutil.env.disableDebugTools
|
||||
var debug = gutil.env.debug
|
||||
|
||||
const browserPlatforms = [
|
||||
'firefox',
|
||||
'chrome',
|
||||
'edge',
|
||||
'opera',
|
||||
]
|
||||
const commonPlatforms = [
|
||||
// browser webapp
|
||||
'mascara',
|
||||
// browser extensions
|
||||
...browserPlatforms
|
||||
]
|
||||
|
||||
// browser reload
|
||||
|
||||
@ -41,65 +59,98 @@ gulp.task('dev:reload', function() {
|
||||
})
|
||||
})
|
||||
|
||||
// copy universal
|
||||
|
||||
// copy static
|
||||
const copyTaskNames = []
|
||||
const copyDevTaskNames = []
|
||||
|
||||
gulp.task('copy:locales', copyTask({
|
||||
createCopyTasks('locales', {
|
||||
source: './app/_locales/',
|
||||
destinations: [
|
||||
'./dist/firefox/_locales',
|
||||
'./dist/chrome/_locales',
|
||||
'./dist/edge/_locales',
|
||||
'./dist/opera/_locales',
|
||||
]
|
||||
}))
|
||||
gulp.task('copy:images', copyTask({
|
||||
destinations: commonPlatforms.map(platform => `./dist/${platform}/_locales`),
|
||||
})
|
||||
createCopyTasks('images', {
|
||||
source: './app/images/',
|
||||
destinations: [
|
||||
'./dist/firefox/images',
|
||||
'./dist/chrome/images',
|
||||
'./dist/edge/images',
|
||||
'./dist/opera/images',
|
||||
],
|
||||
}))
|
||||
gulp.task('copy:contractImages', copyTask({
|
||||
destinations: commonPlatforms.map(platform => `./dist/${platform}/images`),
|
||||
})
|
||||
createCopyTasks('contractImages', {
|
||||
source: './node_modules/eth-contract-metadata/images/',
|
||||
destinations: [
|
||||
'./dist/firefox/images/contract',
|
||||
'./dist/chrome/images/contract',
|
||||
'./dist/edge/images/contract',
|
||||
'./dist/opera/images/contract',
|
||||
],
|
||||
}))
|
||||
gulp.task('copy:fonts', copyTask({
|
||||
destinations: commonPlatforms.map(platform => `./dist/${platform}/images/contract`),
|
||||
})
|
||||
createCopyTasks('fonts', {
|
||||
source: './app/fonts/',
|
||||
destinations: [
|
||||
'./dist/firefox/fonts',
|
||||
'./dist/chrome/fonts',
|
||||
'./dist/edge/fonts',
|
||||
'./dist/opera/fonts',
|
||||
],
|
||||
}))
|
||||
gulp.task('copy:reload', copyTask({
|
||||
destinations: commonPlatforms.map(platform => `./dist/${platform}/fonts`),
|
||||
})
|
||||
createCopyTasks('reload', {
|
||||
devOnly: true,
|
||||
source: './app/scripts/',
|
||||
destinations: [
|
||||
'./dist/firefox/scripts',
|
||||
'./dist/chrome/scripts',
|
||||
'./dist/edge/scripts',
|
||||
'./dist/opera/scripts',
|
||||
],
|
||||
pattern: '/chromereload.js',
|
||||
}))
|
||||
gulp.task('copy:root', copyTask({
|
||||
destinations: commonPlatforms.map(platform => `./dist/${platform}`),
|
||||
})
|
||||
createCopyTasks('html', {
|
||||
source: './app/',
|
||||
destinations: [
|
||||
'./dist/firefox',
|
||||
'./dist/chrome',
|
||||
'./dist/edge',
|
||||
'./dist/opera',
|
||||
],
|
||||
pattern: '/*',
|
||||
}))
|
||||
pattern: '/*.html',
|
||||
destinations: commonPlatforms.map(platform => `./dist/${platform}`),
|
||||
})
|
||||
|
||||
// copy extension
|
||||
|
||||
createCopyTasks('manifest', {
|
||||
source: './app/',
|
||||
pattern: '/*.json',
|
||||
destinations: browserPlatforms.map(platform => `./dist/${platform}`),
|
||||
})
|
||||
|
||||
// copy mascara
|
||||
|
||||
createCopyTasks('html:mascara', {
|
||||
source: './mascara/',
|
||||
pattern: 'proxy/index.html',
|
||||
destinations: [`./dist/mascara/`],
|
||||
})
|
||||
|
||||
function createCopyTasks(label, opts) {
|
||||
if (!opts.devOnly) {
|
||||
const copyTaskName = `copy:${label}`
|
||||
copyTask(copyTaskName, opts)
|
||||
copyTaskNames.push(copyTaskName)
|
||||
}
|
||||
const copyDevTaskName = `dev:copy:${label}`
|
||||
copyTask(copyDevTaskName, Object.assign({ devMode: true }, opts))
|
||||
copyDevTaskNames.push(copyDevTaskName)
|
||||
}
|
||||
|
||||
function copyTask(taskName, opts){
|
||||
const source = opts.source
|
||||
const destination = opts.destination
|
||||
const destinations = opts.destinations || [destination]
|
||||
const pattern = opts.pattern || '/**/*'
|
||||
const devMode = opts.devMode
|
||||
|
||||
return gulp.task(taskName, function () {
|
||||
if (devMode) {
|
||||
watch(source + pattern, (event) => {
|
||||
livereload.changed(event.path)
|
||||
performCopy()
|
||||
})
|
||||
}
|
||||
|
||||
return performCopy()
|
||||
})
|
||||
|
||||
function performCopy() {
|
||||
// stream from source
|
||||
let stream = gulp.src(source + pattern, { base: source })
|
||||
|
||||
// copy to destinations
|
||||
destinations.forEach(function(destination) {
|
||||
stream = stream.pipe(gulp.dest(destination))
|
||||
})
|
||||
|
||||
return stream
|
||||
}
|
||||
}
|
||||
|
||||
// manifest tinkering
|
||||
|
||||
gulp.task('manifest:chrome', function() {
|
||||
return gulp.src('./dist/chrome/manifest.json')
|
||||
@ -134,52 +185,40 @@ gulp.task('manifest:production', function() {
|
||||
],{base: './dist/'})
|
||||
|
||||
// Exclude chromereload script in production:
|
||||
.pipe(gulpif(!debug,jsoneditor(function(json) {
|
||||
.pipe(jsoneditor(function(json) {
|
||||
json.background.scripts = json.background.scripts.filter((script) => {
|
||||
return !script.includes('chromereload')
|
||||
})
|
||||
return json
|
||||
})))
|
||||
}))
|
||||
|
||||
.pipe(gulp.dest('./dist/', { overwrite: true }))
|
||||
})
|
||||
|
||||
const staticFiles = [
|
||||
'locales',
|
||||
'images',
|
||||
'fonts',
|
||||
'root'
|
||||
]
|
||||
gulp.task('copy',
|
||||
gulp.series(
|
||||
gulp.parallel(...copyTaskNames),
|
||||
'manifest:production',
|
||||
'manifest:chrome',
|
||||
'manifest:opera'
|
||||
)
|
||||
)
|
||||
|
||||
var copyStrings = staticFiles.map(staticFile => `copy:${staticFile}`)
|
||||
copyStrings.push('copy:contractImages')
|
||||
|
||||
if (debug) {
|
||||
copyStrings.push('copy:reload')
|
||||
}
|
||||
|
||||
gulp.task('copy', gulp.series(gulp.parallel(...copyStrings), 'manifest:production', 'manifest:chrome', 'manifest:opera'))
|
||||
gulp.task('copy:watch', function(){
|
||||
gulp.watch(['./app/{_locales,images}/*', './app/scripts/chromereload.js', './app/*.{html,json}'], gulp.series('copy'))
|
||||
})
|
||||
|
||||
// record deps
|
||||
|
||||
gulp.task('deps', function (cb) {
|
||||
exec('npm ls', (err, stdoutOutput, stderrOutput) => {
|
||||
if (err) return cb(err)
|
||||
const browsers = ['firefox','chrome','edge','opera']
|
||||
asyncEach(browsers, (target, done) => {
|
||||
fs.writeFile(`./dist/${target}/deps.txt`, stdoutOutput, done)
|
||||
}, cb)
|
||||
})
|
||||
})
|
||||
gulp.task('dev:copy',
|
||||
gulp.series(
|
||||
gulp.parallel(...copyDevTaskNames),
|
||||
'manifest:chrome',
|
||||
'manifest:opera'
|
||||
)
|
||||
)
|
||||
|
||||
// lint js
|
||||
|
||||
const lintTargets = ['app/**/*.json', 'app/**/*.js', '!app/scripts/vendor/**/*.js', 'ui/**/*.js', 'old-ui/**/*.js', 'mascara/src/*.js', 'mascara/server/*.js', '!node_modules/**', '!dist/firefox/**', '!docs/**', '!app/scripts/chromereload.js', '!mascara/test/jquery-3.1.0.min.js']
|
||||
|
||||
gulp.task('lint', function () {
|
||||
// Ignoring node_modules, dist/firefox, and docs folders:
|
||||
return gulp.src(['app/**/*.js', '!app/scripts/vendor/**/*.js', 'ui/**/*.js', 'mascara/src/*.js', 'mascara/server/*.js', '!node_modules/**', '!dist/firefox/**', '!docs/**', '!app/scripts/chromereload.js', '!mascara/test/jquery-3.1.0.min.js'])
|
||||
return gulp.src(lintTargets)
|
||||
.pipe(eslint(fs.readFileSync(path.join(__dirname, '.eslintrc'))))
|
||||
// eslint.format() outputs the lint results to the console.
|
||||
// Alternatively use eslint.formatEach() (see Docs).
|
||||
@ -190,47 +229,55 @@ gulp.task('lint', function () {
|
||||
});
|
||||
|
||||
gulp.task('lint:fix', function () {
|
||||
return gulp.src(['app/**/*.js', 'ui/**/*.js', 'mascara/src/*.js', 'mascara/server/*.js', '!node_modules/**', '!dist/firefox/**', '!docs/**', '!app/scripts/chromereload.js', '!mascara/test/jquery-3.1.0.min.js'])
|
||||
return gulp.src(lintTargets)
|
||||
.pipe(eslint(Object.assign(fs.readFileSync(path.join(__dirname, '.eslintrc')), {fix: true})))
|
||||
.pipe(eslint.format())
|
||||
.pipe(eslint.failAfterError())
|
||||
});
|
||||
|
||||
/*
|
||||
gulp.task('default', ['lint'], function () {
|
||||
// This will only run if the lint task is successful...
|
||||
});
|
||||
*/
|
||||
|
||||
// build js
|
||||
|
||||
const jsFiles = [
|
||||
'inpage',
|
||||
'contentscript',
|
||||
'background',
|
||||
'popup',
|
||||
]
|
||||
|
||||
// scss compilation and autoprefixing tasks
|
||||
|
||||
gulp.task('build:scss', function () {
|
||||
return gulp.src('ui/app/css/index.scss')
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(sass().on('error', sass.logError))
|
||||
.pipe(sourcemaps.write())
|
||||
.pipe(autoprefixer())
|
||||
.pipe(gulp.dest('ui/app/css/output'))
|
||||
})
|
||||
gulp.task('watch:scss', function() {
|
||||
gulp.watch(['ui/app/css/**/*.scss'], gulp.series(['build:scss']))
|
||||
})
|
||||
gulp.task('build:scss', createScssBuildTask({
|
||||
src: 'ui/app/css/index.scss',
|
||||
dest: 'ui/app/css/output',
|
||||
devMode: false,
|
||||
}))
|
||||
|
||||
gulp.task('dev:scss', createScssBuildTask({
|
||||
src: 'ui/app/css/index.scss',
|
||||
dest: 'ui/app/css/output',
|
||||
devMode: true,
|
||||
pattern: 'ui/app/css/**/*.scss',
|
||||
}))
|
||||
|
||||
function createScssBuildTask({ src, dest, devMode, pattern }) {
|
||||
return function () {
|
||||
if (devMode) {
|
||||
watch(pattern, async (event) => {
|
||||
const stream = buildScss()
|
||||
await endOfStream(stream)
|
||||
livereload.changed(event.path)
|
||||
})
|
||||
}
|
||||
return buildScss()
|
||||
}
|
||||
|
||||
function buildScss() {
|
||||
return gulp.src(src)
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(sass().on('error', sass.logError))
|
||||
.pipe(sourcemaps.write())
|
||||
.pipe(autoprefixer())
|
||||
.pipe(gulp.dest(dest))
|
||||
}
|
||||
}
|
||||
|
||||
gulp.task('lint-scss', function() {
|
||||
return gulp
|
||||
.src('ui/app/css/itcss/**/*.scss')
|
||||
.pipe(gulpStylelint({
|
||||
reporters: [
|
||||
{formatter: 'string', console: true}
|
||||
{ formatter: 'string', console: true }
|
||||
],
|
||||
fix: true,
|
||||
}));
|
||||
@ -242,46 +289,84 @@ gulp.task('fmt-scss', function () {
|
||||
.pipe(gulp.dest('ui/app/css/itcss'));
|
||||
});
|
||||
|
||||
// build js
|
||||
|
||||
const buildJsFiles = [
|
||||
'inpage',
|
||||
'contentscript',
|
||||
'background',
|
||||
'ui',
|
||||
]
|
||||
|
||||
// bundle tasks
|
||||
createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'dev:extension:js', devMode: true })
|
||||
createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'build:extension:js' })
|
||||
createTasksForBuildJsMascara({ taskPrefix: 'build:mascara:js' })
|
||||
createTasksForBuildJsMascara({ taskPrefix: 'dev:mascara:js', devMode: true })
|
||||
|
||||
var jsDevStrings = jsFiles.map(jsFile => `dev:js:${jsFile}`)
|
||||
var jsBuildStrings = jsFiles.map(jsFile => `build:js:${jsFile}`)
|
||||
function createTasksForBuildJsExtension({ buildJsFiles, taskPrefix, devMode, bundleTaskOpts = {} }) {
|
||||
// inpage must be built before all other scripts:
|
||||
const rootDir = './app/scripts'
|
||||
const nonInpageFiles = buildJsFiles.filter(file => file !== 'inpage')
|
||||
const buildPhase1 = ['inpage']
|
||||
const buildPhase2 = nonInpageFiles
|
||||
const destinations = browserPlatforms.map(platform => `./dist/${platform}`)
|
||||
bundleTaskOpts = Object.assign({
|
||||
buildSourceMaps: true,
|
||||
sourceMapDir: devMode ? './' : '../sourcemaps',
|
||||
minifyBuild: !devMode,
|
||||
buildWithFullPaths: devMode,
|
||||
watch: devMode,
|
||||
devMode,
|
||||
}, bundleTaskOpts)
|
||||
createTasksForBuildJs({ rootDir, taskPrefix, bundleTaskOpts, destinations, buildPhase1, buildPhase2 })
|
||||
}
|
||||
|
||||
jsFiles.forEach((jsFile) => {
|
||||
gulp.task(`dev:js:${jsFile}`, bundleTask({
|
||||
watch: true,
|
||||
label: jsFile,
|
||||
filename: `${jsFile}.js`,
|
||||
isBuild: false
|
||||
}))
|
||||
gulp.task(`build:js:${jsFile}`, bundleTask({
|
||||
watch: false,
|
||||
label: jsFile,
|
||||
filename: `${jsFile}.js`,
|
||||
isBuild: true
|
||||
}))
|
||||
})
|
||||
function createTasksForBuildJsMascara({ taskPrefix, devMode, bundleTaskOpts = {} }) {
|
||||
// inpage must be built before all other scripts:
|
||||
const rootDir = './mascara/src/'
|
||||
const buildPhase1 = ['ui', 'proxy', 'background', 'metamascara']
|
||||
const destinations = ['./dist/mascara']
|
||||
bundleTaskOpts = Object.assign({
|
||||
buildSourceMaps: true,
|
||||
sourceMapDir: './',
|
||||
minifyBuild: !devMode,
|
||||
buildWithFullPaths: devMode,
|
||||
watch: devMode,
|
||||
devMode,
|
||||
}, bundleTaskOpts)
|
||||
createTasksForBuildJs({ rootDir, taskPrefix, bundleTaskOpts, destinations, buildPhase1 })
|
||||
}
|
||||
|
||||
// inpage must be built before all other scripts:
|
||||
const firstDevString = jsDevStrings.shift()
|
||||
gulp.task('dev:js', gulp.series(firstDevString, gulp.parallel(...jsDevStrings)))
|
||||
function createTasksForBuildJs({ rootDir, taskPrefix, bundleTaskOpts, destinations, buildPhase1 = [], buildPhase2 = [] }) {
|
||||
// bundle task for each file
|
||||
const jsFiles = [].concat(buildPhase1, buildPhase2)
|
||||
jsFiles.forEach((jsFile) => {
|
||||
gulp.task(`${taskPrefix}:${jsFile}`, bundleTask(Object.assign({
|
||||
label: jsFile,
|
||||
filename: `${jsFile}.js`,
|
||||
filepath: `${rootDir}/${jsFile}.js`,
|
||||
destinations,
|
||||
}, bundleTaskOpts)))
|
||||
})
|
||||
// compose into larger task
|
||||
const subtasks = []
|
||||
subtasks.push(gulp.parallel(buildPhase1.map(file => `${taskPrefix}:${file}`)))
|
||||
if (buildPhase2.length) subtasks.push(gulp.parallel(buildPhase2.map(file => `${taskPrefix}:${file}`)))
|
||||
|
||||
// inpage must be built before all other scripts:
|
||||
const firstBuildString = jsBuildStrings.shift()
|
||||
gulp.task('build:js', gulp.series(firstBuildString, gulp.parallel(...jsBuildStrings)))
|
||||
gulp.task(taskPrefix, gulp.series(subtasks))
|
||||
}
|
||||
|
||||
// disc bundle analyzer tasks
|
||||
|
||||
jsFiles.forEach((jsFile) => {
|
||||
gulp.task(`disc:${jsFile}`, discTask({ label: jsFile, filename: `${jsFile}.js` }))
|
||||
buildJsFiles.forEach((jsFile) => {
|
||||
gulp.task(`disc:${jsFile}`, discTask({ label: jsFile, filename: `${jsFile}.js` }))
|
||||
})
|
||||
|
||||
gulp.task('disc', gulp.parallel(jsFiles.map(jsFile => `disc:${jsFile}`)))
|
||||
|
||||
gulp.task('disc', gulp.parallel(buildJsFiles.map(jsFile => `disc:${jsFile}`)))
|
||||
|
||||
// clean dist
|
||||
|
||||
|
||||
gulp.task('clean', function clean() {
|
||||
return del(['./dist/*'])
|
||||
})
|
||||
@ -293,40 +378,88 @@ gulp.task('zip:edge', zipTask('edge'))
|
||||
gulp.task('zip:opera', zipTask('opera'))
|
||||
gulp.task('zip', gulp.parallel('zip:chrome', 'zip:firefox', 'zip:edge', 'zip:opera'))
|
||||
|
||||
// set env var for production
|
||||
gulp.task('apply-prod-environment', function(done) {
|
||||
process.env.NODE_ENV = 'production'
|
||||
done()
|
||||
});
|
||||
|
||||
// high level tasks
|
||||
|
||||
gulp.task('dev', gulp.series('build:scss', 'dev:js', 'copy', gulp.parallel('watch:scss', 'copy:watch', 'dev:reload')))
|
||||
gulp.task('dev',
|
||||
gulp.series(
|
||||
'clean',
|
||||
'dev:scss',
|
||||
gulp.parallel(
|
||||
'dev:extension:js',
|
||||
'dev:mascara:js',
|
||||
'dev:copy',
|
||||
'dev:reload'
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
gulp.task('build', gulp.series('clean', 'build:scss', gulp.parallel('build:js', 'copy')))
|
||||
gulp.task('dist', gulp.series('apply-prod-environment', 'build', 'zip'))
|
||||
gulp.task('dev:extension',
|
||||
gulp.series(
|
||||
'clean',
|
||||
'dev:scss',
|
||||
gulp.parallel(
|
||||
'dev:extension:js',
|
||||
'dev:copy',
|
||||
'dev:reload'
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
gulp.task('dev:mascara',
|
||||
gulp.series(
|
||||
'clean',
|
||||
'dev:scss',
|
||||
gulp.parallel(
|
||||
'dev:mascara:js',
|
||||
'dev:copy',
|
||||
'dev:reload'
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
gulp.task('build',
|
||||
gulp.series(
|
||||
'clean',
|
||||
'build:scss',
|
||||
gulpParallel(
|
||||
'build:extension:js',
|
||||
'build:mascara:js',
|
||||
'copy'
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
gulp.task('build:extension',
|
||||
gulp.series(
|
||||
'clean',
|
||||
'build:scss',
|
||||
gulp.parallel(
|
||||
'build:extension:js',
|
||||
'copy'
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
gulp.task('build:mascara',
|
||||
gulp.series(
|
||||
'clean',
|
||||
'build:scss',
|
||||
gulp.parallel(
|
||||
'build:mascara:js',
|
||||
'copy'
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
gulp.task('dist',
|
||||
gulp.series(
|
||||
'build',
|
||||
'zip'
|
||||
)
|
||||
)
|
||||
|
||||
// task generators
|
||||
|
||||
function copyTask(opts){
|
||||
var source = opts.source
|
||||
var destination = opts.destination
|
||||
var destinations = opts.destinations || [ destination ]
|
||||
var pattern = opts.pattern || '/**/*'
|
||||
|
||||
return performCopy
|
||||
|
||||
function performCopy(){
|
||||
let stream = gulp.src(source + pattern, { base: source })
|
||||
destinations.forEach(function(destination) {
|
||||
stream = stream.pipe(gulp.dest(destination))
|
||||
})
|
||||
stream.pipe(gulpif(debug,livereload()))
|
||||
|
||||
return stream
|
||||
}
|
||||
}
|
||||
|
||||
function zipTask(target) {
|
||||
return () => {
|
||||
return gulp.src(`dist/${target}/**`)
|
||||
@ -337,24 +470,48 @@ function zipTask(target) {
|
||||
|
||||
function generateBundler(opts, performBundle) {
|
||||
const browserifyOpts = assign({}, watchify.args, {
|
||||
entries: ['./app/scripts/'+opts.filename],
|
||||
entries: [opts.filepath],
|
||||
plugin: 'browserify-derequire',
|
||||
debug: true,
|
||||
fullPaths: debug,
|
||||
debug: opts.buildSourceMaps,
|
||||
fullPaths: opts.buildWithFullPaths,
|
||||
})
|
||||
|
||||
let bundler = browserify(browserifyOpts)
|
||||
|
||||
// inject variables into bundle
|
||||
bundler.transform(envify({
|
||||
METAMASK_DEBUG: opts.devMode,
|
||||
NODE_ENV: opts.devMode ? 'development' : 'production',
|
||||
}))
|
||||
|
||||
// Minification
|
||||
if (opts.minifyBuild) {
|
||||
bundler.transform('uglifyify', {
|
||||
global: true,
|
||||
mangle: {
|
||||
reserved: [ 'MetamaskInpageProvider' ]
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (opts.watch) {
|
||||
bundler = watchify(bundler)
|
||||
// on any file update, re-runs the bundler
|
||||
bundler.on('update', performBundle)
|
||||
bundler.on('update', async (ids) => {
|
||||
const stream = performBundle()
|
||||
await endOfStream(stream)
|
||||
livereload.changed(`${ids}`)
|
||||
})
|
||||
}
|
||||
|
||||
return bundler
|
||||
}
|
||||
|
||||
function discTask(opts) {
|
||||
opts = Object.assign({
|
||||
buildWithFullPaths: true,
|
||||
}, opts)
|
||||
|
||||
const bundler = generateBundler(opts, performBundle)
|
||||
// output build logs to terminal
|
||||
bundler.on('log', gutil.log)
|
||||
@ -363,9 +520,9 @@ function discTask(opts) {
|
||||
|
||||
function performBundle(){
|
||||
// start "disc" build
|
||||
let discDir = path.join(__dirname, 'disc')
|
||||
const discDir = path.join(__dirname, 'disc')
|
||||
mkdirp.sync(discDir)
|
||||
let discPath = path.join(discDir, `${opts.label}.html`)
|
||||
const discPath = path.join(discDir, `${opts.label}.html`)
|
||||
|
||||
return (
|
||||
bundler.bundle()
|
||||
@ -384,43 +541,45 @@ function bundleTask(opts) {
|
||||
return performBundle
|
||||
|
||||
function performBundle(){
|
||||
return (
|
||||
let buildStream = bundler.bundle()
|
||||
|
||||
bundler.bundle()
|
||||
// handle errors
|
||||
buildStream.on('error', (err) => {
|
||||
beep()
|
||||
if (opts.watch) {
|
||||
console.warn(err.stack)
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
|
||||
// handle errors
|
||||
.on('error', (err) => {
|
||||
beep()
|
||||
if (opts.watch) {
|
||||
console.warn(err.stack)
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
// process bundles
|
||||
buildStream = buildStream
|
||||
// convert bundle stream to gulp vinyl stream
|
||||
.pipe(source(opts.filename))
|
||||
// inject variables into bundle
|
||||
.pipe(replace('\'GULP_METAMASK_DEBUG\'', debug))
|
||||
// buffer file contents (?)
|
||||
.pipe(buffer())
|
||||
// sourcemaps
|
||||
// loads map from browserify file
|
||||
.pipe(sourcemaps.init({ loadMaps: true }))
|
||||
// Minification
|
||||
.pipe(gulpif(opts.isBuild, uglify({
|
||||
mangle: { reserved: [ 'MetamaskInpageProvider' ] },
|
||||
})))
|
||||
// writes .map file
|
||||
.pipe(sourcemaps.write(debug ? './' : '../../sourcemaps'))
|
||||
// write completed bundles
|
||||
.pipe(gulp.dest('./dist/firefox/scripts'))
|
||||
.pipe(gulp.dest('./dist/chrome/scripts'))
|
||||
.pipe(gulp.dest('./dist/edge/scripts'))
|
||||
.pipe(gulp.dest('./dist/opera/scripts'))
|
||||
// finally, trigger live reload
|
||||
.pipe(gulpif(debug, livereload()))
|
||||
|
||||
)
|
||||
// Initialize Source Maps
|
||||
if (opts.buildSourceMaps) {
|
||||
buildStream = buildStream
|
||||
// loads map from browserify file
|
||||
.pipe(sourcemaps.init({ loadMaps: true }))
|
||||
}
|
||||
|
||||
// Finalize Source Maps (writes .map file)
|
||||
if (opts.buildSourceMaps) {
|
||||
buildStream = buildStream
|
||||
.pipe(sourcemaps.write(opts.sourceMapDir))
|
||||
}
|
||||
|
||||
// write completed bundles
|
||||
opts.destinations.forEach((dest) => {
|
||||
buildStream = buildStream.pipe(gulp.dest(dest))
|
||||
})
|
||||
|
||||
return buildStream
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,4 +17,4 @@
|
||||
Hello! I am the MetaMask iframe.
|
||||
<script src="./proxy.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
@ -1,7 +1,5 @@
|
||||
const path = require('path')
|
||||
const express = require('express')
|
||||
const createBundle = require('./util').createBundle
|
||||
const serveBundle = require('./util').serveBundle
|
||||
const compression = require('compression')
|
||||
|
||||
module.exports = createMetamascaraServer
|
||||
@ -9,27 +7,14 @@ module.exports = createMetamascaraServer
|
||||
|
||||
function createMetamascaraServer () {
|
||||
|
||||
// start bundlers
|
||||
const metamascaraBundle = createBundle(path.join(__dirname, '/../src/mascara.js'))
|
||||
const proxyBundle = createBundle(path.join(__dirname, '/../src/proxy.js'))
|
||||
const uiBundle = createBundle(path.join(__dirname, '/../src/ui.js'))
|
||||
const backgroundBuild = createBundle(path.join(__dirname, '/../src/background.js'))
|
||||
|
||||
// serve bundles
|
||||
// setup server
|
||||
const server = express()
|
||||
server.use(compression())
|
||||
|
||||
// ui window
|
||||
serveBundle(server, '/ui.js', uiBundle)
|
||||
// serve assets
|
||||
server.use(express.static(path.join(__dirname, '/../ui/'), { setHeaders: (res) => res.set('X-Frame-Options', 'DENY') }))
|
||||
server.use(express.static(path.join(__dirname, '/../../dist/chrome')))
|
||||
// metamascara
|
||||
serveBundle(server, '/metamascara.js', metamascaraBundle)
|
||||
// proxy
|
||||
serveBundle(server, '/proxy/proxy.js', proxyBundle)
|
||||
server.use('/proxy/', express.static(path.join(__dirname, '/../proxy')))
|
||||
// background
|
||||
serveBundle(server, '/background.js', backgroundBuild)
|
||||
server.use(express.static(path.join(__dirname, '/../../dist/mascara')))
|
||||
server.use(express.static(path.join(__dirname, '/../proxy')))
|
||||
|
||||
return server
|
||||
|
||||
|
@ -20,7 +20,6 @@ class FirstTimeFlow extends Component {
|
||||
seedWords: PropTypes.string,
|
||||
address: PropTypes.string,
|
||||
noActiveNotices: PropTypes.bool,
|
||||
goToBuyEtherView: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@ -171,4 +170,3 @@ export default connect(
|
||||
openBuyEtherModal: () => dispatch(showModal({ name: 'DEPOSIT_ETHER'})),
|
||||
})
|
||||
)(FirstTimeFlow)
|
||||
|
||||
|
@ -30,15 +30,19 @@ global.addEventListener('activate', function (event) {
|
||||
|
||||
log.debug('inside:open')
|
||||
|
||||
|
||||
// // state persistence
|
||||
// state persistence
|
||||
const dbController = new DbController({
|
||||
key: STORAGE_KEY,
|
||||
})
|
||||
loadStateFromPersistence()
|
||||
.then((initState) => setupController(initState))
|
||||
.then(() => log.debug('MetaMask initialization complete.'))
|
||||
.catch((err) => console.error('WHILE SETTING UP:', err))
|
||||
|
||||
start().catch(log.error)
|
||||
|
||||
async function start() {
|
||||
log.debug('MetaMask initializing...')
|
||||
const initState = await loadStateFromPersistence()
|
||||
await setupController(initState)
|
||||
log.debug('MetaMask initialization complete.')
|
||||
}
|
||||
|
||||
//
|
||||
// State and Persistence
|
||||
|
@ -1,13 +1,13 @@
|
||||
const createParentStream = require('iframe-stream').ParentStream
|
||||
const SWcontroller = require('client-sw-ready-event/lib/sw-client.js')
|
||||
const SwController = require('sw-controller')
|
||||
const SwStream = require('sw-stream/lib/sw-stream.js')
|
||||
|
||||
const intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000
|
||||
const background = new SWcontroller({
|
||||
fileName: '/background.js',
|
||||
letBeIdle: false,
|
||||
wakeUpInterval: 30000,
|
||||
intervalDelay,
|
||||
const keepAliveDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000
|
||||
const background = new SwController({
|
||||
fileName: './scripts/background.js',
|
||||
keepAlive: true,
|
||||
keepAliveInterval: 30000,
|
||||
keepAliveDelay,
|
||||
})
|
||||
|
||||
const pageStream = createParentStream()
|
||||
|
@ -1,6 +1,6 @@
|
||||
const injectCss = require('inject-css')
|
||||
const SWcontroller = require('client-sw-ready-event/lib/sw-client.js')
|
||||
const SwStream = require('sw-stream/lib/sw-stream.js')
|
||||
const SwController = require('sw-controller')
|
||||
const SwStream = require('sw-stream')
|
||||
const MetaMaskUiCss = require('../../ui/css')
|
||||
const MetamascaraPlatform = require('../../app/scripts/platforms/window')
|
||||
const startPopup = require('../../app/scripts/popup-core')
|
||||
@ -8,27 +8,44 @@ const startPopup = require('../../app/scripts/popup-core')
|
||||
// create platform global
|
||||
global.platform = new MetamascaraPlatform()
|
||||
|
||||
|
||||
var css = MetaMaskUiCss()
|
||||
injectCss(css)
|
||||
const container = document.getElementById('app-content')
|
||||
|
||||
var name = 'popup'
|
||||
const name = 'popup'
|
||||
window.METAMASK_UI_TYPE = name
|
||||
window.METAMASK_PLATFORM_TYPE = 'mascara'
|
||||
|
||||
const intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000
|
||||
const keepAliveDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000
|
||||
|
||||
const background = new SWcontroller({
|
||||
fileName: '/background.js',
|
||||
letBeIdle: false,
|
||||
intervalDelay,
|
||||
wakeUpInterval: 20000,
|
||||
const swController = new SwController({
|
||||
fileName: './background.js',
|
||||
keepAlive: true,
|
||||
keepAliveDelay,
|
||||
keepAliveInterval: 20000,
|
||||
})
|
||||
|
||||
swController.once('updatefound', windowReload)
|
||||
swController.once('ready', async () => {
|
||||
try {
|
||||
swController.removeListener('updatefound', windowReload)
|
||||
console.log('swController ready')
|
||||
await timeout(1000)
|
||||
console.log('connecting to app')
|
||||
await connectApp()
|
||||
console.log('app connected')
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
})
|
||||
|
||||
console.log('starting service worker')
|
||||
swController.startWorker()
|
||||
|
||||
// Setup listener for when the service worker is read
|
||||
const connectApp = function (readSw) {
|
||||
function connectApp() {
|
||||
const connectionStream = SwStream({
|
||||
serviceWorker: background.controller,
|
||||
serviceWorker: swController.getWorker(),
|
||||
context: name,
|
||||
})
|
||||
return new Promise((resolve, reject) => {
|
||||
@ -43,19 +60,6 @@ const connectApp = function (readSw) {
|
||||
})
|
||||
})
|
||||
}
|
||||
background.on('ready', async (sw) => {
|
||||
try {
|
||||
background.removeListener('updatefound', connectApp)
|
||||
await timeout(1000)
|
||||
await connectApp(sw)
|
||||
console.log('hello from cb ready event!')
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
})
|
||||
background.on('updatefound', windowReload)
|
||||
|
||||
background.startWorker()
|
||||
|
||||
function windowReload () {
|
||||
if (window.METAMASK_SKIP_RELOAD) return
|
||||
|
@ -7,6 +7,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="app-content"></div>
|
||||
<script src="./ui.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="./scripts/ui.js" type="text/javascript" charset="utf-8"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
@ -171,7 +171,7 @@ App.prototype.renderAppBar = function () {
|
||||
h('img', {
|
||||
height: 24,
|
||||
width: 24,
|
||||
src: '/images/icon-128.png',
|
||||
src: './images/icon-128.png',
|
||||
}),
|
||||
|
||||
h(NetworkIndicator, {
|
||||
@ -581,7 +581,6 @@ App.prototype.renderPrimary = function () {
|
||||
|
||||
case 'qr':
|
||||
log.debug('rendering show qr screen')
|
||||
console.log(`QrView`, QrView);
|
||||
return h('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
|
@ -247,7 +247,6 @@ BuyButtonSubview.prototype.backButtonContext = function () {
|
||||
if (this.props.context === 'confTx') {
|
||||
this.props.dispatch(actions.showConfTxPage(false))
|
||||
} else {
|
||||
console.log(`actions.goHome`, actions.goHome);
|
||||
this.props.dispatch(actions.goHome())
|
||||
}
|
||||
}
|
||||
|
@ -62,8 +62,8 @@ PendingTx.prototype.render = function () {
|
||||
const gasBn = hexToBn(gas)
|
||||
// default to 8MM gas limit
|
||||
const gasLimit = new BN(parseInt(blockGasLimit) || '8000000')
|
||||
const safeGasLimitBN = this.bnMultiplyByFraction(gasLimit, 19, 20)
|
||||
const saferGasLimitBN = this.bnMultiplyByFraction(gasLimit, 18, 20)
|
||||
const safeGasLimitBN = this.bnMultiplyByFraction(gasLimit, 99, 100)
|
||||
const saferGasLimitBN = this.bnMultiplyByFraction(gasLimit, 98, 100)
|
||||
const safeGasLimit = safeGasLimitBN.toString(10)
|
||||
|
||||
// Gas Price
|
||||
@ -311,7 +311,7 @@ PendingTx.prototype.render = function () {
|
||||
style: {
|
||||
fontSize: '0.9em',
|
||||
},
|
||||
}, 'Gas limit set dangerously high. Approving this transaction is likely to fail.')
|
||||
}, 'Gas limit set dangerously high. Approving this transaction is liable to fail.')
|
||||
: null,
|
||||
]),
|
||||
|
||||
|
@ -25,7 +25,6 @@ function QrCodeView () {
|
||||
QrCodeView.prototype.render = function () {
|
||||
const props = this.props
|
||||
const Qr = props.Qr
|
||||
console.log(`QrCodeView Qr`, Qr);
|
||||
const address = `${isHexPrefixed(Qr.data) ? 'ethereum:' : ''}${Qr.data}`
|
||||
const qrImage = qrCode(4, 'M')
|
||||
qrImage.addData(address)
|
||||
|
@ -35,7 +35,7 @@ RangeSlider.prototype.render = function () {
|
||||
step: increment,
|
||||
style: range,
|
||||
value: state.value || defaultValue,
|
||||
onChange: mirrorInput ? this.mirrorInputs.bind(this, event) : onInput,
|
||||
onChange: mirrorInput ? this.mirrorInputs.bind(this) : onInput,
|
||||
}),
|
||||
|
||||
// Mirrored input for range
|
||||
@ -47,7 +47,7 @@ RangeSlider.prototype.render = function () {
|
||||
value: state.value || defaultValue,
|
||||
step: increment,
|
||||
style: input,
|
||||
onChange: this.mirrorInputs.bind(this, event),
|
||||
onChange: this.mirrorInputs.bind(this),
|
||||
}) : null,
|
||||
])
|
||||
)
|
||||
|
@ -30,7 +30,12 @@ function TransactionListItem () {
|
||||
|
||||
TransactionListItem.prototype.showRetryButton = function () {
|
||||
const { transaction = {}, transactions } = this.props
|
||||
const { status, submittedTime, txParams } = transaction
|
||||
const { submittedTime, txParams } = transaction
|
||||
|
||||
if (!txParams) {
|
||||
return false
|
||||
}
|
||||
|
||||
const currentNonce = txParams.nonce
|
||||
const currentNonceTxs = transactions.filter(tx => tx.txParams.nonce === currentNonce)
|
||||
const currentNonceSubmittedTxs = currentNonceTxs.filter(tx => tx.status === 'submitted')
|
||||
|
@ -42,7 +42,7 @@ ConfigScreen.prototype.render = function () {
|
||||
// subtitle and nav
|
||||
h('.section-title.flex-row.flex-center', [
|
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
|
||||
onClick: (event) => {
|
||||
onClick: () => {
|
||||
state.dispatch(actions.goHome())
|
||||
},
|
||||
}),
|
||||
@ -168,7 +168,6 @@ ConfigScreen.prototype.render = function () {
|
||||
h('a', {
|
||||
href: 'http://metamask.helpscoutdocs.com/article/36-resetting-an-account',
|
||||
target: '_blank',
|
||||
onClick (event) { this.navigateTo(event.target.href) },
|
||||
}, 'Read more.'),
|
||||
]),
|
||||
h('br'),
|
||||
@ -260,7 +259,3 @@ function currentProviderDisplay (metamaskState) {
|
||||
h('span', value),
|
||||
])
|
||||
}
|
||||
|
||||
ConfigScreen.prototype.navigateTo = function (url) {
|
||||
global.platform.openWindow({ url })
|
||||
}
|
||||
|
@ -231,6 +231,7 @@ function exportAsFile (filename, data) {
|
||||
window.navigator.msSaveBlob(blob, filename)
|
||||
} else {
|
||||
const elem = window.document.createElement('a')
|
||||
elem.target = '_blank'
|
||||
elem.href = window.URL.createObjectURL(blob)
|
||||
elem.download = filename
|
||||
document.body.appendChild(elem)
|
||||
|
@ -11,7 +11,7 @@ var cssFiles = {
|
||||
'transitions.css': fs.readFileSync(path.join(__dirname, '/app/css/transitions.css'), 'utf8'),
|
||||
'first-time.css': fs.readFileSync(path.join(__dirname, '../mascara/src/app/first-time/index.css'), 'utf8'),
|
||||
'react-tooltip-component.css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-tooltip-component', 'dist', 'react-tooltip-component.css'), 'utf8'),
|
||||
'react-css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-select', 'dist', 'react-select.css'), 'utf8'),
|
||||
'react-css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-select', 'dist', 'react-select.css'), 'utf8')
|
||||
}
|
||||
|
||||
function bundleCss () {
|
||||
@ -19,7 +19,7 @@ function bundleCss () {
|
||||
var fileContent = cssFiles[fileName]
|
||||
var output = String()
|
||||
|
||||
output += '/*========== ' + fileName + ' ==========*/\n\n'
|
||||
output += '/ *========== ' + fileName + ' ========== * /\n\n'
|
||||
output += fileContent
|
||||
output += '\n\n'
|
||||
|
||||
|
7764
package-lock.json
generated
7764
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
153
package.json
153
package.json
@ -4,40 +4,39 @@
|
||||
"public": false,
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "npm run dev",
|
||||
"dev": "gulp dev --debug",
|
||||
"ui": "npm run test:flat:build:states && beefy development/ui-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./",
|
||||
"mock": "beefy development/mock-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./",
|
||||
"watch": "mocha watch --recursive \"test/unit/**/*.js\"",
|
||||
"mascara": "gulp build && cross-env METAMASK_DEBUG=true node ./mascara/example/server",
|
||||
"dist": "npm run dist:clear && npm install && gulp dist",
|
||||
"dist:clear": "rm -rf node_modules/eth-contract-metadata && rm -rf node_modules/eth-phishing-detect",
|
||||
"start": "gulp dev:extension",
|
||||
"mascara": "gulp dev:mascara & node ./mascara/example/server",
|
||||
"dist": "gulp dist",
|
||||
"test": "npm run test:unit && npm run test:integration && npm run lint",
|
||||
"test:unit": "cross-env METAMASK_ENV=test mocha --exit --require babel-core/register --require test/helper.js --recursive \"test/unit/**/*.js\"",
|
||||
"test:single": "cross-env METAMASK_ENV=test mocha --require test/helper.js",
|
||||
"test:integration": "npm run test:integration:build && npm run test:flat && npm run test:mascara",
|
||||
"test:integration:build": "gulp build:scss",
|
||||
"test:e2e": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:e2e:run'",
|
||||
"test:e2e:run": "mocha test/e2e/metamask.spec --recursive",
|
||||
"test:screens": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:screens:run'",
|
||||
"test:screens:run": "node test/screens/new-ui.js",
|
||||
"test:coverage": "nyc npm run test:unit && npm run test:coveralls-upload",
|
||||
"test:coveralls-upload": "if [ $COVERALLS_REPO_TOKEN ]; then nyc report --reporter=text-lcov | coveralls; fi",
|
||||
"test:flat": "npm run test:flat:build && karma start test/flat.conf.js",
|
||||
"test:flat:build": "npm run test:flat:build:ui && npm run test:flat:build:tests",
|
||||
"test:flat:build": "npm run test:flat:build:ui && npm run test:flat:build:tests && npm run test:flat:build:locales",
|
||||
"test:flat:build:tests": "node test/integration/index.js",
|
||||
"test:flat:build:states": "node development/genStates.js",
|
||||
"test:flat:build:locales": "mkdirp dist/chrome && cp -R app/_locales dist/chrome/_locales",
|
||||
"test:flat:build:ui": "npm run test:flat:build:states && browserify ./development/mock-dev.js -o ./development/bundle.js",
|
||||
"test:mascara": "npm run test:mascara:build && karma start test/mascara.conf.js",
|
||||
"test:mascara:build": "mkdirp dist/mascara && npm run test:mascara:build:ui && npm run test:mascara:build:background && npm run test:mascara:build:tests",
|
||||
"test:mascara:build": "mkdirp dist/mascara && npm run test:mascara:build:ui && npm run test:mascara:build:background && npm run test:mascara:build:tests && npm run test:mascara:build:locales",
|
||||
"test:mascara:build:ui": "browserify mascara/test/test-ui.js -o dist/mascara/ui.js",
|
||||
"test:mascara:build:locales": "mkdirp dist/chrome && cp -R app/_locales dist/chrome/_locales",
|
||||
"test:mascara:build:background": "browserify mascara/src/background.js -o dist/mascara/background.js",
|
||||
"test:mascara:build:tests": "browserify test/integration/lib/first-time.js -o dist/mascara/tests.js",
|
||||
"sentry": "export RELEASE=`cat app/manifest.json| jq -r .version` && npm run sentry:release && npm run sentry:upload",
|
||||
"sentry:release": "npm run sentry:release:new && npm run sentry:release:clean",
|
||||
"sentry:release:new": "sentry-cli releases --org 'metamask' --project 'metamask' new $RELEASE",
|
||||
"sentry:release:clean": "sentry-cli releases --org 'metamask' --project 'metamask' files $RELEASE delete --all",
|
||||
"sentry:upload": "npm run sentry:upload:source && npm run sentry:upload:maps",
|
||||
"sentry:upload:source": "for FILEPATH in ./dist/chrome/scripts/*.js; do [ -e $FILEPATH ] || continue; export FILE=`basename $FILEPATH` && echo uploading $FILE && sentry-cli releases --org 'metamask' --project 'metamask' files $RELEASE upload $FILEPATH metamask/scripts/$FILE; done;",
|
||||
"sentry:upload:maps": "sentry-cli releases --org 'metamask' --project 'metamask' files $RELEASE upload-sourcemaps ./dist/sourcemaps/ --url-prefix 'sourcemaps' --rewrite",
|
||||
"ganache:start": "ganache-cli -m 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent'",
|
||||
"sentry:publish": "node ./development/sentry-publish.js",
|
||||
"lint": "gulp lint",
|
||||
"lint:fix": "gulp lint:fix",
|
||||
"ui": "npm run test:flat:build:states && beefy development/ui-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./",
|
||||
"mock": "beefy development/mock-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./",
|
||||
"watch": "mocha watch --recursive \"test/unit/**/*.js\"",
|
||||
"disc": "gulp disc --debug",
|
||||
"announce": "node development/announcer.js",
|
||||
"version:bump": "node development/run-version-bump.js",
|
||||
@ -56,18 +55,17 @@
|
||||
}
|
||||
],
|
||||
"reactify",
|
||||
"envify",
|
||||
"brfs"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"abi-decoder": "^1.0.9",
|
||||
"abi-decoder": "^1.1.0",
|
||||
"asmcrypto.js": "0.22.0",
|
||||
"async": "^2.5.0",
|
||||
"await-semaphore": "^0.1.1",
|
||||
"babel-runtime": "^6.23.0",
|
||||
"bignumber.js": "^4.1.0",
|
||||
"bip39": "^2.2.0",
|
||||
"bip39": "^2.5.0",
|
||||
"bluebird": "^3.5.0",
|
||||
"bn.js": "^4.11.7",
|
||||
"boron": "^0.2.3",
|
||||
@ -75,8 +73,7 @@
|
||||
"browserify-derequire": "^0.9.4",
|
||||
"browserify-unibabel": "^3.0.0",
|
||||
"classnames": "^2.2.5",
|
||||
"client-sw-ready-event": "^3.3.0",
|
||||
"clone": "^2.1.1",
|
||||
"clone": "^2.1.2",
|
||||
"copy-to-clipboard": "^3.0.8",
|
||||
"debounce": "^1.0.0",
|
||||
"debounce-stream": "^2.0.0",
|
||||
@ -84,46 +81,44 @@
|
||||
"detect-node": "^2.0.3",
|
||||
"disc": "^1.3.2",
|
||||
"dnode": "^1.2.2",
|
||||
"end-of-stream": "^1.1.0",
|
||||
"end-of-stream": "^1.4.1",
|
||||
"ensnare": "^1.0.0",
|
||||
"eslint-plugin-react": "^7.4.0",
|
||||
"eth-bin-to-ops": "^1.0.1",
|
||||
"eth-block-tracker": "^2.3.0",
|
||||
"eth-contract-metadata": "^1.1.5",
|
||||
"eth-contract-metadata": "^1.7.0",
|
||||
"eth-hd-keyring": "^1.2.1",
|
||||
"eth-json-rpc-filters": "^1.2.5",
|
||||
"eth-json-rpc-infura": "^3.0.0",
|
||||
"eth-keyring-controller": "^2.1.4",
|
||||
"eth-phishing-detect": "^1.1.4",
|
||||
"eth-phishing-detect": "^1.1.13",
|
||||
"eth-query": "^2.1.2",
|
||||
"eth-sig-util": "^1.4.2",
|
||||
"eth-token-tracker": "^1.1.4",
|
||||
"ethereumjs-abi": "^0.6.4",
|
||||
"ethereumjs-tx": "^1.3.0",
|
||||
"ethereumjs-tx": "^1.3.4",
|
||||
"ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9",
|
||||
"ethereumjs-wallet": "^0.6.0",
|
||||
"etherscan-link": "^1.0.2",
|
||||
"ethjs": "^0.2.8",
|
||||
"ethjs-contract": "^0.1.9",
|
||||
"ethjs-ens": "^2.0.0",
|
||||
"ethjs-query": "^0.3.1",
|
||||
"express": "^4.15.5",
|
||||
"ethjs-query": "^0.3.4",
|
||||
"express": "^4.16.3",
|
||||
"extension-link-enabler": "^1.0.0",
|
||||
"extensionizer": "^1.0.0",
|
||||
"extensionizer": "^1.0.1",
|
||||
"fast-json-patch": "^2.0.4",
|
||||
"fast-levenshtein": "^2.0.6",
|
||||
"fuse.js": "^3.2.0",
|
||||
"gulp": "github:gulpjs/gulp#4.0",
|
||||
"gulp-autoprefixer": "^5.0.0",
|
||||
"gulp-eslint": "^4.0.0",
|
||||
"gulp-sass": "^3.1.0",
|
||||
"gulp-debug": "^3.2.0",
|
||||
"gulp-sass": "^3.2.1",
|
||||
"hat": "0.0.3",
|
||||
"human-standard-token-abi": "^1.0.2",
|
||||
"idb-global": "^2.1.0",
|
||||
"identicon.js": "^2.3.1",
|
||||
"identicon.js": "^2.3.2",
|
||||
"iframe": "^1.0.0",
|
||||
"iframe-stream": "^3.0.0",
|
||||
"inject-css": "^0.1.1",
|
||||
"inject-css": "^0.1.2",
|
||||
"jazzicon": "^1.2.0",
|
||||
"json-rpc-engine": "^3.6.1",
|
||||
"json-rpc-middleware-stream": "^1.0.1",
|
||||
@ -131,16 +126,16 @@
|
||||
"lodash.memoize": "^4.1.2",
|
||||
"lodash.shuffle": "^4.2.0",
|
||||
"lodash.uniqby": "^4.7.0",
|
||||
"loglevel": "^1.4.1",
|
||||
"loglevel": "^1.6.1",
|
||||
"metamascara": "^2.0.0",
|
||||
"metamask-logo": "^2.1.2",
|
||||
"metamask-logo": "^2.1.4",
|
||||
"mkdirp": "^0.5.1",
|
||||
"multiplex": "^6.7.0",
|
||||
"number-to-bn": "^1.7.0",
|
||||
"obj-multiplex": "^1.0.0",
|
||||
"obs-store": "^3.0.0",
|
||||
"once": "^1.3.3",
|
||||
"percentile": "^1.2.0",
|
||||
"pify": "^3.0.0",
|
||||
"ping-pong-stream": "^1.0.0",
|
||||
"pojo-migrator": "^2.1.0",
|
||||
"polyfill-crypto.getrandomvalues": "^1.0.0",
|
||||
@ -148,25 +143,25 @@
|
||||
"promise-filter": "^1.1.0",
|
||||
"promise-to-callback": "^1.0.0",
|
||||
"pump": "^3.0.0",
|
||||
"pumpify": "^1.3.4",
|
||||
"pumpify": "^1.4.0",
|
||||
"qrcode-npm": "0.0.3",
|
||||
"ramda": "^0.24.1",
|
||||
"raven-js": "^3.24.0",
|
||||
"raven-js": "^3.24.1",
|
||||
"react": "^15.6.2",
|
||||
"react-addons-css-transition-group": "^15.6.0",
|
||||
"react-dom": "^15.6.2",
|
||||
"react-hyperscript": "^3.0.0",
|
||||
"react-markdown": "^3.0.0",
|
||||
"react-redux": "^5.0.5",
|
||||
"react-select": "^1.0.0",
|
||||
"react-simple-file-input": "^2.0.0",
|
||||
"react-hyperscript": "^3.2.0",
|
||||
"react-markdown": "^3.3.0",
|
||||
"react-redux": "^5.0.7",
|
||||
"react-select": "^1.2.1",
|
||||
"react-simple-file-input": "^2.1.0",
|
||||
"react-tippy": "^1.2.2",
|
||||
"react-toggle-button": "^2.2.0",
|
||||
"react-tooltip-component": "^0.3.0",
|
||||
"react-transition-group": "^2.2.1",
|
||||
"react-transition-group": "^2.3.0",
|
||||
"react-trigger-change": "^1.0.2",
|
||||
"reactify": "^1.1.1",
|
||||
"readable-stream": "^2.3.3",
|
||||
"readable-stream": "^2.3.6",
|
||||
"recompose": "^0.25.0",
|
||||
"redux": "^3.0.5",
|
||||
"redux-logger": "^3.0.6",
|
||||
@ -174,22 +169,23 @@
|
||||
"request-promise": "^4.2.1",
|
||||
"sandwich-expando": "^1.1.3",
|
||||
"semaphore": "^1.0.5",
|
||||
"semver": "^5.4.1",
|
||||
"semver": "^5.5.0",
|
||||
"shallow-copy": "0.0.1",
|
||||
"sw-stream": "^2.0.0",
|
||||
"textarea-caret": "^3.0.1",
|
||||
"sw-controller": "^1.0.3",
|
||||
"sw-stream": "^2.0.2",
|
||||
"textarea-caret": "^3.1.0",
|
||||
"through2": "^2.0.3",
|
||||
"valid-url": "^1.0.9",
|
||||
"vreme": "^3.0.2",
|
||||
"web3": "^0.20.1",
|
||||
"web3-provider-engine": "^13.5.6",
|
||||
"web3": "^0.20.6",
|
||||
"web3-provider-engine": "^13.8.0",
|
||||
"web3-stream-provider": "^3.0.1",
|
||||
"xtend": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sentry/cli": "^1.30.3",
|
||||
"babel-core": "^6.24.1",
|
||||
"babel-eslint": "^8.0.0",
|
||||
"babel-eslint": "^8.2.2",
|
||||
"babel-plugin-transform-async-to-generator": "^6.24.1",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"babel-polyfill": "^6.23.0",
|
||||
@ -198,10 +194,11 @@
|
||||
"babel-register": "^6.7.2",
|
||||
"babelify": "^8.0.0",
|
||||
"beefy": "^2.1.5",
|
||||
"brfs": "^1.4.3",
|
||||
"brfs": "^1.5.0",
|
||||
"browserify": "^16.1.1",
|
||||
"chai": "^4.1.0",
|
||||
"compression": "^1.7.1",
|
||||
"chromedriver": "^2.37.0",
|
||||
"compression": "^1.7.2",
|
||||
"coveralls": "^3.0.0",
|
||||
"cross-env": "^5.1.4",
|
||||
"deep-freeze-strict": "^1.1.1",
|
||||
@ -210,18 +207,21 @@
|
||||
"enzyme": "^3.3.0",
|
||||
"enzyme-adapter-react-15": "^1.0.5",
|
||||
"eslint-plugin-chai": "0.0.1",
|
||||
"eslint-plugin-mocha": "^4.9.0",
|
||||
"eslint-plugin-react": "^7.4.0",
|
||||
"eth-json-rpc-middleware": "^1.2.7",
|
||||
"eslint-plugin-json": "^1.2.0",
|
||||
"eslint-plugin-mocha": "^5.0.0",
|
||||
"eslint-plugin-react": "^7.7.0",
|
||||
"eth-json-rpc-middleware": "^1.6.0",
|
||||
"fs-promise": "^2.0.3",
|
||||
"gulp": "github:gulpjs/gulp#6d71a658c61edb3090221579d8f97dbe086ba2ed",
|
||||
"ganache-cli": "^6.1.0",
|
||||
"gifencoder": "^1.1.0",
|
||||
"gulp": "github:gulpjs/gulp#4.0",
|
||||
"gulp-babel": "^7.0.0",
|
||||
"gulp-eslint": "^4.0.0",
|
||||
"gulp-if": "^2.0.2",
|
||||
"gulp-json-editor": "^2.2.1",
|
||||
"gulp-eslint": "^4.0.2",
|
||||
"gulp-json-editor": "^2.3.0",
|
||||
"gulp-livereload": "^3.8.1",
|
||||
"gulp-multi-process": "^1.3.1",
|
||||
"gulp-replace": "^0.6.1",
|
||||
"gulp-sourcemaps": "^2.6.0",
|
||||
"gulp-sourcemaps": "^2.6.4",
|
||||
"gulp-stylefmt": "^1.1.0",
|
||||
"gulp-stylelint": "^7.0.0",
|
||||
"gulp-uglify": "^3.0.0",
|
||||
@ -229,8 +229,9 @@
|
||||
"gulp-util": "^3.0.7",
|
||||
"gulp-watch": "^5.0.0",
|
||||
"gulp-zip": "^4.0.0",
|
||||
"image-size": "^0.6.2",
|
||||
"isomorphic-fetch": "^2.2.1",
|
||||
"jsdom": "^11.1.0",
|
||||
"jsdom": "^11.7.0",
|
||||
"jsdom-global": "^3.0.2",
|
||||
"jshint-stylish": "~2.2.1",
|
||||
"karma": "^2.0.0",
|
||||
@ -239,29 +240,33 @@
|
||||
"karma-firefox-launcher": "^1.0.1",
|
||||
"karma-qunit": "^1.2.1",
|
||||
"lodash.assign": "^4.0.6",
|
||||
"mocha": "^5.0.0",
|
||||
"mocha": "^5.0.5",
|
||||
"mocha-eslint": "^4.0.0",
|
||||
"mocha-jsdom": "^1.1.0",
|
||||
"mocha-sinon": "^2.0.0",
|
||||
"nock": "^9.0.14",
|
||||
"node-sass": "^4.7.2",
|
||||
"nyc": "^11.0.3",
|
||||
"nock": "^9.2.4",
|
||||
"node-sass": "^4.8.3",
|
||||
"nyc": "^11.6.0",
|
||||
"open": "0.0.5",
|
||||
"png-file-stream": "^1.0.0",
|
||||
"prompt": "^1.0.0",
|
||||
"qs": "^6.2.0",
|
||||
"qunitjs": "^2.4.1",
|
||||
"react-addons-test-utils": "^15.5.1",
|
||||
"react-test-renderer": "^15.6.2",
|
||||
"react-testutils-additions": "^15.2.0",
|
||||
"react-testutils-additions": "^15.3.1",
|
||||
"redux-test-utils": "^0.2.2",
|
||||
"sinon": "^4.0.0",
|
||||
"rimraf": "^2.6.2",
|
||||
"selenium-webdriver": "^3.5.0",
|
||||
"shell-parallel": "^1.0.3",
|
||||
"sinon": "^5.0.0",
|
||||
"stylelint-config-standard": "^18.2.0",
|
||||
"tape": "^4.5.1",
|
||||
"testem": "^2.0.0",
|
||||
"uglifyify": "^4.0.2",
|
||||
"tape": "^4.9.0",
|
||||
"testem": "^2.2.1",
|
||||
"uglifyify": "^4.0.5",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"vinyl-source-stream": "^2.0.0",
|
||||
"watchify": "^3.9.0"
|
||||
"watchify": "^3.11.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
|
@ -19,11 +19,13 @@ module.exports = function(config) {
|
||||
'test/integration/jquery-3.1.0.min.js',
|
||||
{ pattern: 'dist/chrome/images/**/*.*', watched: false, included: false, served: true },
|
||||
{ pattern: 'dist/chrome/fonts/**/*.*', watched: false, included: false, served: true },
|
||||
{ pattern: 'dist/chrome/_locales/**/*.*', watched: false, included: false, served: true },
|
||||
],
|
||||
|
||||
proxies: {
|
||||
'/images/': '/base/dist/chrome/images/',
|
||||
'/fonts/': '/base/dist/chrome/fonts/',
|
||||
'/_locales/': '/base/dist/chrome/_locales/',
|
||||
},
|
||||
|
||||
// test results reporter to use
|
||||
|
18
test/e2e/func.js
Normal file
18
test/e2e/func.js
Normal file
@ -0,0 +1,18 @@
|
||||
require('chromedriver')
|
||||
const webdriver = require('selenium-webdriver')
|
||||
|
||||
exports.delay = function delay (time) {
|
||||
return new Promise(resolve => setTimeout(resolve, time))
|
||||
}
|
||||
|
||||
|
||||
exports.buildWebDriver = function buildWebDriver (extPath) {
|
||||
return new webdriver.Builder()
|
||||
.withCapabilities({
|
||||
chromeOptions: {
|
||||
args: [`load-extension=${extPath}`],
|
||||
},
|
||||
})
|
||||
.forBrowser('chrome')
|
||||
.build()
|
||||
}
|
145
test/e2e/metamask.spec.js
Normal file
145
test/e2e/metamask.spec.js
Normal file
@ -0,0 +1,145 @@
|
||||
const fs = require('fs')
|
||||
const mkdirp = require('mkdirp')
|
||||
const path = require('path')
|
||||
const assert = require('assert')
|
||||
const pify = require('pify')
|
||||
const webdriver = require('selenium-webdriver')
|
||||
const By = webdriver.By
|
||||
const { delay, buildWebDriver } = require('./func')
|
||||
|
||||
describe('Metamask popup page', function () {
|
||||
let driver
|
||||
this.seedPhase
|
||||
this.accountAddress
|
||||
this.timeout(0)
|
||||
|
||||
before(async function () {
|
||||
const extPath = path.resolve('dist/chrome')
|
||||
driver = buildWebDriver(extPath)
|
||||
await driver.get('chrome://extensions-frame')
|
||||
const elems = await driver.findElements(By.css('.extension-list-item-wrapper'))
|
||||
const extensionId = await elems[1].getAttribute('id')
|
||||
await driver.get(`chrome-extension://${extensionId}/popup.html`)
|
||||
await delay(500)
|
||||
})
|
||||
|
||||
afterEach(async function () {
|
||||
if (this.currentTest.state === 'failed') {
|
||||
await verboseReportOnFailure(this.currentTest)
|
||||
}
|
||||
})
|
||||
|
||||
after(async function () {
|
||||
await driver.quit()
|
||||
})
|
||||
|
||||
describe('#onboarding', () => {
|
||||
it('should open Metamask.io', async function () {
|
||||
const tabs = await driver.getAllWindowHandles()
|
||||
await driver.switchTo().window(tabs[0])
|
||||
await delay(300)
|
||||
await setProviderType('localhost')
|
||||
await delay(300)
|
||||
})
|
||||
|
||||
it('should match title', async () => {
|
||||
const title = await driver.getTitle()
|
||||
assert.equal(title, 'MetaMask', 'title matches MetaMask')
|
||||
})
|
||||
|
||||
it('should show privacy notice', async () => {
|
||||
const privacy = await driver.findElement(By.css('.terms-header')).getText()
|
||||
assert.equal(privacy, 'PRIVACY NOTICE', 'shows privacy notice')
|
||||
driver.findElement(By.css('button')).click()
|
||||
await delay(300)
|
||||
})
|
||||
|
||||
it('should show terms of use', async () => {
|
||||
await delay(300)
|
||||
const terms = await driver.findElement(By.css('.terms-header')).getText()
|
||||
assert.equal(terms, 'TERMS OF USE', 'shows terms of use')
|
||||
await delay(300)
|
||||
})
|
||||
|
||||
it('should be unable to continue without scolling throught the terms of use', async () => {
|
||||
const button = await driver.findElement(By.css('button')).isEnabled()
|
||||
assert.equal(button, false, 'disabled continue button')
|
||||
const element = driver.findElement(By.linkText(
|
||||
'Attributions'
|
||||
))
|
||||
await driver.executeScript('arguments[0].scrollIntoView(true)', element)
|
||||
await delay(300)
|
||||
})
|
||||
|
||||
it('should be able to continue when scrolled to the bottom of terms of use', async () => {
|
||||
const button = await driver.findElement(By.css('button'))
|
||||
const buttonEnabled = await button.isEnabled()
|
||||
await delay(500)
|
||||
assert.equal(buttonEnabled, true, 'enabled continue button')
|
||||
await button.click()
|
||||
await delay(300)
|
||||
})
|
||||
|
||||
it('should accept password with length of eight', async () => {
|
||||
const passwordBox = await driver.findElement(By.id('password-box'))
|
||||
const passwordBoxConfirm = await driver.findElement(By.id('password-box-confirm'))
|
||||
const button = driver.findElement(By.css('button'))
|
||||
|
||||
passwordBox.sendKeys('123456789')
|
||||
passwordBoxConfirm.sendKeys('123456789')
|
||||
await delay(500)
|
||||
await button.click()
|
||||
})
|
||||
|
||||
it('should show value was created and seed phrase', async () => {
|
||||
await delay(700)
|
||||
this.seedPhase = await driver.findElement(By.css('.twelve-word-phrase')).getText()
|
||||
const continueAfterSeedPhrase = await driver.findElement(By.css('button'))
|
||||
await continueAfterSeedPhrase.click()
|
||||
await delay(300)
|
||||
})
|
||||
|
||||
it('should show lock account', async () => {
|
||||
await driver.findElement(By.css('.sandwich-expando')).click()
|
||||
await delay(500)
|
||||
await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)')).click()
|
||||
})
|
||||
|
||||
it('should accept account password after lock', async () => {
|
||||
await delay(500)
|
||||
await driver.findElement(By.id('password-box')).sendKeys('123456789')
|
||||
await driver.findElement(By.css('button')).click()
|
||||
await delay(500)
|
||||
})
|
||||
|
||||
it('should show QR code option', async () => {
|
||||
await delay(300)
|
||||
await driver.findElement(By.css('.fa-ellipsis-h')).click()
|
||||
await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div:nth-child(1) > flex-column > div.name-label > div > span > i > div > div > li:nth-child(3)')).click()
|
||||
await delay(300)
|
||||
})
|
||||
|
||||
it('should show the account address', async () => {
|
||||
this.accountAddress = await driver.findElement(By.css('.ellip-address')).getText()
|
||||
await driver.findElement(By.css('.fa-arrow-left')).click()
|
||||
await delay(500)
|
||||
})
|
||||
})
|
||||
|
||||
async function setProviderType(type) {
|
||||
await driver.executeScript('window.metamask.setProviderType(arguments[0])', type)
|
||||
}
|
||||
|
||||
async function verboseReportOnFailure(test) {
|
||||
const artifactDir = `./test-artifacts/${test.title}`
|
||||
const filepathBase = `${artifactDir}/test-failure`
|
||||
await pify(mkdirp)(artifactDir)
|
||||
// capture screenshot
|
||||
const screenshot = await driver.takeScreenshot()
|
||||
await pify(fs.writeFile)(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' })
|
||||
// capture dom source
|
||||
const htmlSource = await driver.getPageSource()
|
||||
await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource)
|
||||
}
|
||||
|
||||
})
|
@ -75,7 +75,7 @@ async function runAddTokenFlowTest (assert, done) {
|
||||
// Confirm Add token
|
||||
assert.equal(
|
||||
$('.add-token__description')[0].textContent,
|
||||
'Would you like to add these tokens?',
|
||||
'Token balance(s)',
|
||||
'confirm add token rendered'
|
||||
)
|
||||
assert.ok($('button.btn-primary--lg')[0], 'confirm add token button found')
|
||||
|
61
test/integration/lib/tx-list-items.js
Normal file
61
test/integration/lib/tx-list-items.js
Normal file
@ -0,0 +1,61 @@
|
||||
const reactTriggerChange = require('../../lib/react-trigger-change')
|
||||
const {
|
||||
timeout,
|
||||
queryAsync,
|
||||
findAsync,
|
||||
} = require('../../lib/util')
|
||||
|
||||
QUnit.module('tx list items')
|
||||
|
||||
QUnit.test('renders list items successfully', (assert) => {
|
||||
const done = assert.async()
|
||||
runTxListItemsTest(assert).then(done).catch((err) => {
|
||||
assert.notOk(err, `Error was thrown: ${err.stack}`)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
async function runTxListItemsTest(assert, done) {
|
||||
console.log('*** start runTxListItemsTest')
|
||||
const selectState = await queryAsync($, 'select')
|
||||
selectState.val('tx list items')
|
||||
reactTriggerChange(selectState[0])
|
||||
|
||||
const metamaskLogo = await queryAsync($, '.left-menu-wrapper')
|
||||
assert.ok(metamaskLogo[0], 'metamask logo present')
|
||||
metamaskLogo[0].click()
|
||||
|
||||
const txListItems = await queryAsync($, '.tx-list-item')
|
||||
assert.equal(txListItems.length, 8, 'all tx list items are rendered')
|
||||
|
||||
const unapprovedTx = txListItems[0]
|
||||
assert.equal($(unapprovedTx).hasClass('tx-list-pending-item-container'), true, 'unapprovedTx has the correct class')
|
||||
|
||||
const retryTx = txListItems[1]
|
||||
const retryTxLink = await findAsync($(retryTx), '.tx-list-item-retry-link')
|
||||
assert.equal(retryTxLink[0].textContent, 'Increase the gas price on your transaction', 'retryTx has expected link')
|
||||
|
||||
const approvedTx = txListItems[2]
|
||||
const approvedTxRenderedStatus = await findAsync($(approvedTx), '.tx-list-status')
|
||||
assert.equal(approvedTxRenderedStatus[0].textContent, 'Approved', 'approvedTx has correct label')
|
||||
|
||||
const unapprovedMsg = txListItems[3]
|
||||
const unapprovedMsgDescription = await findAsync($(unapprovedMsg), '.tx-list-account')
|
||||
assert.equal(unapprovedMsgDescription[0].textContent, 'Signature Request', 'unapprovedMsg has correct description')
|
||||
|
||||
const failedTx = txListItems[4]
|
||||
const failedTxRenderedStatus = await findAsync($(failedTx), '.tx-list-status')
|
||||
assert.equal(failedTxRenderedStatus[0].textContent, 'Failed', 'failedTx has correct label')
|
||||
|
||||
const shapeShiftTx = txListItems[5]
|
||||
const shapeShiftTxStatus = await findAsync($(shapeShiftTx), '.flex-column div:eq(1)')
|
||||
assert.equal(shapeShiftTxStatus[0].textContent, 'No deposits received', 'shapeShiftTx has correct status')
|
||||
|
||||
const confirmedTokenTx = txListItems[6]
|
||||
const confirmedTokenTxAddress = await findAsync($(confirmedTokenTx), '.tx-list-account')
|
||||
assert.equal(confirmedTokenTxAddress[0].textContent, '0xe7884118...81a9', 'confirmedTokenTx has correct address')
|
||||
|
||||
const rejectedTx = txListItems[7]
|
||||
const rejectedTxRenderedStatus = await findAsync($(rejectedTx), '.tx-list-status')
|
||||
assert.equal(rejectedTxRenderedStatus[0].textContent, 'Rejected', 'rejectedTx has correct label')
|
||||
}
|
18
test/screens/func.js
Normal file
18
test/screens/func.js
Normal file
@ -0,0 +1,18 @@
|
||||
require('chromedriver')
|
||||
const webdriver = require('selenium-webdriver')
|
||||
|
||||
exports.delay = function delay (time) {
|
||||
return new Promise(resolve => setTimeout(resolve, time))
|
||||
}
|
||||
|
||||
|
||||
exports.buildWebDriver = function buildWebDriver (extPath) {
|
||||
return new webdriver.Builder()
|
||||
.withCapabilities({
|
||||
chromeOptions: {
|
||||
args: [`load-extension=${extPath}`],
|
||||
},
|
||||
})
|
||||
.forBrowser('chrome')
|
||||
.build()
|
||||
}
|
230
test/screens/new-ui.js
Normal file
230
test/screens/new-ui.js
Normal file
@ -0,0 +1,230 @@
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const pify = require('pify')
|
||||
const mkdirp = require('mkdirp')
|
||||
const rimraf = require('rimraf')
|
||||
const webdriver = require('selenium-webdriver')
|
||||
const endOfStream = require('end-of-stream')
|
||||
const GIFEncoder = require('gifencoder')
|
||||
const pngFileStream = require('png-file-stream')
|
||||
const sizeOfPng = require('image-size/lib/types/png')
|
||||
const By = webdriver.By
|
||||
const { delay, buildWebDriver } = require('./func')
|
||||
const localesIndex = require('../../app/_locales/index.json')
|
||||
|
||||
let driver
|
||||
|
||||
captureAllScreens().catch((err) => {
|
||||
try {
|
||||
console.error(err)
|
||||
verboseReportOnFailure()
|
||||
driver.quit()
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
async function captureAllScreens() {
|
||||
let screenshotCount = 0
|
||||
|
||||
// common names
|
||||
let button
|
||||
let tabs
|
||||
let element
|
||||
|
||||
await cleanScreenShotDir()
|
||||
|
||||
// setup selenium and install extension
|
||||
const extPath = path.resolve('dist/chrome')
|
||||
driver = buildWebDriver(extPath)
|
||||
await driver.get('chrome://extensions-frame')
|
||||
const elems = await driver.findElements(By.css('.extension-list-item-wrapper'))
|
||||
const extensionId = await elems[1].getAttribute('id')
|
||||
await driver.get(`chrome-extension://${extensionId}/home.html`)
|
||||
await delay(500)
|
||||
tabs = await driver.getAllWindowHandles()
|
||||
await driver.switchTo().window(tabs[0])
|
||||
await delay(1000)
|
||||
await setProviderType('localhost')
|
||||
await delay(300)
|
||||
|
||||
// click try new ui
|
||||
await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-row.flex-center.flex-grow > p')).click()
|
||||
await delay(300)
|
||||
|
||||
// close metamask homepage and extra home.html
|
||||
tabs = await driver.getAllWindowHandles()
|
||||
// metamask homepage is opened on prod, not dev
|
||||
if (tabs.length > 2) {
|
||||
await driver.switchTo().window(tabs[2])
|
||||
driver.close()
|
||||
}
|
||||
await driver.switchTo().window(tabs[1])
|
||||
driver.close()
|
||||
await driver.switchTo().window(tabs[0])
|
||||
await delay(300)
|
||||
await captureLanguageScreenShots('welcome-new-ui')
|
||||
|
||||
// setup account
|
||||
await delay(1000)
|
||||
await driver.findElement(By.css('body')).click()
|
||||
await delay(300)
|
||||
await captureLanguageScreenShots('welcome')
|
||||
|
||||
await driver.findElement(By.css('button')).click()
|
||||
await captureLanguageScreenShots('create password')
|
||||
|
||||
const passwordBox = await driver.findElement(By.css('input[type=password]:nth-of-type(1)'))
|
||||
const passwordBoxConfirm = await driver.findElement(By.css('input[type=password]:nth-of-type(2)'))
|
||||
passwordBox.sendKeys('123456789')
|
||||
passwordBoxConfirm.sendKeys('123456789')
|
||||
await delay(500)
|
||||
await captureLanguageScreenShots('choose-password-filled')
|
||||
|
||||
await driver.findElement(By.css('button')).click()
|
||||
await delay(500)
|
||||
await captureLanguageScreenShots('unique account image')
|
||||
|
||||
await driver.findElement(By.css('button')).click()
|
||||
await delay(500)
|
||||
await captureLanguageScreenShots('privacy note')
|
||||
|
||||
await driver.findElement(By.css('button')).click()
|
||||
await delay(300)
|
||||
await captureLanguageScreenShots('terms')
|
||||
|
||||
await delay(300)
|
||||
element = driver.findElement(By.linkText('Attributions'))
|
||||
await driver.executeScript('arguments[0].scrollIntoView(true)', element)
|
||||
await delay(300)
|
||||
await captureLanguageScreenShots('terms-scrolled')
|
||||
|
||||
await driver.findElement(By.css('button')).click()
|
||||
await delay(300)
|
||||
await captureLanguageScreenShots('secret backup phrase')
|
||||
|
||||
await driver.findElement(By.css('button')).click()
|
||||
await delay(300)
|
||||
await captureLanguageScreenShots('secret backup phrase')
|
||||
|
||||
await driver.findElement(By.css('.backup-phrase__reveal-button')).click()
|
||||
await delay(300)
|
||||
await captureLanguageScreenShots('secret backup phrase - reveal')
|
||||
|
||||
await driver.findElement(By.css('button')).click()
|
||||
await delay(300)
|
||||
await captureLanguageScreenShots('confirm secret backup phrase')
|
||||
|
||||
// finish up
|
||||
console.log('building gif...')
|
||||
await generateGif()
|
||||
await driver.quit()
|
||||
return
|
||||
|
||||
//
|
||||
// await button.click()
|
||||
// await delay(700)
|
||||
// this.seedPhase = await driver.findElement(By.css('.twelve-word-phrase')).getText()
|
||||
// await captureScreenShot('seed phrase')
|
||||
//
|
||||
// const continueAfterSeedPhrase = await driver.findElement(By.css('button'))
|
||||
// await continueAfterSeedPhrase.click()
|
||||
// await delay(300)
|
||||
// await captureScreenShot('main screen')
|
||||
//
|
||||
// await driver.findElement(By.css('.sandwich-expando')).click()
|
||||
// await delay(500)
|
||||
// await captureScreenShot('menu')
|
||||
|
||||
// await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)')).click()
|
||||
// await captureScreenShot('main screen')
|
||||
// it('should accept account password after lock', async () => {
|
||||
// await delay(500)
|
||||
// await driver.findElement(By.id('password-box')).sendKeys('123456789')
|
||||
// await driver.findElement(By.css('button')).click()
|
||||
// await delay(500)
|
||||
// })
|
||||
//
|
||||
// it('should show QR code option', async () => {
|
||||
// await delay(300)
|
||||
// await driver.findElement(By.css('.fa-ellipsis-h')).click()
|
||||
// await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div:nth-child(1) > flex-column > div.name-label > div > span > i > div > div > li:nth-child(3)')).click()
|
||||
// await delay(300)
|
||||
// })
|
||||
//
|
||||
// it('should show the account address', async () => {
|
||||
// this.accountAddress = await driver.findElement(By.css('.ellip-address')).getText()
|
||||
// await driver.findElement(By.css('.fa-arrow-left')).click()
|
||||
// await delay(500)
|
||||
// })
|
||||
|
||||
async function captureLanguageScreenShots(label) {
|
||||
const nonEnglishLocales = localesIndex.filter(localeMeta => localeMeta.code !== 'en')
|
||||
// take english shot
|
||||
await captureScreenShot(`${label} (en)`)
|
||||
for (let localeMeta of nonEnglishLocales) {
|
||||
// set locale and take shot
|
||||
await setLocale(localeMeta.code)
|
||||
await delay(300)
|
||||
await captureScreenShot(`${label} (${localeMeta.code})`)
|
||||
}
|
||||
// return locale to english
|
||||
await setLocale('en')
|
||||
await delay(300)
|
||||
}
|
||||
|
||||
async function setLocale(code) {
|
||||
await driver.executeScript('window.metamask.updateCurrentLocale(arguments[0])', code)
|
||||
}
|
||||
|
||||
async function setProviderType(type) {
|
||||
await driver.executeScript('window.metamask.setProviderType(arguments[0])', type)
|
||||
}
|
||||
|
||||
// cleanup
|
||||
await driver.quit()
|
||||
|
||||
async function cleanScreenShotDir() {
|
||||
await pify(rimraf)(`./test-artifacts/screens/`)
|
||||
}
|
||||
|
||||
async function captureScreenShot(label) {
|
||||
const shotIndex = screenshotCount.toString().padStart(4, '0')
|
||||
screenshotCount++
|
||||
const artifactDir = `./test-artifacts/screens/`
|
||||
await pify(mkdirp)(artifactDir)
|
||||
// capture screenshot
|
||||
const screenshot = await driver.takeScreenshot()
|
||||
await pify(fs.writeFile)(`${artifactDir}/${shotIndex} - ${label}.png`, screenshot, { encoding: 'base64' })
|
||||
}
|
||||
|
||||
async function generateGif(){
|
||||
// calculate screenshot size
|
||||
const screenshot = await driver.takeScreenshot()
|
||||
const pngBuffer = Buffer.from(screenshot, 'base64')
|
||||
const size = sizeOfPng.calculate(pngBuffer)
|
||||
|
||||
// read only the english pngs into gif
|
||||
const encoder = new GIFEncoder(size.width, size.height)
|
||||
const stream = pngFileStream('./test-artifacts/screens/* (en).png')
|
||||
.pipe(encoder.createWriteStream({ repeat: 0, delay: 1000, quality: 10 }))
|
||||
.pipe(fs.createWriteStream('./test-artifacts/screens/walkthrough (en).gif'))
|
||||
|
||||
// wait for end
|
||||
await pify(endOfStream)(stream)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function verboseReportOnFailure(test) {
|
||||
const artifactDir = `./test-artifacts/${test.title}`
|
||||
const filepathBase = `${artifactDir}/test-failure`
|
||||
await pify(mkdirp)(artifactDir)
|
||||
// capture screenshot
|
||||
const screenshot = await driver.takeScreenshot()
|
||||
await pify(fs.writeFile)(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' })
|
||||
// capture dom source
|
||||
const htmlSource = await driver.getPageSource()
|
||||
await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource)
|
||||
}
|
99
test/unit/migrations/023-test.js
Normal file
99
test/unit/migrations/023-test.js
Normal file
@ -0,0 +1,99 @@
|
||||
const assert = require('assert')
|
||||
const migration23 = require('../../../app/scripts/migrations/023')
|
||||
const properTime = (new Date()).getTime()
|
||||
const storage = {
|
||||
"meta": {},
|
||||
"data": {
|
||||
"TransactionController": {
|
||||
"transactions": [
|
||||
]
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const transactions = []
|
||||
const transactions40 = []
|
||||
const transactions20 = []
|
||||
|
||||
const txStates = [
|
||||
'unapproved',
|
||||
'approved',
|
||||
'signed',
|
||||
'submitted',
|
||||
'confirmed',
|
||||
'rejected',
|
||||
'failed',
|
||||
'dropped',
|
||||
]
|
||||
|
||||
const deletableTxStates = [
|
||||
'confirmed',
|
||||
'rejected',
|
||||
'failed',
|
||||
'dropped',
|
||||
]
|
||||
|
||||
let nonDeletableCount = 0
|
||||
|
||||
let status
|
||||
while (transactions.length <= 100) {
|
||||
status = txStates[Math.floor(Math.random() * Math.floor(txStates.length - 1))]
|
||||
if (!deletableTxStates.find((s) => s === status)) nonDeletableCount++
|
||||
transactions.push({status})
|
||||
}
|
||||
|
||||
while (transactions40.length < 40) {
|
||||
status = txStates[Math.floor(Math.random() * Math.floor(txStates.length - 1))]
|
||||
transactions40.push({status})
|
||||
}
|
||||
|
||||
while (transactions20.length < 20) {
|
||||
status = txStates[Math.floor(Math.random() * Math.floor(txStates.length - 1))]
|
||||
transactions20.push({status})
|
||||
}
|
||||
|
||||
|
||||
|
||||
storage.data.TransactionController.transactions = transactions
|
||||
|
||||
describe('storage is migrated successfully and the proper transactions are remove from state', () => {
|
||||
it('should remove transactions that are unneeded', (done) => {
|
||||
migration23.migrate(storage)
|
||||
.then((migratedData) => {
|
||||
let leftoverNonDeletableTxCount = 0
|
||||
const migratedTransactions = migratedData.data.TransactionController.transactions
|
||||
migratedTransactions.forEach((tx) => {
|
||||
if (!deletableTxStates.find((s) => s === tx.status)) {
|
||||
leftoverNonDeletableTxCount++
|
||||
}
|
||||
})
|
||||
assert.equal(leftoverNonDeletableTxCount, nonDeletableCount, 'migration shouldnt delete transactions we want to keep')
|
||||
assert((migratedTransactions.length >= 40), `should be equal or greater to 40 if they are non deletable states got ${migratedTransactions.length} transactions`)
|
||||
done()
|
||||
}).catch(done)
|
||||
})
|
||||
|
||||
it('should not remove any transactions because 40 is the expectable limit', (done) => {
|
||||
storage.meta.version = 22
|
||||
storage.data.TransactionController.transactions = transactions40
|
||||
migration23.migrate(storage)
|
||||
.then((migratedData) => {
|
||||
const migratedTransactions = migratedData.data.TransactionController.transactions
|
||||
|
||||
assert.equal(migratedTransactions.length, 40, 'migration shouldnt delete when at limit')
|
||||
done()
|
||||
}).catch(done)
|
||||
})
|
||||
|
||||
it('should not remove any transactions because 20 txs is under the expectable limit', (done) => {
|
||||
storage.meta.version = 22
|
||||
storage.data.TransactionController.transactions = transactions20
|
||||
migration23.migrate(storage)
|
||||
.then((migratedData) => {
|
||||
const migratedTransactions = migratedData.data.TransactionController.transactions
|
||||
assert.equal(migratedTransactions.length, 20, 'migration shouldnt delete when under limit')
|
||||
done()
|
||||
}).catch(done)
|
||||
})
|
||||
|
||||
})
|
49
test/unit/migrations/024-test.js
Normal file
49
test/unit/migrations/024-test.js
Normal file
@ -0,0 +1,49 @@
|
||||
const assert = require('assert')
|
||||
const migration24 = require('../../../app/scripts/migrations/024')
|
||||
const firstTimeState = {
|
||||
meta: {},
|
||||
data: require('../../../app/scripts/first-time-state'),
|
||||
}
|
||||
const properTime = (new Date()).getTime()
|
||||
const storage = {
|
||||
"meta": {},
|
||||
"data": {
|
||||
"TransactionController": {
|
||||
"transactions": [
|
||||
]
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const transactions = []
|
||||
|
||||
|
||||
while (transactions.length <= 10) {
|
||||
transactions.push({ txParams: { from: '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675' }, status: 'unapproved' })
|
||||
transactions.push({ txParams: { from: '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675' }, status: 'confirmed' })
|
||||
}
|
||||
|
||||
|
||||
storage.data.TransactionController.transactions = transactions
|
||||
|
||||
describe('storage is migrated successfully and the txParams.from are lowercase', () => {
|
||||
it('should lowercase the from for unapproved txs', (done) => {
|
||||
migration24.migrate(storage)
|
||||
.then((migratedData) => {
|
||||
const migratedTransactions = migratedData.data.TransactionController.transactions
|
||||
migratedTransactions.forEach((tx) => {
|
||||
if (tx.status === 'unapproved') assert.equal(tx.txParams.from, '0x8acce2391c0d510a6c5e5d8f819a678f79b7e675')
|
||||
else assert.equal(tx.txParams.from, '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675')
|
||||
})
|
||||
done()
|
||||
}).catch(done)
|
||||
})
|
||||
|
||||
it('should migrate first time state', (done) => {
|
||||
migration24.migrate(firstTimeState)
|
||||
.then((migratedData) => {
|
||||
assert.equal(migratedData.meta.version, 24)
|
||||
done()
|
||||
}).catch(done)
|
||||
})
|
||||
})
|
49
test/unit/migrations/025-test.js
Normal file
49
test/unit/migrations/025-test.js
Normal file
@ -0,0 +1,49 @@
|
||||
const assert = require('assert')
|
||||
const migration25 = require('../../../app/scripts/migrations/025')
|
||||
const firstTimeState = {
|
||||
meta: {},
|
||||
data: require('../../../app/scripts/first-time-state'),
|
||||
}
|
||||
|
||||
const storage = {
|
||||
"meta": {},
|
||||
"data": {
|
||||
"TransactionController": {
|
||||
"transactions": [
|
||||
]
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const transactions = []
|
||||
|
||||
|
||||
while (transactions.length <= 10) {
|
||||
transactions.push({ txParams: { from: '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675', random: 'stuff', chainId: 2 }, status: 'unapproved' })
|
||||
transactions.push({ txParams: { from: '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675' }, status: 'confirmed' })
|
||||
}
|
||||
|
||||
|
||||
storage.data.TransactionController.transactions = transactions
|
||||
|
||||
describe('storage is migrated successfully and the txParams.from are lowercase', () => {
|
||||
it('should lowercase the from for unapproved txs', (done) => {
|
||||
migration25.migrate(storage)
|
||||
.then((migratedData) => {
|
||||
const migratedTransactions = migratedData.data.TransactionController.transactions
|
||||
migratedTransactions.forEach((tx) => {
|
||||
if (tx.status === 'unapproved') assert(!tx.txParams.random)
|
||||
if (tx.status === 'unapproved') assert(!tx.txParams.chainId)
|
||||
})
|
||||
done()
|
||||
}).catch(done)
|
||||
})
|
||||
|
||||
it('should migrate first time state', (done) => {
|
||||
migration25.migrate(firstTimeState)
|
||||
.then((migratedData) => {
|
||||
assert.equal(migratedData.meta.version, 25)
|
||||
done()
|
||||
}).catch(done)
|
||||
})
|
||||
})
|
17
test/unit/migrations/template-test.js
Normal file
17
test/unit/migrations/template-test.js
Normal file
@ -0,0 +1,17 @@
|
||||
const assert = require('assert')
|
||||
const migrationTemplate = require('../../../app/scripts/migrations/template')
|
||||
const properTime = (new Date()).getTime()
|
||||
const storage = {
|
||||
meta: {},
|
||||
data: {},
|
||||
}
|
||||
|
||||
describe('storage is migrated successfully', () => {
|
||||
it('should work', (done) => {
|
||||
migrationTemplate.migrate(storage)
|
||||
.then((migratedData) => {
|
||||
assert.equal(migratedData.meta.version, 0)
|
||||
done()
|
||||
}).catch(done)
|
||||
})
|
||||
})
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user