1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 18:00:18 +01:00

Merge pull request #9160 from MetaMask/Version-v8.0.7

Version v8.0.7 RC
This commit is contained in:
Mark Stacey 2020-08-10 15:07:25 -03:00 committed by GitHub
commit 1a5218ce8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
100 changed files with 1064 additions and 1512 deletions

View File

@ -38,6 +38,9 @@ workflows:
- test-unit-global:
requires:
- prep-deps
- validate-source-maps:
requires:
- prep-build
- test-mozilla-lint:
requires:
- prep-deps
@ -49,6 +52,7 @@ workflows:
- test-lint-lockfile
- test-unit
- test-unit-global
- validate-source-maps
- test-mozilla-lint
- test-e2e-chrome
- test-e2e-firefox
@ -358,6 +362,18 @@ jobs:
- run:
name: test:unit:global
command: yarn test:unit:global
validate-source-maps:
docker:
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Validate source maps
command: yarn validate-source-maps
test-mozilla-lint:
docker:
- image: circleci/node@sha256:e16740707de2ebed45c05d507f33ef204902349c7356d720610b5ec6a35d3d88

View File

@ -44,7 +44,6 @@ install_github_cli
printf '%s\n' "Creating a Pull Request to sync 'master' with 'develop'"
if ! hub pull-request \
--reviewer '@MetaMask/extension-release-team' \
--message "Master => develop" --message 'Merge latest release back into develop' \
--base "$CIRCLE_PROJECT_USERNAME:$base_branch" \
--head "$CIRCLE_PROJECT_USERNAME:$CIRCLE_BRANCH";

View File

@ -45,7 +45,6 @@ install_github_cli
printf '%s\n' "Creating a Pull Request for $version on GitHub"
if ! hub pull-request \
--reviewer '@MetaMask/extension-release-team' \
--message "${CIRCLE_BRANCH/-/ } RC" --message ':package: :rocket:' \
--base "$CIRCLE_PROJECT_USERNAME:$base_branch" \
--head "$CIRCLE_PROJECT_USERNAME:$CIRCLE_BRANCH";

1
.gitignore vendored
View File

@ -34,6 +34,7 @@ builds.zip
test-artifacts
test-builds
build-artifacts
#ignore css output and sourcemaps
ui/app/css/output/

View File

@ -2,6 +2,18 @@
## Current Develop Branch
## 8.0.7 Fri Aug 07 2020
- [#9065](https://github.com/MetaMask/metamask-extension/pull/9065): Change title of "Reveal Seed Words" page to "Reveal Seed Phrase"
- [#8974](https://github.com/MetaMask/metamask-extension/pull/8974): Add tooltip to copy button for contacts and seed phrase
- [#9063](https://github.com/MetaMask/metamask-extension/pull/9063): Fix broken UI upon failed password validation
- [#9075](https://github.com/MetaMask/metamask-extension/pull/9075): Fix shifted popup notification when browser is in fullscreen on macOS
- [#9085](https://github.com/MetaMask/metamask-extension/pull/9085): Support longer text in network dropdown
- [#8873](https://github.com/MetaMask/metamask-extension/pull/8873): Fix onboarding bug where user can be asked to verify seed phrase twice
- [#9104](https://github.com/MetaMask/metamask-extension/pull/9104): Replace "Email us" button with "Contact us" button
- [#9137](https://github.com/MetaMask/metamask-extension/pull/9137): Fix bug where `accountsChanged` events stop after a dapp connection is closed.
- [#9152](https://github.com/MetaMask/metamask-extension/pull/9152): Fix network name alignment
- [#9144](https://github.com/MetaMask/metamask-extension/pull/9144): Add web3 usage metrics and prepare for web3 removal
## 8.0.6 Wed Jul 22 2020
- [#9030](https://github.com/MetaMask/metamask-extension/pull/9030): Hide "delete" button when editing contact of wallet account
- [#9031](https://github.com/MetaMask/metamask-extension/pull/9031): Fix crash upon removing contact

View File

@ -359,9 +359,6 @@
"editContact": {
"message": "ዕውቂያን አርትዕ"
},
"emailUs": {
"message": "ኢሜይል ያድርጉልን!"
},
"endOfFlowMessage1": {
"message": "ፈተናውን አልፈዋል - የዘር ሐረግዎን ደህንነቱ በተጠበቀ መንገድ ያስቀምጡ፣ የእርስዎ ሃላፊነት ነው! "
},

View File

@ -359,9 +359,6 @@
"editContact": {
"message": "تعديل جهة الاتصال"
},
"emailUs": {
"message": "راسلنا عبر البريد الإلكتروني!"
},
"endOfFlowMessage1": {
"message": "لقد نجحت في الاختبار - احتفظ بعبارة الأمان الخاصة بك في مكان آمن، إنها مسؤوليتك!"
},

View File

@ -359,9 +359,6 @@
"editContact": {
"message": "Редактиране на контакт"
},
"emailUs": {
"message": "Изпратете ни имейл!"
},
"endOfFlowMessage1": {
"message": "Преминахте теста - пазете основната си фраза на сигурно място, това е Ваша отговорност!"
},

View File

@ -359,9 +359,6 @@
"editContact": {
"message": "পরিচিতি সম্পাদনা করুন"
},
"emailUs": {
"message": "আমাদের ইমেল করুন!"
},
"endOfFlowMessage1": {
"message": "আপনি টেস্টটি পাস করেছেন - আপনার সীডফ্রেজ নিরাপদে রাখুন, এটি আপনার দায়িত্ব!"
},

View File

@ -356,9 +356,6 @@
"editContact": {
"message": "Editar Contacte"
},
"emailUs": {
"message": "Envia'ns un correu!"
},
"endOfFlowMessage1": {
"message": "Has passat el test - mantingues la teva seedphrase segura, és la teva responsabilitat!"
},

View File

@ -148,9 +148,6 @@
"edit": {
"message": "Upravit"
},
"emailUs": {
"message": "Napište nám e-mail!"
},
"enterPassword": {
"message": "Zadejte heslo"
},

View File

@ -359,9 +359,6 @@
"editContact": {
"message": "Redigér Kontakt"
},
"emailUs": {
"message": "Send os en email!"
},
"endOfFlowMessage1": {
"message": "Du bestod testen - pas godt på din seed-sætning, det er dit ansvar!"
},

View File

@ -344,9 +344,6 @@
"editContact": {
"message": "Kontakt bearbeiten"
},
"emailUs": {
"message": "Schreib uns eine Mail!"
},
"endOfFlowMessage1": {
"message": "Sie haben den Test bestanden - bewahren Sie Ihre mnemonische Phrase sicher auf, Sie sind dafür verantwortlich! "
},

View File

@ -356,9 +356,6 @@
"editContact": {
"message": "Επεξεργασία Επαφής"
},
"emailUs": {
"message": "Στείλτε μας email!"
},
"endOfFlowMessage1": {
"message": "Περάσατε τη δοκιμή - κρατήστε τη φράση φύτρου σας ασφαλή, είναι δική σας ευθύνη!"
},

View File

@ -409,6 +409,9 @@
"contactsSettingsDescription": {
"message": "Add, edit, remove, and manage your contacts"
},
"contactUs": {
"message": "Contact us!"
},
"continueToWyre": {
"message": "Continue to Wyre"
},
@ -535,9 +538,6 @@
"editPermission": {
"message": "Edit Permission"
},
"emailUs": {
"message": "Email us!"
},
"endOfFlowMessage1": {
"message": "You passed the test - keep your seedphrase safe, it's your responsibility!"
},
@ -1196,7 +1196,7 @@
"message": "Restore"
},
"revealSeedWords": {
"message": "Reveal Seed Words"
"message": "Reveal Seed Phrase"
},
"revealSeedWordsTitle": {
"message": "Seed Phrase"

View File

@ -313,9 +313,6 @@
"editContact": {
"message": "Editar Contacto"
},
"emailUs": {
"message": "¡Envíanos un correo!"
},
"endOfFlowMessage8": {
"message": "MetaMask no puede recuperar tu seedphrase. Saber más."
},

View File

@ -356,9 +356,6 @@
"editContact": {
"message": "Editar contacto"
},
"emailUs": {
"message": "¡Envíanos un correo electrónico!"
},
"endOfFlowMessage1": {
"message": "Pasaste la prueba. Es responsabilidad tuya mantener la frase de inicialización segura."
},

View File

@ -359,9 +359,6 @@
"editContact": {
"message": "Muuda kontakti"
},
"emailUs": {
"message": "Saatke meile e-kiri!"
},
"endOfFlowMessage1": {
"message": "Läbisite kontrolli hoidke seemnefraasi turvaliselt, see on teie vastutusel!"
},

View File

@ -359,9 +359,6 @@
"editContact": {
"message": "ویرایش تماس"
},
"emailUs": {
"message": "به ما ایمیل کنید!"
},
"endOfFlowMessage1": {
"message": "شما در امتحان موفق شدید - عبارت بازیابی را مصؤن نگهدارید، این مسؤلیت شماست!"
},

View File

@ -356,9 +356,6 @@
"editContact": {
"message": "Muokkaa yhteystietoa"
},
"emailUs": {
"message": "Lähetä meille sähköpostia!"
},
"endOfFlowMessage1": {
"message": "Läpäisit kokeen pidä salaustekstisi turvassa. Se on sinun vastuullasi!"
},

View File

@ -332,9 +332,6 @@
"editContact": {
"message": "I-edit ang Contact"
},
"emailUs": {
"message": "Mag-email sa amin!"
},
"endOfFlowMessage1": {
"message": "Pumasa ka sa test - panatilihing ligtas ang iyong seedphrase, responsibilidad mo ito!"
},

View File

@ -347,9 +347,6 @@
"editContact": {
"message": "Modifier le contact"
},
"emailUs": {
"message": "Envoyez-nous un email !"
},
"endOfFlowMessage1": {
"message": "Vous avez réussi l'essai : gardez votre phrase de départ en sécurité, c'est de votre responsabilité !"
},

View File

@ -359,9 +359,6 @@
"editContact": {
"message": "ערוך איש קשר"
},
"emailUs": {
"message": "שלח/י לנו מייל!"
},
"endOfFlowMessage1": {
"message": "עברת את הבחינה - יש לשמור את ה-seedphrase שלך במקום בטוח, זה באחריותך!"
},

View File

@ -359,9 +359,6 @@
"editContact": {
"message": "संपर्क संपादित करें"
},
"emailUs": {
"message": "हमें ईमेल करें!"
},
"endOfFlowMessage1": {
"message": "आपने परीक्षा उत्तीर्ण कर ली - अपने बीज वाक्यांश को सुरक्षित रखें, यह आपकी जिम्मेदारी है!"
},

View File

@ -124,9 +124,6 @@
"edit": {
"message": "संपादित करें"
},
"emailUs": {
"message": "हमें ईमेल करें!"
},
"enterPassword": {
"message": "पासवर्ड दर्ज करें"
},

View File

@ -359,9 +359,6 @@
"editContact": {
"message": "Uredi kontakt"
},
"emailUs": {
"message": "Pošaljite nam elektroničku poruku!"
},
"endOfFlowMessage1": {
"message": "Prošli ste test čuvajte svoju početnu rečenicu jer ste vi odgovorni za nju!"
},

View File

@ -205,9 +205,6 @@
"edit": {
"message": "Korije"
},
"emailUs": {
"message": "Imèl nou!"
},
"enterPassword": {
"message": "Mete modpas"
},

View File

@ -359,9 +359,6 @@
"editContact": {
"message": "Kapcsolatok szerkesztése"
},
"emailUs": {
"message": "Írjon nekünk!"
},
"endOfFlowMessage1": {
"message": "Átment a teszten - tartsa biztonságban a seed mondatot, ez az ön felelőssége!"
},

View File

@ -350,9 +350,6 @@
"editContact": {
"message": "Sunting Kontak"
},
"emailUs": {
"message": "Kirimkan kami email!"
},
"endOfFlowMessage1": {
"message": "Anda lulus tes - jagalah agar frasa benih Anda aman, itu tanggung jawab Anda!"
},

View File

@ -531,9 +531,6 @@
"editPermission": {
"message": "Modifica Permessi"
},
"emailUs": {
"message": "Mandaci una mail!"
},
"endOfFlowMessage1": {
"message": "Hai passato il test - tieni la tua frase seed al sicuro, è tua responsabilità!"
},

View File

@ -359,9 +359,6 @@
"editContact": {
"message": "ಸಂಪರ್ಕವನ್ನು ಸಂಪಾದಿಸಿ"
},
"emailUs": {
"message": "ನಮಗೆ ಇಮೇಲ್ ಮಾಡಿ!"
},
"endOfFlowMessage1": {
"message": "ನೀವು ಪರೀಕ್ಷೆಯನ್ನು ಪಾಸ್ ಮಾಡಿರುವಿರಿ - ನಿಮ್ಮ ಸೀಡ್‌ಫ್ರೇಸ್ ಸುರಕ್ಷಿತವಾಗಿರಿಸಿ, ಅದು ನಿಮ್ಮ ಜವಾಬ್ದಾರಿಯಾಗಿದೆ!"
},

View File

@ -356,9 +356,6 @@
"editContact": {
"message": "연락처 수정"
},
"emailUs": {
"message": "저자에게 메일 보내기!"
},
"endOfFlowMessage1": {
"message": "인증을 통과했습니다 - 시드 구문을 안전하게 보관하세요. 그것은 당신의 의무입니다."
},

View File

@ -359,9 +359,6 @@
"editContact": {
"message": "Taisyti kontaktą"
},
"emailUs": {
"message": "Rašykite mums el. paštu!"
},
"endOfFlowMessage1": {
"message": "Jūs perėjote testą laikykite saugiai savo atkūrimo frazę, tai jūsų atsakomybė!"
},

View File

@ -359,9 +359,6 @@
"editContact": {
"message": "Rediģēt līgumu"
},
"emailUs": {
"message": "Raksiet mums e-pastu!"
},
"endOfFlowMessage1": {
"message": "Jūs izgājāt pārbaudi — glabājiet atkopšanas frāzi drošībā, tā ir jūsu atbildība!"
},

View File

@ -347,9 +347,6 @@
"editContact": {
"message": "Edit Kenalan"
},
"emailUs": {
"message": "Hantarkan e-mel kepada kami!"
},
"endOfFlowMessage1": {
"message": "Anda telah lulus ujian - simpan frasa benih anda di tempat yang selamat, itu tanggungjawab anda!"
},

View File

@ -118,9 +118,6 @@
"edit": {
"message": "Bewerk"
},
"emailUs": {
"message": "Email ons!"
},
"enterPassword": {
"message": "Voer wachtwoord in"
},

View File

@ -356,9 +356,6 @@
"editContact": {
"message": "Rediger kontakt"
},
"emailUs": {
"message": "Send oss en e-post!"
},
"endOfFlowMessage1": {
"message": "Du besto prøven - oppbevar den mnemoniske gjenopprettingsfrasen din på et sikkert sted, det er ditt ansvar! "
},

View File

@ -356,9 +356,6 @@
"editContact": {
"message": "Edytuj kontakt"
},
"emailUs": {
"message": "Napisz do nas!"
},
"endOfFlowMessage1": {
"message": "Zaliczyłeś test zabezpiecz swoją frazę seed, to Twoja odpowiedzialność!"
},

View File

@ -124,9 +124,6 @@
"edit": {
"message": "Editar"
},
"emailUs": {
"message": "Fale connosco!"
},
"enterPassword": {
"message": "Introduza palavra-passe"
},

View File

@ -353,9 +353,6 @@
"editContact": {
"message": "Editar Contato"
},
"emailUs": {
"message": "Escreva para nós!"
},
"endOfFlowMessage1": {
"message": "Você passou no teste — mantenha sua frase semente segura, a responsabilidade é sua!"
},

View File

@ -359,9 +359,6 @@
"editContact": {
"message": "Editați contact"
},
"emailUs": {
"message": "Trimiteți-ne un email!"
},
"endOfFlowMessage1": {
"message": "Ați trecut testul păstrați expresia sursă în siguranță, este răspunderea dvs.!"
},

View File

@ -154,9 +154,6 @@
"edit": {
"message": "Редактировать"
},
"emailUs": {
"message": "Свяжитесь с нами по электронной почте!"
},
"enterPassword": {
"message": "Введите пароль"
},

View File

@ -350,9 +350,6 @@
"editContact": {
"message": "Upraviť kontakt"
},
"emailUs": {
"message": "Napište nám e-mail!"
},
"endOfFlowMessage1": {
"message": "Úspešne ste prešli testom uchovávajte svoju seed frázu v bezpečí. Je to vaša zodpovednosť!"
},

View File

@ -359,9 +359,6 @@
"editContact": {
"message": "Uredi stik"
},
"emailUs": {
"message": "Pišite nam!"
},
"endOfFlowMessage1": {
"message": "Opravili ste test - vaše geslo seed phrase je vaša odgovornost - skrbno pazite nanj!"
},

View File

@ -356,9 +356,6 @@
"editContact": {
"message": "Izmeni kontakt"
},
"emailUs": {
"message": "Pošaljite nam e-poštu!"
},
"endOfFlowMessage1": {
"message": "Prošli ste test - čuvajte svoju frazu početnih vrednosti, to je vaša odgovornost!"
},

View File

@ -353,9 +353,6 @@
"editContact": {
"message": "Redigera kontakt"
},
"emailUs": {
"message": "Mejla oss!"
},
"endOfFlowMessage1": {
"message": "Du klarade testet. Håll din seed phrase hemlig, den är på ditt ansvar!"
},

View File

@ -353,9 +353,6 @@
"editContact": {
"message": "Hariri Mawasiliano"
},
"emailUs": {
"message": "Tutumie barua pepe!"
},
"endOfFlowMessage1": {
"message": "Umefaulu jaribio - weka kirai chako cha kuanzia mahali salama, ni wajibu wako!"
},

View File

@ -139,9 +139,6 @@
"edit": {
"message": "திருத்து"
},
"emailUs": {
"message": "எங்களுக்கு மின்னஞ்சல்!"
},
"enterPassword": {
"message": "கடவுச்சொல்லை உள்ளிடவும்"
},

View File

@ -178,9 +178,6 @@
"editContact": {
"message": "แก้ไขผู้ติดต่อ"
},
"emailUs": {
"message": "อีเมลหาเรา!"
},
"endOfFlowMessage1": {
"message": "คุณผ่านการทดสอบแล้ว กรุณารับผิดชอบในการรักษา Seed Phrase ให้ปลอดภัย"
},

View File

@ -151,9 +151,6 @@
"edit": {
"message": "Düzenle"
},
"emailUs": {
"message": "Bize e-posta atın!"
},
"enterPassword": {
"message": "Parolanızı girin"
},

View File

@ -359,9 +359,6 @@
"editContact": {
"message": "Редагувати контракт"
},
"emailUs": {
"message": "Напишіть нам!"
},
"endOfFlowMessage1": {
"message": "Ви пройшли тест, зберігайте вашу початкову фразу в безпеці - це ваша відповідальність!"
},

View File

@ -359,9 +359,6 @@
"editContact": {
"message": "编辑联系人"
},
"emailUs": {
"message": "联系我们"
},
"endOfFlowMessage1": {
"message": "您已通过测试 - 请妥善保管你的种子密语。这是您的责任!"
},

View File

@ -356,9 +356,6 @@
"editContact": {
"message": "編輯聯絡資訊"
},
"emailUs": {
"message": "寄 Email 給我們!"
},
"endOfFlowMessage1": {
"message": "你通過測試了—安全存放助記詞,這是你的責任!"
},

View File

@ -1,7 +1,7 @@
{
"name": "__MSG_appName__",
"short_name": "__MSG_appName__",
"version": "8.0.6",
"version": "8.0.7",
"manifest_version": 2,
"author": "https://metamask.io",
"description": "__MSG_appDescription__",

View File

@ -7,7 +7,7 @@ import { CapabilitiesController as RpcCap } from 'rpc-cap'
import { ethErrors } from 'eth-json-rpc-errors'
import { cloneDeep } from 'lodash'
import createMethodMiddleware from './methodMiddleware'
import createPermissionsMethodMiddleware from './permissionsMethodMiddleware'
import PermissionsLogController from './permissionsLog'
// Methods that do not require any permissions to use:
@ -90,7 +90,7 @@ export class PermissionsController {
engine.push(this.permissionsLog.createMiddleware())
engine.push(createMethodMiddleware({
engine.push(createPermissionsMethodMiddleware({
addDomainMetadata: this.addDomainMetadata.bind(this),
getAccounts: this.getAccounts.bind(this, origin),
getUnlockPromise: () => this._getUnlockPromise(true),

View File

@ -4,7 +4,7 @@ import { ethErrors } from 'eth-json-rpc-errors'
/**
* Create middleware for handling certain methods and preprocessing permissions requests.
*/
export default function createMethodMiddleware ({
export default function createPermissionsMethodMiddleware ({
addDomainMetadata,
getAccounts,
getUnlockPromise,

View File

@ -1,34 +1,30 @@
export default function getRestrictedMethods ({ getIdentities, getKeyringAccounts }) {
return {
'eth_accounts': {
method: (_, res, __, end) => {
getKeyringAccounts()
.then((accounts) => {
const identities = getIdentities()
res.result = accounts
.sort((firstAddress, secondAddress) => {
if (!identities[firstAddress]) {
throw new Error(`Missing identity for address ${firstAddress}`)
} else if (!identities[secondAddress]) {
throw new Error(`Missing identity for address ${secondAddress}`)
} else if (identities[firstAddress].lastSelected === identities[secondAddress].lastSelected) {
return 0
} else if (identities[firstAddress].lastSelected === undefined) {
return 1
} else if (identities[secondAddress].lastSelected === undefined) {
return -1
}
method: async (_, res, __, end) => {
try {
const accounts = await getKeyringAccounts()
const identities = getIdentities()
res.result = accounts.sort((firstAddress, secondAddress) => {
if (!identities[firstAddress]) {
throw new Error(`Missing identity for address ${firstAddress}`)
} else if (!identities[secondAddress]) {
throw new Error(`Missing identity for address ${secondAddress}`)
} else if (identities[firstAddress].lastSelected === identities[secondAddress].lastSelected) {
return 0
} else if (identities[firstAddress].lastSelected === undefined) {
return 1
} else if (identities[secondAddress].lastSelected === undefined) {
return -1
}
return identities[secondAddress].lastSelected - identities[firstAddress].lastSelected
})
end()
return identities[secondAddress].lastSelected - identities[firstAddress].lastSelected
})
.catch(
(err) => {
res.error = err
end(err)
},
)
end()
} catch (err) {
res.error = err
end(err)
}
},
},
}

View File

@ -434,7 +434,7 @@ export default class TransactionController extends EventEmitter {
// Since this transaction is async,
// we need to keep track of what is currently being signed,
// So that we do not increment nonce + resubmit something
// that is already being incrmented & signed.
// that is already being incremented & signed.
if (this.inProcessOfSigning.has(txId)) {
return
}

View File

@ -1,5 +1,3 @@
/*global Web3*/
// need to make sure we aren't affected by overlapping namespaces
// and that we dont affect the app with our namespace
// mostly a fix for web3's BigNumber if AMD's "define" is defined...
@ -37,9 +35,7 @@ import LocalMessageDuplexStream from 'post-message-stream'
import { initProvider } from '@metamask/inpage-provider'
// TODO:deprecate:2020
import 'web3/dist/web3.min.js'
import setupDappAutoReload from './lib/auto-reload.js'
import setupWeb3 from './lib/setupWeb3.js'
restoreContextAfterImports()
@ -59,11 +55,9 @@ initProvider({
connectionStream: metamaskStream,
})
//
// TODO:deprecate:2020
//
// Setup web3
// setup web3
if (typeof window.web3 !== 'undefined') {
throw new Error(`MetaMask detected another web3.
@ -73,18 +67,5 @@ if (typeof window.web3 !== 'undefined') {
and try again.`)
}
const web3 = new Web3(window.ethereum)
web3.setProvider = function () {
log.debug('MetaMask - overrode web3.setProvider')
}
log.debug('MetaMask - injected web3')
Object.defineProperty(window.ethereum, '_web3Ref', {
enumerable: false,
writable: true,
configurable: true,
value: web3.eth,
})
// setup dapp auto reload AND proxy web3
setupDappAutoReload(web3, window.ethereum._publicConfigStore)
// proxy web3, assign to window, and set up site auto reload
setupWeb3(log)

View File

@ -1,20 +1,16 @@
import { getBackgroundMetaMetricState } from '../../../ui/app/selectors'
import { sendMetaMetricsEvent } from '../../../ui/app/helpers/utils/metametrics.util'
const inDevelopment = process.env.NODE_ENV === 'development'
export default function backgroundMetaMetricsEvent (metaMaskState, eventData) {
const METAMETRICS_TRACKING_URL = inDevelopment
? 'http://www.metamask.io/metametrics'
: 'http://www.metamask.io/metametrics-prod'
eventData.eventOpts['category'] = 'Background'
export default function backEndMetaMetricsEvent (metaMaskState, eventData) {
const stateEventData = getBackgroundMetaMetricState({ metamask: metaMaskState })
if (stateEventData.participateInMetaMetrics) {
sendMetaMetricsEvent({
...stateEventData,
...eventData,
url: METAMETRICS_TRACKING_URL + '/backend',
currentPath: '/background',
})
}
}

View File

@ -0,0 +1,32 @@
/**
* Returns a middleware that implements the following RPC methods:
* - metamask_logInjectedWeb3Usage
*
* @param {Object} opts - The middleware options
* @param {string} opts.origin - The origin for the middleware stack
* @param {Function} opts.sendMetrics - A function for sending a metrics event
* @returns {(req: any, res: any, next: Function, end: Function) => void}
*/
export default function createMethodMiddleware ({ origin, sendMetrics }) {
return function methodMiddleware (req, res, next, end) {
switch (req.method) {
case 'metamask_logInjectedWeb3Usage':
const { action, name } = req.params[0]
sendMetrics({
action,
name,
customVariables: { origin },
})
res.result = true
break
default:
return next()
}
return end()
}
}

View File

@ -56,7 +56,7 @@ export default class NotificationManager {
})
// Firefox currently ignores left/top for create, but it works for update
if (popupWindow.left !== left) {
if (popupWindow.left !== left && popupWindow.state !== 'fullscreen') {
await this.platform.updateWindowPosition(popupWindow.id, left, top)
}
this._popupId = popupWindow.id

View File

@ -16,41 +16,33 @@ const seedPhraseVerifier = {
* @returns {Promise<void>} - Promises undefined
*
*/
verifyAccounts (createdAccounts, seedWords) {
async verifyAccounts (createdAccounts, seedWords) {
if (!createdAccounts || createdAccounts.length < 1) {
throw new Error('No created accounts defined.')
}
return new Promise((resolve, reject) => {
const keyringController = new KeyringController({})
const Keyring = keyringController.getKeyringClassForType('HD Key Tree')
const opts = {
mnemonic: seedWords,
numberOfAccounts: createdAccounts.length,
}
if (!createdAccounts || createdAccounts.length < 1) {
return reject(new Error('No created accounts defined.'))
const keyring = new Keyring(opts)
const restoredAccounts = await keyring.getAccounts()
log.debug('Created accounts: ' + JSON.stringify(createdAccounts))
log.debug('Restored accounts: ' + JSON.stringify(restoredAccounts))
if (restoredAccounts.length !== createdAccounts.length) {
// this should not happen...
throw new Error('Wrong number of accounts')
}
for (let i = 0; i < restoredAccounts.length; i++) {
if (restoredAccounts[i].toLowerCase() !== createdAccounts[i].toLowerCase()) {
throw new Error('Not identical accounts! Original: ' + createdAccounts[i] + ', Restored: ' + restoredAccounts[i])
}
const keyringController = new KeyringController({})
const Keyring = keyringController.getKeyringClassForType('HD Key Tree')
const opts = {
mnemonic: seedWords,
numberOfAccounts: createdAccounts.length,
}
const keyring = new Keyring(opts)
keyring.getAccounts()
.then((restoredAccounts) => {
log.debug('Created accounts: ' + JSON.stringify(createdAccounts))
log.debug('Restored accounts: ' + JSON.stringify(restoredAccounts))
if (restoredAccounts.length !== createdAccounts.length) {
// this should not happen...
return reject(new Error('Wrong number of accounts'))
}
for (let i = 0; i < restoredAccounts.length; i++) {
if (restoredAccounts[i].toLowerCase() !== createdAccounts[i].toLowerCase()) {
return reject(new Error('Not identical accounts! Original: ' + createdAccounts[i] + ', Restored: ' + restoredAccounts[i]))
}
}
return resolve()
})
})
}
},
}

View File

@ -5,7 +5,6 @@ import extractEthjsErrorMessage from './extractEthjsErrorMessage'
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
const METAMASK_ENVIRONMENT = process.env.METAMASK_ENVIRONMENT
const SENTRY_DSN_PROD = 'https://3567c198f8a8412082d32655da2961d0@sentry.io/273505'
const SENTRY_DSN_DEV = 'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496'
// This describes the subset of Redux state attached to errors sent to Sentry
@ -72,12 +71,17 @@ export const SENTRY_STATE = {
export default function setupSentry ({ release, getState }) {
let sentryTarget
if (METAMASK_DEBUG || process.env.IN_TEST) {
if (METAMASK_DEBUG) {
return
} else if (METAMASK_ENVIRONMENT === 'production') {
if (!process.env.SENTRY_DSN) {
throw new Error(`Missing SENTRY_DSN environment variable in production environment`)
}
console.log(`Setting up Sentry Remote Error Reporting for '${METAMASK_ENVIRONMENT}': SENTRY_DSN`)
sentryTarget = process.env.SENTRY_DSN
} else {
console.log(`Setting up Sentry Remote Error Reporting for '${METAMASK_ENVIRONMENT}': SENTRY_DSN_DEV`)
sentryTarget = SENTRY_DSN_DEV
} else {
console.log(`Setting up Sentry Remote Error Reporting for '${METAMASK_ENVIRONMENT}': SENTRY_DSN_PROD`)
sentryTarget = SENTRY_DSN_PROD
}
Sentry.init({

View File

@ -1,26 +1,67 @@
/*global Web3*/
// TODO:deprecate:2020
// Delete this file
export default function setupDappAutoReload (web3, observable) {
import 'web3/dist/web3.min.js'
const shouldLogUsage = !([
'docs.metamask.io',
'metamask.github.io',
'metamask.io',
].includes(window.location.hostname))
export default function setupWeb3 (log) {
// export web3 as a global, checking for usage
let reloadInProgress = false
let lastTimeUsed
let lastSeenNetwork
let hasBeenWarned = false
const web3 = new Web3(window.ethereum)
web3.setProvider = function () {
log.debug('MetaMask - overrode web3.setProvider')
}
log.debug('MetaMask - injected web3')
Object.defineProperty(window.ethereum, '_web3Ref', {
enumerable: false,
writable: true,
configurable: true,
value: web3.eth,
})
const web3Proxy = new Proxy(web3, {
get: (_web3, key) => {
// get the time of use
lastTimeUsed = Date.now()
// show warning once on web3 access
if (!hasBeenWarned && key !== 'currentProvider') {
if (!hasBeenWarned) {
console.warn(`MetaMask: We will stop injecting web3 in Q4 2020.\nPlease see this article for more information: https://medium.com/metamask/no-longer-injecting-web3-js-4a899ad6e59e`)
hasBeenWarned = true
}
if (shouldLogUsage) {
window.ethereum.request({
method: 'metamask_logInjectedWeb3Usage',
params: [{ action: 'window.web3 get', name: key }],
})
}
// return value normally
return _web3[key]
},
set: (_web3, key, value) => {
if (shouldLogUsage) {
window.ethereum.request({
method: 'metamask_logInjectedWeb3Usage',
params: [{ action: 'window.web3 set', name: key }],
})
}
// set value normally
_web3[key] = value
},
@ -33,7 +74,7 @@ export default function setupDappAutoReload (web3, observable) {
value: web3Proxy,
})
observable.subscribe(function (state) {
window.ethereum._publicConfigStore.subscribe((state) => {
// if the auto refresh on network change is false do not
// do anything
if (!window.ethereum.autoRefreshOnNetworkChange) {

View File

@ -19,6 +19,7 @@ import createEngineStream from 'json-rpc-middleware-stream/engineStream'
import createFilterMiddleware from 'eth-json-rpc-filters'
import createSubscriptionManager from 'eth-json-rpc-filters/subscriptionManager'
import createLoggerMiddleware from './lib/createLoggerMiddleware'
import createMethodMiddleware from './lib/createMethodMiddleware'
import createOriginMiddleware from './lib/createOriginMiddleware'
import createTabIdMiddleware from './lib/createTabIdMiddleware'
import createOnboardingMiddleware from './lib/createOnboardingMiddleware'
@ -66,7 +67,7 @@ import {
PhishingController,
} from '@metamask/controllers'
import backEndMetaMetricsEvent from './lib/backend-metametrics'
import backgroundMetaMetricsEvent from './lib/background-metametrics'
export default class MetamaskController extends EventEmitter {
@ -249,18 +250,11 @@ export default class MetamaskController extends EventEmitter {
this.platform.showTransactionNotification(txMeta)
const { txReceipt } = txMeta
const participateInMetaMetrics = this.preferencesController.getParticipateInMetaMetrics()
if (txReceipt && txReceipt.status === '0x0' && participateInMetaMetrics) {
const metamaskState = await this.getState()
backEndMetaMetricsEvent(metamaskState, {
customVariables: {
errorMessage: txMeta.simulationFails?.reason,
},
eventOpts: {
category: 'backend',
action: 'Transactions',
name: 'On Chain Failure',
},
if (txReceipt && txReceipt.status === '0x0') {
this.sendBackgroundMetaMetrics({
action: 'Transactions',
name: 'On Chain Failure',
customVariables: { errorMessage: txMeta.simulationFails?.reason },
})
}
}
@ -478,6 +472,7 @@ export default class MetamaskController extends EventEmitter {
// vault management
submitPassword: nodeify(this.submitPassword, this),
verifyPassword: nodeify(this.verifyPassword, this),
// network management
setProviderType: nodeify(networkController.setProviderType, networkController),
@ -808,6 +803,15 @@ export default class MetamaskController extends EventEmitter {
return this.keyringController.fullUpdate()
}
/**
* Submits a user's password to check its validity.
*
* @param {string} password The user's password
*/
async verifyPassword (password) {
await this.keyringController.verifyPassword(password)
}
/**
* @type Identity
* @property {string} name - The account nickname.
@ -1627,6 +1631,10 @@ export default class MetamaskController extends EventEmitter {
location,
registerOnboarding: this.onboardingController.registerOnboarding,
}))
engine.push(createMethodMiddleware({
origin,
sendMetrics: this.sendBackgroundMetaMetrics.bind(this),
}))
// filter and subscription polyfills
engine.push(filterMiddleware)
engine.push(subscriptionManager.middleware)
@ -1712,7 +1720,7 @@ export default class MetamaskController extends EventEmitter {
delete connections[id]
if (Object.keys(connections.length === 0)) {
if (Object.keys(connections).length === 0) {
delete this.connections[origin]
}
}
@ -1827,6 +1835,22 @@ export default class MetamaskController extends EventEmitter {
return nonceLock.nextNonce
}
async sendBackgroundMetaMetrics ({ action, name, customVariables } = {}) {
if (!action || !name) {
throw new Error('Must provide action and name.')
}
const metamaskState = await this.getState()
backgroundMetaMetricsEvent(metamaskState, {
customVariables,
eventOpts: {
action,
name,
},
})
}
//=============================================================================
// CONFIG
//=============================================================================

View File

@ -206,6 +206,9 @@ function createScriptTasks ({ browserPlatforms, livereload }) {
mangle: {
reserved: [ 'MetamaskInpageProvider' ],
},
sourceMap: {
content: true,
},
}))
}
@ -313,33 +316,23 @@ function createScriptTasks ({ browserPlatforms, livereload }) {
bundler = bundler.external(opts.externalDependencies)
}
let environment
if (opts.devMode) {
environment = 'development'
} else if (opts.testing) {
environment = 'testing'
} else if (process.env.CIRCLE_BRANCH === 'master') {
environment = 'production'
} else if (/^Version-v(\d+)[.](\d+)[.](\d+)/.test(process.env.CIRCLE_BRANCH)) {
environment = 'release-candidate'
} else if (process.env.CIRCLE_BRANCH === 'develop') {
environment = 'staging'
} else if (process.env.CIRCLE_PULL_REQUEST) {
environment = 'pull-request'
} else {
environment = 'other'
const environment = getEnvironment({ devMode: opts.devMode, test: opts.testing })
if (environment === 'production' && !process.env.SENTRY_DSN) {
throw new Error('Missing SENTRY_DSN environment variable')
}
// Inject variables into bundle
bundler.transform(envify({
METAMASK_DEBUG: opts.devMode,
METAMASK_ENVIRONMENT: environment,
METAMETRICS_PROJECT_ID: process.env.METAMETRICS_PROJECT_ID,
NODE_ENV: opts.devMode ? 'development' : 'production',
IN_TEST: opts.testing ? 'true' : false,
PUBNUB_SUB_KEY: process.env.PUBNUB_SUB_KEY || '',
PUBNUB_PUB_KEY: process.env.PUBNUB_PUB_KEY || '',
ETH_GAS_STATION_API_KEY: process.env.ETH_GAS_STATION_API_KEY || '',
CONF: opts.devMode ? conf : ({}),
SENTRY_DSN: process.env.SENTRY_DSN,
}), {
global: true,
})
@ -363,3 +356,22 @@ function createScriptTasks ({ browserPlatforms, livereload }) {
function beep () {
process.stdout.write('\x07')
}
function getEnvironment ({ devMode, test }) {
// get environment slug
if (devMode) {
return 'development'
} else if (test) {
return 'testing'
} else if (process.env.CIRCLE_BRANCH === 'master') {
return 'production'
} else if (/^Version-v(\d+)[.](\d+)[.](\d+)/.test(process.env.CIRCLE_BRANCH)) {
return 'release-candidate'
} else if (process.env.CIRCLE_BRANCH === 'develop') {
return 'staging'
} else if (process.env.CIRCLE_PULL_REQUEST) {
return 'pull-request'
} else {
return 'other'
}
}

View File

@ -13,13 +13,31 @@ const fsAsync = pify(fs)
// if not working it may error or print minified garbage
//
start().catch(console.error)
start().catch((error) => {
console.error(error)
process.exit(1)
})
async function start () {
const targetFiles = [`inpage.js`, `contentscript.js`, `ui.js`, `background.js`]
const targetFiles = [
`background.js`,
// `bg-libs`, skipped because source maps are invalid due to browserify bug: https://github.com/browserify/browserify/issues/1971
// `contentscript.js`, skipped because the validator is erroneously sampling the inlined `inpage.js` script
`inpage.js`,
'phishing-detect.js',
`ui.js`,
// `ui-libs.js`, skipped because source maps are invalid due to browserify bug: https://github.com/browserify/browserify/issues/1971
]
let valid = true
for (const buildName of targetFiles) {
await validateSourcemapForFile({ buildName })
const fileIsValid = await validateSourcemapForFile({ buildName })
valid = valid && fileIsValid
}
if (!valid) {
process.exit(1)
}
}
@ -59,6 +77,7 @@ async function validateSourcemapForFile ({ buildName }) {
console.log(` sampling from ${consumer.sources.length} files`)
let sampleCount = 0
let valid = true
const buildLines = rawBuild.split('\n')
const targetString = 'new Error'
@ -71,6 +90,7 @@ async function validateSourcemapForFile ({ buildName }) {
const result = consumer.originalPositionFor(position)
// warn if source content is missing
if (!result.source) {
valid = false
console.warn(`!! missing source for position: ${JSON.stringify(position)}`)
// const buildLine = buildLines[position.line - 1]
console.warn(` origin in build:`)
@ -86,14 +106,27 @@ async function validateSourcemapForFile ({ buildName }) {
const portion = line.slice(result.column)
const isMaybeValid = portion.includes(targetString)
if (!isMaybeValid) {
console.error('Sourcemap seems invalid:')
console.log(`\n========================== ${result.source} ====================================\n`)
console.log(line)
console.log(`\n==============================================================================\n`)
valid = false
console.error(`Sourcemap seems invalid:\n${getFencedCode(result.source, line)}`)
}
})
})
console.log(` checked ${sampleCount} samples`)
return valid
}
const CODE_FENCE_LENGTH = 80
const TITLE_PADDING_LENGTH = 1
function getFencedCode (filename, code) {
const title = `${' '.repeat(TITLE_PADDING_LENGTH)}${filename}${' '.repeat(TITLE_PADDING_LENGTH)}`
const openingFenceLength = Math.max(CODE_FENCE_LENGTH - (filename.length + (TITLE_PADDING_LENGTH * 2)), 0)
const startOpeningFenceLength = Math.floor(openingFenceLength / 2)
const endOpeningFenceLength = Math.ceil(openingFenceLength / 2)
const openingFence = `${'='.repeat(startOpeningFenceLength)}${title}${'='.repeat(endOpeningFenceLength)}`
const closingFence = '='.repeat(CODE_FENCE_LENGTH)
return `${openingFence}\n${code}\n${closingFence}\n`
}
function indicesOf (substring, string) {

View File

@ -22,8 +22,6 @@
"test:unit:strict": "mocha --exit --require test/env.js --require test/setup.js --recursive \"test/unit/**/permissions/*.js\"",
"test:unit:path": "mocha --exit --require test/env.js --require test/setup.js --recursive",
"test:e2e:chrome": "SELENIUM_BROWSER=chrome test/e2e/run-all.sh",
"test:web3:chrome": "SELENIUM_BROWSER=chrome test/e2e/run-web3.sh",
"test:web3:firefox": "SELENIUM_BROWSER=firefox test/e2e/run-web3.sh",
"test:e2e:firefox": "SELENIUM_BROWSER=firefox test/e2e/run-all.sh",
"test:coverage": "nyc --silent --check-coverage yarn test:unit:strict && nyc --silent --no-clean yarn test:unit:lax && nyc report --reporter=text --reporter=html",
"test:coverage:strict": "nyc --check-coverage yarn test:unit:strict",
@ -37,6 +35,7 @@
"lint:shellcheck": "./development/shellcheck.sh",
"lint:styles": "stylelint '*/**/*.scss'",
"lint:lockfile": "lockfile-lint --path yarn.lock --allowed-hosts npm yarn github.com codeload.github.com --empty-hostname false --allowed-schemes \"https:\" \"git+https:\"",
"validate-source-maps": "node ./development/sourcemap-validator.js",
"verify-locales": "node ./development/verify-locale-strings.js",
"verify-locales:fix": "node ./development/verify-locale-strings.js --fix",
"mozilla-lint": "addons-linter dist/firefox",
@ -52,10 +51,13 @@
"generate:migration": "./development/generate-migration.sh"
},
"resolutions": {
"**/configstore/dot-prop": "^5.1.1",
"**/ethers/elliptic": "^6.5.3",
"**/knex/minimist": "^1.2.5",
"**/optimist/minimist": "^1.2.5",
"**/socketcluster/minimist": "^1.2.5",
"3box/ipfs/ipld-zcash/zcash-bitcore-lib/lodash": "^4.17.19",
"3box/ipfs/ipld-zcash/zcash-bitcore-lib/elliptic": "^6.5.3",
"ganache-core/lodash": "^4.17.19"
},
"dependencies": {
@ -98,7 +100,7 @@
"eth-json-rpc-filters": "^4.1.1",
"eth-json-rpc-infura": "^4.0.2",
"eth-json-rpc-middleware": "^5.0.2",
"eth-keyring-controller": "^6.0.1",
"eth-keyring-controller": "^6.1.0",
"eth-method-registry": "^1.2.0",
"eth-phishing-detect": "^1.1.4",
"eth-query": "^2.1.2",
@ -119,12 +121,12 @@
"fuse.js": "^3.2.0",
"human-standard-token-abi": "^2.0.0",
"jazzicon": "^2.0.0",
"json-rpc-engine": "^5.1.8",
"json-rpc-engine": "^5.2.0",
"json-rpc-middleware-stream": "^2.1.1",
"jsonschema": "^1.2.4",
"lodash": "^4.17.19",
"loglevel": "^1.4.1",
"luxon": "^1.23.0",
"luxon": "^1.24.1",
"metamask-logo": "^2.1.4",
"multihashes": "^0.4.12",
"nanoid": "^2.1.6",
@ -157,7 +159,7 @@
"redux": "^4.0.5",
"redux-thunk": "^2.3.0",
"reselect": "^3.0.1",
"rpc-cap": "^3.0.1",
"rpc-cap": "^3.1.0",
"safe-event-emitter": "^1.0.1",
"safe-json-stringify": "^1.2.0",
"single-call-balance-checker-abi": "^1.0.0",
@ -192,8 +194,8 @@
"babel-eslint": "^10.1.0",
"babel-loader": "^8.0.6",
"babelify": "^10.0.0",
"brfs": "^1.6.1",
"browserify": "^16.2.3",
"brfs": "^2.0.2",
"browserify": "^16.5.1",
"browserify-derequire": "^1.0.1",
"browserify-transform-tools": "^1.7.0",
"chai": "^4.1.0",
@ -230,13 +232,13 @@
"gulp-imagemin": "^6.1.0",
"gulp-livereload": "4.0.0",
"gulp-multi-process": "^1.3.1",
"gulp-rename": "^1.4.0",
"gulp-rename": "^2.0.0",
"gulp-replace": "^1.0.0",
"gulp-rtlcss": "^1.4.0",
"gulp-sass": "^4.0.0",
"gulp-sourcemaps": "^2.6.0",
"gulp-stylelint": "^13.0.0",
"gulp-terser-js": "^5.0.0",
"gulp-terser-js": "^5.2.2",
"gulp-watch": "^5.0.1",
"gulp-zip": "^4.0.0",
"jsdom": "^11.2.0",
@ -251,7 +253,7 @@
"proxyquire": "^2.1.3",
"randomcolor": "^0.5.4",
"rc": "^1.2.8",
"react-devtools": "^4.4.0",
"react-devtools": "^4.8.0",
"react-test-renderer": "^16.12.0",
"read-installed": "^4.0.3",
"redux-mock-store": "^1.5.4",
@ -263,10 +265,10 @@
"selenium-webdriver": "^4.0.0-alpha.5",
"serve-handler": "^6.1.2",
"sesify": "^4.2.1",
"sesify-viz": "^3.0.5",
"sesify-viz": "^3.0.10",
"sinon": "^9.0.0",
"source-map": "^0.7.2",
"source-map-explorer": "^2.0.1",
"source-map-explorer": "^2.4.2",
"string.prototype.matchall": "^4.0.2",
"style-loader": "^0.21.0",
"stylelint": "^13.6.1",

View File

@ -5,72 +5,91 @@ set -e
set -u
set -o pipefail
retry () {
retry=0
limit="${METAMASK_E2E_RETRY_LIMIT:-3}"
while [[ $retry -lt $limit ]]
do
"$@" && break
retry=$(( retry + 1 ))
sleep 1
done
if [[ $retry == "$limit" ]]
then
exit 1
fi
}
export PATH="$PATH:./node_modules/.bin"
mocha --no-timeouts test/e2e/tests/*.spec.js
for spec in test/e2e/tests/*.spec.js
do
retry mocha --no-timeouts "${spec}"
done
concurrently --kill-others \
retry concurrently --kill-others \
--names 'dapp,e2e' \
--prefix '[{time}][{name}]' \
--success first \
'yarn dapp' \
'mocha test/e2e/metamask-ui.spec'
concurrently --kill-others \
retry concurrently --kill-others \
--names 'dapp,e2e' \
--prefix '[{time}][{name}]' \
--success first \
'yarn dapp' \
'mocha test/e2e/metamask-responsive-ui.spec'
concurrently --kill-others \
retry concurrently --kill-others \
--names 'dapp,e2e' \
--prefix '[{time}][{name}]' \
--success first \
'yarn dapp' \
'mocha test/e2e/signature-request.spec'
concurrently --kill-others \
retry concurrently --kill-others \
--names 'e2e' \
--prefix '[{time}][{name}]' \
--success first \
'mocha test/e2e/from-import-ui.spec'
concurrently --kill-others \
retry concurrently --kill-others \
--names 'e2e' \
--prefix '[{time}][{name}]' \
--success first \
'mocha test/e2e/send-edit.spec'
concurrently --kill-others \
retry concurrently --kill-others \
--names 'dapp,e2e' \
--prefix '[{time}][{name}]' \
--success first \
'yarn dapp' \
'mocha test/e2e/ethereum-on.spec'
concurrently --kill-others \
retry concurrently --kill-others \
--names 'dapp,e2e' \
--prefix '[{time}][{name}]' \
--success first \
'yarn dapp' \
'mocha test/e2e/permissions.spec'
concurrently --kill-others \
retry concurrently --kill-others \
--names 'sendwithprivatedapp,e2e' \
--prefix '[{time}][{name}]' \
--success first \
'yarn sendwithprivatedapp' \
'mocha test/e2e/incremental-security.spec'
concurrently --kill-others \
retry concurrently --kill-others \
--names 'dapp,e2e' \
--prefix '[{time}][{name}]' \
--success first \
'yarn dapp' \
'mocha test/e2e/address-book.spec'
concurrently --kill-others \
retry concurrently --kill-others \
--names '3box,dapp,e2e' \
--prefix '[{time}][{name}]' \
--success first \

View File

@ -1,13 +0,0 @@
#!/usr/bin/env bash
set -e
set -u
set -o pipefail
export PATH="$PATH:./node_modules/.bin"
concurrently --kill-others \
--names 'dapp,e2e' \
--prefix '[{time}][{name}]' \
'node development/static-server.js test/web3 --port 8080' \
'sleep 5 && mocha test/e2e/web3.spec'

View File

@ -1,288 +0,0 @@
const assert = require('assert')
const webdriver = require('selenium-webdriver')
const { By } = webdriver
const {
regularDelayMs,
largeDelayMs,
} = require('./helpers')
const { buildWebDriver } = require('./webdriver')
const enLocaleMessages = require('../../app/_locales/en/messages.json')
describe('Using MetaMask with an existing account', function () {
let driver
const testSeedPhrase = 'forum vessel pink push lonely enact gentle tail admit parrot grunt dress'
const button = async (x) => {
const buttoncheck = x
await buttoncheck.click()
await driver.delay(largeDelayMs)
const [results] = await driver.findElements(By.css('#results'))
const resulttext = await results.getText()
const parsedData = JSON.parse(resulttext)
return (parsedData)
}
this.timeout(0)
this.bail(true)
before(async function () {
const result = await buildWebDriver()
driver = result.driver
})
afterEach(async function () {
if (process.env.SELENIUM_BROWSER === 'chrome') {
const errors = await driver.checkBrowserForConsoleErrors(driver)
if (errors.length) {
const errorReports = errors.map((err) => err.message)
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}`
console.error(new Error(errorMessage))
}
}
if (this.currentTest.state === 'failed') {
await driver.verboseReportOnFailure(this.currentTest.title)
}
})
after(async function () {
await driver.quit()
})
describe('First time flow starting from an existing seed phrase', function () {
it('clicks the continue button on the welcome screen', async function () {
await driver.findElement(By.css('.welcome-page__header'))
await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`))
await driver.delay(largeDelayMs)
})
it('clicks the "Import Wallet" option', async function () {
await driver.clickElement(By.xpath(`//button[contains(text(), 'Import wallet')]`))
await driver.delay(largeDelayMs)
})
it('clicks the "No thanks" option on the metametrics opt-in screen', async function () {
await driver.clickElement(By.css('.btn-default'))
await driver.delay(largeDelayMs)
})
it('imports a seed phrase', async function () {
const [seedTextArea] = await driver.findElements(By.css('input[placeholder="Paste seed phrase from clipboard"]'))
await seedTextArea.sendKeys(testSeedPhrase)
await driver.delay(regularDelayMs)
const [password] = await driver.findElements(By.id('password'))
await password.sendKeys('correct horse battery staple')
const [confirmPassword] = await driver.findElements(By.id('confirm-password'))
confirmPassword.sendKeys('correct horse battery staple')
await driver.clickElement(By.css('.first-time-flow__terms'))
await driver.clickElement(By.xpath(`//button[contains(text(), 'Import')]`))
await driver.delay(regularDelayMs)
})
it('clicks through the success screen', async function () {
await driver.findElement(By.xpath(`//div[contains(text(), 'Congratulations')]`))
await driver.clickElement(By.xpath(`//button[contains(text(), '${enLocaleMessages.endOfFlowMessage10.message}')]`))
await driver.delay(regularDelayMs)
})
})
describe('opens dapp', function () {
it('switches to mainnet', async function () {
await driver.clickElement(By.css('.network-name'))
await driver.delay(regularDelayMs)
await driver.clickElement(By.xpath(`//span[contains(text(), 'Main Ethereum Network')]`))
await driver.delay(largeDelayMs * 2)
})
it('connects to dapp', async function () {
await driver.openNewPage('http://127.0.0.1:8080/')
await driver.delay(regularDelayMs)
await driver.clickElement(By.xpath(`//button[contains(text(), 'Connect')]`))
await driver.delay(regularDelayMs)
await driver.waitUntilXWindowHandles(3)
const windowHandles = await driver.getAllWindowHandles()
const extension = windowHandles[0]
const popup = await driver.switchToWindowWithTitle('MetaMask Notification', windowHandles)
const dapp = windowHandles.find((handle) => handle !== extension && handle !== popup)
await driver.delay(regularDelayMs)
await driver.clickElement(By.xpath(`//button[contains(text(), 'Connect')]`))
await driver.switchToWindow(dapp)
await driver.delay(regularDelayMs)
})
})
describe('testing web3 methods', function () {
it('testing hexa methods', async function () {
const List = await driver.findClickableElements(By.className('hexaNumberMethods'))
for (let i = 0; i < List.length; i++) {
try {
const parsedData = await button(List[i])
console.log(parsedData)
const result = parseInt(parsedData.result, 16)
assert.equal((typeof result === 'number'), true)
await driver.delay(regularDelayMs)
} catch (err) {
console.log(err)
assert(false)
}
}
})
it('testing booleanMethods', async function () {
const List = await driver.findClickableElement(By.className('booleanMethods'))
for (let i = 0; i < List.length; i++) {
try {
const parsedData = await button(List[i])
console.log(parsedData)
const result = parsedData.result
assert.equal(result, false)
await driver.delay(regularDelayMs)
} catch (err) {
console.log(err)
assert(false)
}
}
})
it('testing transactionMethods', async function () {
const List = await driver.findClickableElement(By.className('transactionMethods'))
for (let i = 0; i < List.length; i++) {
try {
const parsedData = await button(List[i])
console.log(parsedData.result.blockHash)
const result = []
result.push(parseInt(parsedData.result.blockHash, 16))
result.push(parseInt(parsedData.result.blockNumber, 16))
result.push(parseInt(parsedData.result.gas, 16))
result.push(parseInt(parsedData.result.gasPrice, 16))
result.push(parseInt(parsedData.result.hash, 16))
result.push(parseInt(parsedData.result.input, 16))
result.push(parseInt(parsedData.result.nonce, 16))
result.push(parseInt(parsedData.result.r, 16))
result.push(parseInt(parsedData.result.s, 16))
result.push(parseInt(parsedData.result.v, 16))
result.push(parseInt(parsedData.result.to, 16))
result.push(parseInt(parsedData.result.value, 16))
result.forEach((value) => {
assert.equal((typeof value === 'number'), true)
})
} catch (err) {
console.log(err)
assert(false)
}
}
})
it('testing blockMethods', async function () {
const List = await driver.findClickableElement(By.className('blockMethods'))
for (let i = 0; i < List.length; i++) {
try {
const parsedData = await button(List[i])
console.log(JSON.stringify(parsedData) + i)
console.log(parsedData.result.parentHash)
const result = parseInt(parsedData.result.parentHash, 16)
assert.equal((typeof result === 'number'), true)
await driver.delay(regularDelayMs)
} catch (err) {
console.log(err)
assert(false)
}
}
})
it('testing methods', async function () {
const List = await driver.findClickableElement(By.className('methods'))
let parsedData
let result
for (let i = 0; i < List.length; i++) {
try {
if (i === 2) {
parsedData = await button(List[i])
console.log(parsedData.result.blockHash)
result = parseInt(parsedData.result.blockHash, 16)
assert.equal((typeof result === 'number' || (result === 0)), true)
await driver.delay(regularDelayMs)
} else {
parsedData = await button(List[i])
console.log(parsedData.result)
result = parseInt(parsedData.result, 16)
assert.equal((typeof result === 'number' || (result === 0)), true)
await driver.delay(regularDelayMs)
}
} catch (err) {
console.log(err)
assert(false)
}
}
})
})
})

View File

@ -18,7 +18,6 @@ describe('NetworkController', function () {
.reply(200)
networkController = new NetworkController()
networkController.initializeProvider(networkControllerProviderConfig)
})
afterEach(function () {
@ -34,8 +33,9 @@ describe('NetworkController', function () {
assert.equal(providerProxy.test, true)
})
})
describe('#getNetworkState', function () {
it('should return loading when new', function () {
it('should return "loading" when new', function () {
const networkState = networkController.getNetworkState()
assert.equal(networkState, 'loading', 'network is loading')
})
@ -51,11 +51,13 @@ describe('NetworkController', function () {
describe('#setProviderType', function () {
it('should update provider.type', function () {
networkController.initializeProvider(networkControllerProviderConfig)
networkController.setProviderType('mainnet')
const type = networkController.getProviderConfig().type
assert.equal(type, 'mainnet', 'provider type is updated')
})
it('should set the network to loading', function () {
networkController.initializeProvider(networkControllerProviderConfig)
networkController.setProviderType('mainnet')
const loading = networkController.isNetworkLoading()
assert.ok(loading, 'network is loading')

View File

@ -19,25 +19,44 @@ export function grantPermissions (permController, origin, permissions) {
}
/**
* Sets the underlying rpc-cap requestUserApproval function, and returns
* a promise that's resolved once it has been set.
* Returns a wrapper for the given permissions controller's requestUserApproval
* function, so we don't have to worry about its internals.
*
* This function must be called on the given permissions controller every
* time you want such a Promise. As of writing, it's only called once per test.
* @param {PermissionsController} permController - The permissions controller.
* @return {Function} A convenient wrapper for the requestUserApproval function.
*/
export function getRequestUserApprovalHelper (permController) {
/**
* Returns a request object that can be passed to requestUserApproval.
*
* @param {string} id - The internal permissions request ID (not the RPC request ID).
* @param {string} [origin] - The origin of the request, if necessary.
* @returns {Object} The corresponding request object.
*/
return (id, origin = 'defaultOrigin') => {
return permController.permissions.requestUserApproval({ metadata: { id, origin } })
}
}
/**
* Returns a Promise that resolves once a pending user approval has been set.
* Calls the underlying requestUserApproval function as normal, and restores it
* once the Promise is resolved.
*
* This function must be called on the permissions controller for each request.
*
* @param {PermissionsController} - A permissions controller.
* @returns {Promise<void>} A Promise that resolves once a pending approval
* has been set.
*/
export function getUserApprovalPromise (permController) {
return new Promise((resolveForCaller) => {
permController.permissions.requestUserApproval = async (req) => {
const { origin, metadata: { id } } = req
return new Promise((resolve, reject) => {
permController.pendingApprovals.set(id, { origin, resolve, reject })
resolveForCaller()
})
const originalFunction = permController.permissions.requestUserApproval
return new Promise((resolveHelperPromise) => {
permController.permissions.requestUserApproval = (req) => {
const userApprovalPromise = originalFunction(req)
permController.permissions.requestUserApproval = originalFunction
resolveHelperPromise()
return userApprovalPromise
}
})
}

View File

@ -15,6 +15,7 @@ import {
} from '../../../../../app/scripts/controllers/permissions'
import {
getRequestUserApprovalHelper,
grantPermissions,
} from './helpers'
@ -58,12 +59,6 @@ const initPermController = (notifications = initNotifications()) => {
})
}
const getMockRequestUserApprovalFunction = (permController) => (id, origin) => {
return new Promise((resolve, reject) => {
permController.pendingApprovals.set(id, { origin, resolve, reject })
})
}
describe('permissions controller', function () {
describe('getAccounts', function () {
@ -951,13 +946,11 @@ describe('permissions controller', function () {
describe('approvePermissionsRequest', function () {
let permController, mockRequestUserApproval
let permController, requestUserApproval
beforeEach(function () {
permController = initPermController()
mockRequestUserApproval = getMockRequestUserApprovalFunction(
permController,
)
requestUserApproval = getRequestUserApprovalHelper(permController)
})
it('does nothing if called on non-existing request', async function () {
@ -994,14 +987,14 @@ describe('permissions controller', function () {
PERMS.requests.eth_accounts(),
)
const requestRejection = assert.rejects(
mockRequestUserApproval(REQUEST_IDS.a),
const rejectionPromise = assert.rejects(
requestUserApproval(REQUEST_IDS.a),
ERRORS.validatePermittedAccounts.invalidParam(),
'should reject bad accounts',
'should reject with "null" accounts',
)
await permController.approvePermissionsRequest(request, null)
await requestRejection
await rejectionPromise
assert.equal(
permController.pendingApprovals.size, 0,
@ -1014,7 +1007,7 @@ describe('permissions controller', function () {
const request = PERMS.approvedRequest(REQUEST_IDS.a, {})
const requestRejection = assert.rejects(
mockRequestUserApproval(REQUEST_IDS.a),
requestUserApproval(REQUEST_IDS.a),
ERRORS.approvePermissionsRequest.noPermsRequested(),
'should reject if no permissions in request',
)
@ -1036,7 +1029,7 @@ describe('permissions controller', function () {
const requestApproval = assert.doesNotReject(
async () => {
perms = await mockRequestUserApproval(REQUEST_IDS.a)
perms = await requestUserApproval(REQUEST_IDS.a)
},
'should not reject single valid request',
)
@ -1065,14 +1058,14 @@ describe('permissions controller', function () {
const approval1 = assert.doesNotReject(
async () => {
perms1 = await mockRequestUserApproval(REQUEST_IDS.a)
perms1 = await requestUserApproval(REQUEST_IDS.a, DOMAINS.a.origin)
},
'should not reject request',
)
const approval2 = assert.doesNotReject(
async () => {
perms2 = await mockRequestUserApproval(REQUEST_IDS.b)
perms2 = await requestUserApproval(REQUEST_IDS.b, DOMAINS.b.origin)
},
'should not reject request',
)
@ -1105,13 +1098,11 @@ describe('permissions controller', function () {
describe('rejectPermissionsRequest', function () {
let permController, mockRequestUserApproval
let permController, requestUserApproval
beforeEach(async function () {
permController = initPermController()
mockRequestUserApproval = getMockRequestUserApprovalFunction(
permController,
)
requestUserApproval = getRequestUserApprovalHelper(permController)
})
it('does nothing if called on non-existing request', async function () {
@ -1135,7 +1126,7 @@ describe('permissions controller', function () {
it('rejects single existing request', async function () {
const requestRejection = assert.rejects(
mockRequestUserApproval(REQUEST_IDS.a),
requestUserApproval(REQUEST_IDS.a),
ERRORS.rejectPermissionsRequest.rejection(),
'should reject with expected error',
)
@ -1152,13 +1143,13 @@ describe('permissions controller', function () {
it('rejects requests regardless of order', async function () {
const requestRejection1 = assert.rejects(
mockRequestUserApproval(REQUEST_IDS.b),
requestUserApproval(REQUEST_IDS.b, DOMAINS.b.origin),
ERRORS.rejectPermissionsRequest.rejection(),
'should reject with expected error',
)
const requestRejection2 = assert.rejects(
mockRequestUserApproval(REQUEST_IDS.c),
requestUserApproval(REQUEST_IDS.c, DOMAINS.c.origin),
ERRORS.rejectPermissionsRequest.rejection(),
'should reject with expected error',
)

View File

@ -70,11 +70,15 @@ describe('permissions middleware', function () {
)
const res = {}
const userApprovalPromise = getUserApprovalPromise(permController)
const pendingApproval = assert.doesNotReject(
aMiddleware(req, res),
'should not reject permissions request',
)
await userApprovalPromise
assert.equal(
permController.pendingApprovals.size, 1,
'perm controller should have single pending approval',
@ -131,11 +135,15 @@ describe('permissions middleware', function () {
// send, approve, and validate first request
// note use of ACCOUNTS.a.permitted
let userApprovalPromise = getUserApprovalPromise(permController)
const pendingApproval1 = assert.doesNotReject(
aMiddleware(req1, res1),
'should not reject permissions request',
)
await userApprovalPromise
const id1 = permController.pendingApprovals.keys().next().value
const approvedReq1 = PERMS.approvedRequest(id1, PERMS.requests.eth_accounts())
@ -187,11 +195,15 @@ describe('permissions middleware', function () {
// send, approve, and validate second request
// note use of ACCOUNTS.b.permitted
userApprovalPromise = getUserApprovalPromise(permController)
const pendingApproval2 = assert.doesNotReject(
aMiddleware(req2, res2),
'should not reject permissions request',
)
await userApprovalPromise
const id2 = permController.pendingApprovals.keys().next().value
const approvedReq2 = PERMS.approvedRequest(id2, { ...requestedPerms2 })
@ -251,12 +263,16 @@ describe('permissions middleware', function () {
const expectedError = ERRORS.rejectPermissionsRequest.rejection()
const userApprovalPromise = getUserApprovalPromise(permController)
const requestRejection = assert.rejects(
aMiddleware(req, res),
expectedError,
'request should be rejected with correct error',
)
await userApprovalPromise
assert.equal(
permController.pendingApprovals.size, 1,
'perm controller should have single pending approval',
@ -343,11 +359,15 @@ describe('permissions middleware', function () {
)
const resA1 = {}
let userApprovalPromise = getUserApprovalPromise(permController)
const requestApproval1 = assert.doesNotReject(
aMiddleware(reqA1, resA1),
'should not reject permissions request',
)
await userApprovalPromise
// create and start processing first request for second origin
const reqB1 = RPC_REQUESTS.requestPermission(
@ -355,11 +375,15 @@ describe('permissions middleware', function () {
)
const resB1 = {}
userApprovalPromise = getUserApprovalPromise(permController)
const requestApproval2 = assert.doesNotReject(
bMiddleware(reqB1, resB1),
'should not reject permissions request',
)
await userApprovalPromise
assert.equal(
permController.pendingApprovals.size, 2,
'perm controller should have expected number of pending approvals',
@ -373,12 +397,17 @@ describe('permissions middleware', function () {
)
const resA2 = {}
await assert.rejects(
userApprovalPromise = getUserApprovalPromise(permController)
const requestApprovalFail = assert.rejects(
aMiddleware(reqA2, resA2),
expectedError,
'request should be rejected with correct error',
)
await userApprovalPromise
await requestApprovalFail
assert.ok(
(
!resA2.result && resA2.error &&

View File

@ -1,105 +0,0 @@
<html>
<head>
<title>Web3 Test Dapp</title>
</head>
<body>
<div style="display: flex; flex-flow: column;">
<div style="display: flex; font-size: 1.25rem;">hexaNumberMethods</div>
<div style="display: flex;">
<button id="eth_blockNumber" class="hexaNumberMethods">eth_blockNumber</button>
<button id="eth_gasPrice" class="hexaNumberMethods">eth_gasPrice</button>
<button id="eth_newBlockFilter" class="hexaNumberMethods">eth_newBlockFilter</button>
<button id="eth_newPendingTransactionFilter" class="hexaNumberMethods">
eth_newPendingTransactionFilter
</button>
<button id="eth_getUncleCountByBlockHash" class="hexaNumberMethods">
eth_getUncleCountByBlockHash
</button>
<button id="eth_getBlockTransactionCountByHash" class="hexaNumberMethods">
getBlockTransactionCountByHash
</button>
</div>
<div style="display: flex ;">
<button id="eth_getTransactionCount" class="hexaNumberMethods">eth_getTransactionCount</button>
<button id="eth_getBalance" class="hexaNumberMethods">eth_getBalance</button>
<button id="eth_estimateGas" class="hexaNumberMethods">eth_estimateGas</button>
</div>
<div style="display: flex ;">
<button id="eth_getUncleCountByBlockNumber" class="hexaNumberMethods">
eth_getUncleCountByBlockNumber
</button>
<button id='eth_getBlockTransactionCountByNumber' class="hexaNumberMethods">
eth_getBlockTransactionCountByNumber
</button>
<button id="eth_protocolVersion" class="hexaNumberMethods">eth_protocolVersion</button>
<button id="eth_getCode" class="hexaNumberMethods">eth_getCode</button>
</div>
</div>
<div style="display: flex; flex-flow: column;">
<div style="display: flex; font-size: 1.25rem;">booleanMethods</div>
<div style="display: flex ;">
<button id="eth_uninstallFilter" class = 'booleanMethods'>eth_uninstallFilter</button>
<button id="eth_mining" class = 'booleanMethods'>eth_mining</button>
<button id="eth_syncing" class = 'booleanMethods'>eth_syncing</button>
</div>
</div>
<div style="display: flex; flex-flow: column;">
<div style="display: flex; font-size: 1.25rem;" >transactionMethods</div>
<div style="display: flex ;">
<button id="eth_getTransactionByHash" class='transactionMethods'>eth_getTransactionByHash</button>
<button id="eth_getTransactionByBlockHashAndIndex" class = 'transactionMethods'>
eth_getTransactionByBlockHashAndIndex
</button>
<button id="eth_getTransactionByBlockNumberAndIndex" class="transactionMethods">
eth_getTransactionByBlockNumberAndIndex
</button>
</div>
</div>
<div style="display: flex; flex-flow: column;">
<div style="display: flex; font-size: 1.25rem;">blockMethods</div>
<div style="display: flex ;">
<button id="eth_getUncleByBlockHashAndIndex" class="blockMethods">
eth_getUncleByBlockHashAndIndex
</button>
<button id="eth_getBlockByHash" class="blockMethods">eth_getBlockByHash</button>
</div>
<div style="display: flex ;">
<button id="eth_getBlockByNumber" class="blockMethods">eth_getBlockByNumber</button>
</div>
</div>
<div style="display: flex; flex-flow: column;">
<div style="display: flex; font-size: 1.25rem;">Methods</div>
<div style="display: flex ;">
<button id="eth_call" class = 'methods'>eth_call</button>
<button id="eth_getStorageAt" class="methods">eth_getStorageAt</button>
<button id="eth_getTransactionReceipt" class="methods">
eth_getTransactionReceipt
</button>
</div>
</div>
<div style="display: flex; flex-flow: column;">
<div id='results'></div>
</div>
</div>
<script src="schema.js"></script>
<script src="web3.js"></script>
</body>
</html>

View File

@ -1,209 +0,0 @@
/* eslint no-unused-vars: 0 */
const params = {
// diffrent params used in the methods
param: [],
blockHashParams: '0xb3b20624f8f0f86eb50dd04688409e5cea4bd02d700bf6e79e9384d47d6a5a35',
filterParams: ['0xfe704947a3cd3ca12541458a4321c869'],
transactionHashParams: [
'0xbb3a336e3f823ec18197f1e13ee875700f08f03e2cab75f0d0b118dabb44cba0',
],
blockHashAndIndexParams: [
'0xb3b20624f8f0f86eb50dd04688409e5cea4bd02d700bf6e79e9384d47d6a5a35',
'0x0',
],
uncleByBlockNumberAndIndexParams: ['0x29c', '0x0'],
blockParameterParams: '0x5bad55',
data: '0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675',
addressParams: '0xc94770007dda54cF92009BFF0dE90c06F603a09f',
getStorageAtParams: [
'0x295a70b2de5e3953354a6a8344e616ed314d7251',
'0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9',
'0x65a8db',
],
getCodeParams: ['0x06012c8cf97bead5deae237070f9587f8e7a266d', '0x65a8db'],
estimateTransaction: {
from: '0xb60e8dd61c5d32be8058bb8eb970870f07233155',
to: '0xd46e8dd67c5d32be8058bb8eb970870f07244567',
gas: '0x76c0',
gasPrice: '0x9184e72a000',
value: '0x9184e72a',
data: '0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675',
},
filterGetLogs: [{ 'blockHash': '0x7c5a35e9cb3e8ae0e221ab470abae9d446c3a5626ce6689fc777dcffcab52c70', 'topics': ['0x241ea03ca20251805084d27d4440371c34a0b85ff108f6bb5611248f73818b80'] }],
block: {
__required: [],
number: 'Q',
hash: 'D32',
parentHash: 'D32',
nonce: 'D',
sha3Uncles: 'D',
logsBloom: 'D',
transactionsRoot: 'D',
stateRoot: 'D',
receiptsRoot: 'D',
miner: 'D',
difficulty: 'Q',
totalDifficulty: 'Q',
extraData: 'D',
size: 'Q',
gasLimit: 'Q',
gasUsed: 'Q',
timestamp: 'Q',
transactions: ['DATA|Transaction'],
uncles: ['D'],
},
transaction: {
__required: [],
hash: 'D32',
nonce: 'Q',
blockHash: 'D32',
blockNumber: 'Q',
transactionIndex: 'Q',
from: 'D20',
to: 'D20',
value: 'Q',
gasPrice: 'Q',
gas: 'Q',
input: 'D',
},
receipt: {
__required: [],
transactionHash: 'D32',
transactionIndex: 'Q',
blockHash: 'D32',
blockNumber: 'Q',
cumulativeGasUsed: 'Q',
gasUsed: 'Q',
contractAddress: 'D20',
logs: ['FilterChange'],
},
filterChange: {
__required: [],
removed: 'B',
logIndex: 'Q',
transactionIndex: 'Q',
transactionHash: 'D32',
blockHash: 'D32',
blockNumber: 'Q',
address: 'D20',
data: 'Array|DATA',
topics: ['D'],
},
}
const methods = {
hexaNumberMethods: {
// these are the methods which have output in the form of hexa decimal numbers
eth_blockNumber: ['eth_blockNumber', params.param, 'Q'],
eth_gasPrice: ['eth_gasPrice', params.param, 'Q'],
eth_newBlockFilter: ['eth_newBlockFilter', params.param, 'Q'],
eth_newPendingTransactionFilter: [
'eth_newPendingTransactionFilter',
params.param,
'Q',
],
eth_getUncleCountByBlockHash: [
'eth_getUncleCountByBlockHash',
[params.blockHashParams],
'Q',
1,
],
eth_getBlockTransactionCountByHash: [
'eth_getBlockTransactionCountByHash',
[params.blockHashParams],
'Q',
1,
],
eth_getTransactionCount: [
'eth_getTransactionCount',
[params.addressParams, params.blockParameterParams],
'Q',
1,
2,
],
eth_getBalance: ['eth_getBalance', [params.addressParams, 'latest'], 'Q', 1, 2],
eth_estimateGas: ['eth_estimateGas', [params.estimateTransaction], 'Q', 1],
eth_getUncleCountByBlockNumber: [
'eth_getUncleCountByBlockNumber',
[params.blockParameterParams],
'Q',
1,
],
eth_getBlockTransactionCountByNumber: [
'eth_getBlockTransactionCountByNumber',
['latest'],
'Q',
1,
],
eth_protocolVersion: ['eth_protocolVersion', params.param, 'S'],
eth_getCode: ['eth_getCode', params.getCodeParams, 'D', 1, 2],
},
booleanMethods: {
// these are the methods which have output in the form of boolean
eth_uninstallFilter: ['eth_uninstallFilter', params.filterParams, 'B', 1],
eth_mining: ['eth_mining', params.param, 'B'],
eth_syncing: ['eth_syncing', params.param, 'B|EthSyncing'],
},
transactionMethods: {
// these are the methods which have output in the form of transaction object
eth_getTransactionByHash: [
'eth_getTransactionByHash',
params.transactionHashParams,
params.transaction,
1,
],
eth_getTransactionByBlockHashAndIndex: [
'eth_getTransactionByBlockHashAndIndex',
params.blockHashAndIndexParams,
params.transaction,
2,
],
eth_getTransactionByBlockNumberAndIndex: [
'eth_getTransactionByBlockNumberAndIndex',
[params.blockParameterParams, '0x0'],
params.transaction,
2,
],
},
blockMethods: {
// these are the methods which have output in the form of a block
eth_getUncleByBlockNumberAndIndex: [
'eth_getUncleByBlockNumberAndIndex',
params.uncleByBlockNumberAndIndexParams,
params.block,
2,
],
eth_getBlockByHash: [
'eth_getBlockByHash',
[params.params, false],
params.block,
2,
],
eth_getBlockByNumber: [
'eth_getBlockByNumber',
[params.blockParameterParams, false],
params.block,
2,
],
},
methods: {
// these are the methods which have output in the form of bytes data
eth_call: ['eth_call', [params.estimateTransaction, 'latest'], 'D', 1, 2],
eth_getStorageAt: ['eth_getStorageAt', params.getStorageAtParams, 'D', 2, 2],
eth_getTransactionReceipt: [
'eth_getTransactionReceipt',
params.transactionHashParams,
params.receipt,
1,
],
},
}

View File

@ -1,34 +0,0 @@
/* eslint no-undef: 0 */
const json = methods
web3.currentProvider.enable().then(() => {
Object.keys(json).forEach((methodGroupKey) => {
console.log(methodGroupKey)
const methodGroup = json[methodGroupKey]
console.log(methodGroup)
Object.keys(methodGroup).forEach((methodKey) => {
const methodButton = document.getElementById(methodKey)
methodButton.addEventListener('click', () => {
window.ethereum.sendAsync({
method: methodKey,
params: methodGroup[methodKey][1],
}, (err, result) => {
if (err) {
console.log(err)
console.log(methodKey)
} else {
document.getElementById('results').innerHTML = JSON.stringify(result)
}
})
})
})
})
})

View File

@ -1,52 +1,47 @@
import React, { Component } from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import copyToClipboard from 'copy-to-clipboard'
import { exportAsFile } from '../../../helpers/utils/util'
import Copy from '../icon/copy-icon.component'
import { useI18nContext } from '../../../hooks/useI18nContext'
import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard'
class ExportTextContainer extends Component {
render () {
const { text = '' } = this.props
const { t } = this.context
function ExportTextContainer ({ text = '' }) {
const t = useI18nContext()
const [copied, handleCopy] = useCopyToClipboard()
return (
<div className="export-text-container">
<div className="export-text-container__text-container">
<div className="export-text-container__text notranslate">
{text}
return (
<div className="export-text-container">
<div className="export-text-container__text-container">
<div className="export-text-container__text notranslate">{text}</div>
</div>
<div className="export-text-container__buttons-container">
<div
className="export-text-container__button export-text-container__button--copy"
onClick={() => {
handleCopy(text)
}}
>
<Copy size={17} color="#3098DC" />
<div className="export-text-container__button-text">
{copied ? t('copiedExclamation') : t('copyToClipboard')}
</div>
</div>
<div className="export-text-container__buttons-container">
<div
className="export-text-container__button export-text-container__button--copy"
onClick={() => copyToClipboard(text)}
>
<Copy size={17} color="#3098DC" />
<div className="export-text-container__button-text">
{t('copyToClipboard')}
</div>
</div>
<div
className="export-text-container__button"
onClick={() => exportAsFile('', text)}
>
<img src="images/download.svg" alt="" />
<div className="export-text-container__button-text">
{t('saveAsCsvFile')}
</div>
<div
className="export-text-container__button"
onClick={() => exportAsFile('', text)}
>
<img src="images/download.svg" alt="" />
<div className="export-text-container__button-text">
{t('saveAsCsvFile')}
</div>
</div>
</div>
)
}
</div>
)
}
ExportTextContainer.propTypes = {
text: PropTypes.string,
}
ExportTextContainer.contextTypes = {
t: PropTypes.func,
}
export default ExportTextContainer
export default React.memo(ExportTextContainer)

View File

@ -41,7 +41,7 @@ export function MetaMetricsProvider ({ children }) {
const numberOfAccounts = useSelector(getNumberOfAccounts)
const history = useHistory()
const [state, setState] = useState(() => ({
currentPath: window.location.href,
currentPath: (new URL(window.location.href)).pathname,
previousPath: '',
}))
@ -49,7 +49,7 @@ export function MetaMetricsProvider ({ children }) {
useEffect(() => {
const unlisten = history.listen(() => setState((prevState) => ({
currentPath: window.location.href,
currentPath: (new URL(window.location.href)).pathname,
previousPath: prevState.currentPath,
})))
// remove this listener if the component is no longer mounted
@ -59,8 +59,8 @@ export function MetaMetricsProvider ({ children }) {
const metricsEvent = useCallback((config = {}, overrides = {}) => {
const { eventOpts = {} } = config
const { name = '' } = eventOpts
const { pathname: overRidePathName = '' } = overrides
const isSendFlow = Boolean(name.match(/^send|^confirm/) || overRidePathName.match(/send|confirm/))
const { currentPath: overrideCurrentPath = '' } = overrides
const isSendFlow = Boolean(name.match(/^send|^confirm/) || overrideCurrentPath.match(/send|confirm/))
if (participateInMetaMetrics || config.isOptIn) {
return sendMetaMetricsEvent({

View File

@ -75,7 +75,6 @@
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
height: 16px;
}
.dropdown-menu-item .fa.delete {
@ -175,7 +174,7 @@
}
.network-dropdown-content {
height: 36px;
min-height: 36px;
width: 265px;
color: $dusty-gray;
font-family: Roboto;

View File

@ -6,6 +6,7 @@ import * as Sentry from '@sentry/browser'
const warned = {}
const missingMessageErrors = {}
const missingSubstitutionErrors = {}
/**
* Returns a localized message for the given key
@ -55,7 +56,11 @@ export const getMessage = (localeCode, localeMessages, key, substitutions) => {
return part
}
const substituteIndex = Number(subMatch[1]) - 1
if (substitutions[substituteIndex] == null) {
if (substitutions[substituteIndex] == null && !missingSubstitutionErrors[localeCode]?.[key]) {
if (!missingSubstitutionErrors[localeCode]) {
missingSubstitutionErrors[localeCode] = {}
}
missingSubstitutionErrors[localeCode][key] = true
const error = new Error(`Insufficient number of substitutions for message: '${phrase}'`)
log.error(error)
Sentry.captureException(error)

View File

@ -2,10 +2,15 @@
import ethUtil from 'ethereumjs-util'
const inDevelopment = process.env.NODE_ENV === 'development'
const inDevelopment = process.env.METAMASK_DEBUG || process.env.IN_TEST
let projectId = process.env.METAMETRICS_PROJECT_ID
if (!projectId) {
projectId = inDevelopment ? 1 : 2
}
const METAMETRICS_BASE_URL = 'https://chromeextensionmm.innocraft.cloud/piwik.php'
const METAMETRICS_REQUIRED_PARAMS = `?idsite=${inDevelopment ? 1 : 2}&rec=1&apiv=1`
const METAMETRICS_REQUIRED_PARAMS = `?idsite=${projectId}&rec=1&apiv=1`
const METAMETRICS_BASE_FULL = METAMETRICS_BASE_URL + METAMETRICS_REQUIRED_PARAMS
const METAMETRICS_TRACKING_URL = inDevelopment
@ -18,7 +23,7 @@ const METAMETRICS_CUSTOM_GAS_LIMIT_CHANGE = 'gasLimitChange'
const METAMETRICS_CUSTOM_GAS_PRICE_CHANGE = 'gasPriceChange'
const METAMETRICS_CUSTOM_FUNCTION_TYPE = 'functionType'
const METAMETRICS_CUSTOM_RECIPIENT_KNOWN = 'recipientKnown'
const METAMETRICS_CUSTOM_CONFIRM_SCREEN_ORIGIN = 'origin'
const METAMETRICS_REQUEST_ORIGIN = 'origin'
const METAMETRICS_CUSTOM_FROM_NETWORK = 'fromNetwork'
const METAMETRICS_CUSTOM_TO_NETWORK = 'toNetwork'
const METAMETRICS_CUSTOM_ERROR_FIELD = 'errorField'
@ -31,7 +36,7 @@ const METAMETRICS_CUSTOM_ASSET_SELECTED = 'assetSelected'
const customVariableNameIdMap = {
[METAMETRICS_CUSTOM_FUNCTION_TYPE]: 1,
[METAMETRICS_CUSTOM_RECIPIENT_KNOWN]: 2,
[METAMETRICS_CUSTOM_CONFIRM_SCREEN_ORIGIN]: 3,
[METAMETRICS_REQUEST_ORIGIN]: 3,
[METAMETRICS_CUSTOM_GAS_LIMIT_CHANGE]: 4,
[METAMETRICS_CUSTOM_GAS_PRICE_CHANGE]: 5,
@ -69,7 +74,7 @@ const customDimensionsNameIdMap = {
function composeUrlRefParamAddition (previousPath, confirmTransactionOrigin) {
const externalOrigin = confirmTransactionOrigin && confirmTransactionOrigin !== 'metamask'
return `&urlref=${externalOrigin ? 'EXTERNAL' : encodeURIComponent(previousPath.replace(/chrome-extension:\/\/\w+/, METAMETRICS_TRACKING_URL))}`
return `&urlref=${externalOrigin ? 'EXTERNAL' : encodeURIComponent(`${METAMETRICS_TRACKING_URL}${previousPath}`)}`
}
// composes query params of the form &dimension[0-999]=[value]
@ -110,11 +115,10 @@ function composeParamAddition (paramValue, paramName) {
* @property {string} config.accountType The account type being used at the time of the event: 'hardware', 'imported' or 'default'
* @property {number} config.numberOfTokens The number of tokens that the user has added at the time of the event
* @property {number} config.numberOfAccounts The number of accounts the user has added at the time of the event
* @property {string} config.previousPath The location path the user was on prior to the path they are on at the time of the event
* @property {string} config.currentPath The location path the user is on at the time of the event
* @property {string} config.previousPath The pathname of the URL the user was on prior to the URL they are on at the time of the event
* @property {string} config.currentPath The pathname of the URL the user is on at the time of the event
* @property {string} config.metaMetricsId A random id assigned to a user at the time of opting in to metametrics. A hexadecimal number
* @property {string} config.confirmTransactionOrigin The origin on a transaction
* @property {string} config.url The url to track an event at. Overrides `currentPath`
* @property {boolean} config.excludeMetaMetricsId Whether or not the tracked event data should be associated with a metametrics id
* @property {boolean} config.isNewVisit Whether or not the event should be tracked as a new visit/user sessions
* @returns {string} - Returns a url to be passed to fetch to make the appropriate request to matomo.
@ -136,7 +140,6 @@ function composeUrl (config) {
currentPath,
metaMetricsId,
confirmTransactionOrigin,
url: configUrl,
excludeMetaMetricsId,
isNewVisit,
} = config
@ -162,10 +165,10 @@ function composeUrl (config) {
numberOfTokens: (customVariables && customVariables.numberOfTokens) || numberOfTokens,
numberOfAccounts: (customVariables && customVariables.numberOfAccounts) || numberOfAccounts,
}) : ''
const url = configUrl || currentPath ? `&url=${encodeURIComponent(currentPath.replace(/chrome-extension:\/\/\w+/, METAMETRICS_TRACKING_URL))}` : ''
const url = currentPath ? `&url=${encodeURIComponent(`${METAMETRICS_TRACKING_URL}${currentPath}`)}` : ''
const _id = metaMetricsId && !excludeMetaMetricsId ? `&_id=${metaMetricsId.slice(2, 18)}` : ''
const rand = `&rand=${String(Math.random()).slice(2)}`
const pv_id = ((url || currentPath) && `&pv_id=${ethUtil.bufferToHex(ethUtil.sha3(url || currentPath.match(/chrome-extension:\/\/\w+\/(.+)/)[0])).slice(2, 8)}`) || ''
const pv_id = currentPath ? `&pv_id=${ethUtil.bufferToHex(ethUtil.sha3(currentPath)).slice(2, 8)}` : ''
const uid = metaMetricsId && !excludeMetaMetricsId
? `&uid=${metaMetricsId.slice(2, 18)}`
: excludeMetaMetricsId

View File

@ -0,0 +1,28 @@
import { useState, useCallback } from 'react'
import copyToClipboard from 'copy-to-clipboard'
import { useTimeout } from './useTimeout'
/**
* useCopyToClipboard
*
* @param {number} [delay=3000] - delay in ms
*
* @return {[boolean, Function]}
*/
const DEFAULT_DELAY = 3000
export function useCopyToClipboard (delay = DEFAULT_DELAY) {
const [copied, setCopied] = useState(false)
const startTimeout = useTimeout(() => setCopied(false), delay, false)
const handleCopy = useCallback(
(text) => {
setCopied(true)
startTimeout()
copyToClipboard(text)
},
[startTimeout],
)
return [copied, handleCopy]
}

View File

@ -0,0 +1,46 @@
import { useState, useEffect, useRef, useCallback } from 'react'
/**
* useTimeout
*
* @param {Function} cb - callback function inside setTimeout
* @param {number} delay - delay in ms
* @param {boolean} [immediate] - determines whether the timeout is invoked immediately
*
* @return {Function}
*/
export function useTimeout (cb, delay, immediate = true) {
const saveCb = useRef()
const [timeoutId, setTimeoutId] = useState(null)
useEffect(() => {
saveCb.current = cb
}, [cb])
useEffect(() => {
if (timeoutId !== 'start') {
return
}
const id = setTimeout(() => {
saveCb.current()
}, delay)
setTimeoutId(id)
return () => {
clearTimeout(timeoutId)
}
}, [delay, timeoutId])
const startTimeout = useCallback(() => {
clearTimeout(timeoutId)
setTimeoutId('start')
}, [timeoutId])
if (immediate) {
startTimeout()
}
return startTimeout
}

View File

@ -19,6 +19,7 @@ export default class ImportWithSeedPhrase extends PureComponent {
onSubmit: PropTypes.func.isRequired,
setSeedPhraseBackedUp: PropTypes.func,
initializeThreeBox: PropTypes.func,
completeOnboarding: PropTypes.func,
}
state = {
@ -119,7 +120,7 @@ export default class ImportWithSeedPhrase extends PureComponent {
}
const { password, seedPhrase } = this.state
const { history, onSubmit, setSeedPhraseBackedUp, initializeThreeBox } = this.props
const { history, onSubmit, setSeedPhraseBackedUp, initializeThreeBox, completeOnboarding } = this.props
try {
await onSubmit(password, this.parseSeedPhrase(seedPhrase))
@ -131,7 +132,8 @@ export default class ImportWithSeedPhrase extends PureComponent {
},
})
setSeedPhraseBackedUp(true).then(() => {
setSeedPhraseBackedUp(true).then(async () => {
await completeOnboarding()
initializeThreeBox()
history.push(INITIALIZE_END_OF_FLOW_ROUTE)
})

View File

@ -3,12 +3,14 @@ import ImportWithSeedPhrase from './import-with-seed-phrase.component'
import {
setSeedPhraseBackedUp,
initializeThreeBox,
setCompletedOnboarding,
} from '../../../../store/actions'
const mapDispatchToProps = (dispatch) => {
return {
setSeedPhraseBackedUp: (seedPhraseBackupState) => dispatch(setSeedPhraseBackedUp(seedPhraseBackupState)),
initializeThreeBox: () => dispatch(initializeThreeBox()),
completeOnboarding: () => dispatch(setCompletedOnboarding()),
}
}

View File

@ -14,7 +14,6 @@ export default class EndOfFlowScreen extends PureComponent {
static propTypes = {
history: PropTypes.object,
completeOnboarding: PropTypes.func,
completionMetaMetricsName: PropTypes.string,
onboardingInitiator: PropTypes.exact({
location: PropTypes.string,
@ -23,9 +22,8 @@ export default class EndOfFlowScreen extends PureComponent {
}
onComplete = async () => {
const { history, completeOnboarding, completionMetaMetricsName, onboardingInitiator } = this.props
const { history, completionMetaMetricsName, onboardingInitiator } = this.props
await completeOnboarding()
this.context.metricsEvent({
eventOpts: {
category: 'Onboarding',

View File

@ -1,6 +1,5 @@
import { connect } from 'react-redux'
import EndOfFlow from './end-of-flow.component'
import { setCompletedOnboarding } from '../../../store/actions'
import { getOnboardingInitiator } from '../../../selectors'
const firstTimeFlowTypeNameMap = {
@ -17,10 +16,4 @@ const mapStateToProps = (state) => {
}
}
const mapDispatchToProps = (dispatch) => {
return {
completeOnboarding: () => dispatch(setCompletedOnboarding()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(EndOfFlow)
export default connect(mapStateToProps)(EndOfFlow)

View File

@ -12,7 +12,6 @@ describe('End of Flow Screen', function () {
history: {
push: sinon.spy(),
},
completeOnboarding: sinon.spy(),
}
beforeEach(function () {
@ -30,7 +29,6 @@ describe('End of Flow Screen', function () {
endOfFlowButton.simulate('click')
setImmediate(() => {
assert(props.completeOnboarding.calledOnce)
assert(props.history.push.calledOnceWithExactly(DEFAULT_ROUTE))
done()
})

View File

@ -26,6 +26,7 @@ export default class ConfirmSeedPhrase extends PureComponent {
seedPhrase: PropTypes.string,
initializeThreeBox: PropTypes.func,
setSeedPhraseBackedUp: PropTypes.func,
completeOnboarding: PropTypes.func,
}
state = {
@ -66,6 +67,10 @@ export default class ConfirmSeedPhrase extends PureComponent {
exportAsFile('', this.props.seedPhrase, 'text/plain')
}
setOnboardingCompleted = async () => {
await this.props.completeOnboarding()
}
handleSubmit = async () => {
const {
history,
@ -86,8 +91,9 @@ export default class ConfirmSeedPhrase extends PureComponent {
},
})
setSeedPhraseBackedUp(true).then(() => {
setSeedPhraseBackedUp(true).then(async () => {
initializeThreeBox()
this.setOnboardingCompleted()
history.push(INITIALIZE_END_OF_FLOW_ROUTE)
})
} catch (error) {

View File

@ -3,12 +3,14 @@ import ConfirmSeedPhrase from './confirm-seed-phrase.component'
import {
setSeedPhraseBackedUp,
initializeThreeBox,
setCompletedOnboarding,
} from '../../../../store/actions'
const mapDispatchToProps = (dispatch) => {
return {
setSeedPhraseBackedUp: (seedPhraseBackupState) => dispatch(setSeedPhraseBackedUp(seedPhraseBackupState)),
initializeThreeBox: () => dispatch(initializeThreeBox()),
completeOnboarding: () => dispatch(setCompletedOnboarding()),
}
}

View File

@ -142,6 +142,7 @@ describe('ConfirmSeedPhrase Component', function () {
history: { push: pushSpy },
setSeedPhraseBackedUp: () => Promise.resolve(),
initializeThreeBox: initialize3BoxSpy,
completeOnboarding: sinon.spy(),
},
{
metricsEvent: metricsEventSpy,

View File

@ -98,11 +98,8 @@ export default class Routes extends Component {
this.props.history.listen((locationObj, action) => {
if (action === 'PUSH') {
pageChanged(locationObj.pathname)
const url = `&url=${encodeURIComponent('http://www.metamask.io/metametrics' + locationObj.pathname)}`
this.context.metricsEvent({}, {
currentPath: '',
pathname: locationObj.pathname,
url,
currentPath: locationObj.pathname,
pageOpts: {
hideDimensions: true,
},

View File

@ -106,7 +106,8 @@
height: 20px;
padding: 0;
background: none;
padding-left: 10px;
padding-left: 0;
margin-left: 10px;
}
}

View File

@ -1,85 +1,102 @@
import React, { PureComponent } from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import { Redirect } from 'react-router-dom'
import Identicon from '../../../../components/ui/identicon'
import Copy from '../../../../components/ui/icon/copy-icon.component'
import Button from '../../../../components/ui/button/button.component'
import copyToClipboard from 'copy-to-clipboard'
import Tooltip from '../../../../components/ui/tooltip-v2'
import { useI18nContext } from '../../../../hooks/useI18nContext'
import { useCopyToClipboard } from '../../../../hooks/useCopyToClipboard'
function quadSplit (address) {
return '0x ' + address.slice(2).match(/.{1,4}/g).join(' ')
return (
'0x ' +
address
.slice(2)
.match(/.{1,4}/g)
.join(' ')
)
}
export default class ViewContact extends PureComponent {
function ViewContact ({
history,
name,
address,
checkSummedAddress,
memo,
editRoute,
listRoute,
}) {
const t = useI18nContext()
const [copied, handleCopy] = useCopyToClipboard()
static contextTypes = {
t: PropTypes.func,
if (!address) {
return <Redirect to={{ pathname: listRoute }} />
}
static propTypes = {
name: PropTypes.string,
address: PropTypes.string,
history: PropTypes.object,
checkSummedAddress: PropTypes.string,
memo: PropTypes.string,
editRoute: PropTypes.string,
listRoute: PropTypes.string.isRequired,
}
render () {
const { t } = this.context
const { history, name, address, checkSummedAddress, memo, editRoute, listRoute } = this.props
if (!address) {
return <Redirect to={{ pathname: listRoute }} />
}
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<div className="settings-page__header address-book__header">
<Identicon address={address} diameter={60} />
<div className="address-book__header__name">{ name }</div>
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<div className="settings-page__header address-book__header">
<Identicon address={address} diameter={60} />
<div className="address-book__header__name">{name}</div>
</div>
<div className="address-book__view-contact__group">
<Button
type="secondary"
onClick={() => {
history.push(`${editRoute}/${address}`)
}}
>
{t('edit')}
</Button>
</div>
<div className="address-book__view-contact__group">
<div className="address-book__view-contact__group__label">
{t('ethereumPublicAddress')}
</div>
<div className="address-book__view-contact__group">
<Button
type="secondary"
onClick={() => {
history.push(`${editRoute}/${address}`)
}}
>
{t('edit')}
</Button>
</div>
<div className="address-book__view-contact__group">
<div className="address-book__view-contact__group__label">
{ t('ethereumPublicAddress') }
<div className="address-book__view-contact__group__value">
<div className="address-book__view-contact__group__static-address">
{quadSplit(checkSummedAddress)}
</div>
<div className="address-book__view-contact__group__value">
<div
className="address-book__view-contact__group__static-address"
>
{ quadSplit(checkSummedAddress) }
</div>
<Tooltip
position="bottom"
title={copied ? t('copiedExclamation') : t('copyToClipboard')}
>
<button
className="address-book__view-contact__group__static-address--copy-icon"
onClick={() => copyToClipboard(checkSummedAddress)}
onClick={() => {
handleCopy(checkSummedAddress)
}}
>
<Copy size={20} color="#3098DC" />
</button>
</div>
</Tooltip>
</div>
<div className="address-book__view-contact__group">
<div className="address-book__view-contact__group__label--capitalized">
{ t('memo') }
</div>
<div className="address-book__view-contact__group__static-address">
{ memo }
</div>
</div>
<div className="address-book__view-contact__group">
<div className="address-book__view-contact__group__label--capitalized">
{t('memo')}
</div>
<div className="address-book__view-contact__group__static-address">
{memo}
</div>
</div>
</div>
)
}
</div>
)
}
ViewContact.propTypes = {
name: PropTypes.string,
address: PropTypes.string,
history: PropTypes.object,
checkSummedAddress: PropTypes.string,
memo: PropTypes.string,
editRoute: PropTypes.string,
listRoute: PropTypes.string.isRequired,
}
export default React.memo(ViewContact)

View File

@ -76,12 +76,12 @@ export default class InfoTab extends PureComponent {
</div>
<div className="info-tab__link-item">
<a
href="mailto:help@metamask.io?subject=Feedback"
href="https://metamask.zendesk.com/hc/en-us/requests/new"
target="_blank"
rel="noopener noreferrer"
>
<span className="info-tab__link-text">
{ t('emailUs') }
{ t('contactUs') }
</span>
</a>
</div>

View File

@ -168,7 +168,7 @@ export function createNewVault (password) {
export function verifyPassword (password) {
return new Promise((resolve, reject) => {
background.submitPassword(password, (error) => {
background.verifyPassword(password, (error) => {
if (error) {
return reject(error)
}
@ -193,7 +193,7 @@ export function verifySeedPhrase () {
export function requestRevealSeedWords (password) {
return async (dispatch) => {
dispatch(showLoadingIndication())
log.debug(`background.submitPassword`)
log.debug(`background.verifyPassword`)
try {
await verifyPassword(password)

799
yarn.lock

File diff suppressed because it is too large Load Diff