mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-22 09:23:21 +01:00
Connect distinct accounts per site (#7004)
* add PermissionsController remove provider approval controller integrate rpc-cap create PermissionsController move provider approval functionality to permissions controller add permissions approval ui, settings page add permissions activity and history move some functionality to metamask-inpage-provider rename siteMetadata -> domainMetadata add accountsChange notification to inpage provider move functionality to inpage provider update inpage provider Remove 'Connections' settings page (#7369) add hooks for exposing accounts in settings rename unused messages in non-English locales Add external extension id to metadata (#7396) update inpage provider, rpc-cap add eth_requestAccounts handling to background prevent notifying connections if extension is locked update inpage provider Fix lint errors add migration review fixes transaction controller review updates removed unused messages * Login Per Site UI (#7368) * LoginPerSite original UI changes to keep * First commit * Get necessary connected tab info for redirect and icon display for permissioned sites * Fix up designs and add missing features * Some lint fixes * More lint fixes * Ensures the tx controller + tx-state-manager orders transactions in the order they are received * Code cleanup for LoginPerSite-ui * Update e2e tests to use new connection flow * Fix display of connect screen and app header after login when connect request present * Update metamask-responsive-ui.spec for new item in accounts dropdown * Fix approve container by replacing approvedOrigins with domainMetaData * Adds test/e2e/permissions.spec.js * Correctly handle cancellation of a permissions request * Redirect to home after disconnecting all sites / cancelling all permissions * Fix display of site icons in menu * Fix height of permissions page container * Remove unused locale messages * Set default values for openExternalTabs and tabIdOrigins in account-menu.container * More code cleanup for LoginPerSite-ui * Use extensions api to close tab in permissions-connect * Remove unnecessary change in domIsReady() in contentscript * Remove unnecessary private function markers and class methods (for background tab info) in metamask-controller. * Adds getOriginOfCurrentTab selector * Adds IconWithFallback component and substitutes for appropriate cases * Add and utilize font mixins * Remove unused method in disconnect-all.container.js * Simplify buttonSizeLarge code in page-container-footer.component.js * Add and utilize getAccountsWithLabels selector * Remove console.log in ui/app/store/actions.js * Change last connected time format to yyyy-M-d * Fix css associated with IconWithFallback change * Ensure tracked openNonMetamaskTabsIDs are correctly set to inactive on tab changes * Code cleanup for LoginPerSite-ui * Use reusable function for modifying openNonMetamaskTabsIDs in background.js * Enables automatic switching to connected account when connected domain is open * Prevent exploit of tabIdOriginMap in background.js * Remove unneeded code from contentscript.js * Simplify current tab origin and window opener logic using remotePort listener tabs.queryTabs * Design and styling fixes for LoginPerSite-ui * Fix permissionHistory and permission logging for eth_requestAccounts and eth_accounts * Front end changes to support display of lastConnected time in connected and permissions screens * Fix lint errors * Refactor structure of permissionsHistory * Fix default values and object modifications for domain and permissionsHistory related data * Fix connecting to new accounts from modal * Replace retweet.svg with connect-white.svg * Fix signature-request.spec * Update metamask-inpage-provider version * Fix permissions e2e tests * Remove unneeded delay from test/e2e/signature-request.spec.js * Add delay before attempting to retrieve network id in dapp in ethereum-on=.spec * Use requestAccountTabIds strategy for determining tab id that opened a given window * Improve default values for permissions requests * Add some message descriptions to app/_locales/en/messages.json * Code clean up in permission controller * Stopped deep cloning object in mapObjectValues * Bump metamask-inpage-provider version * Add missing description in app/_locales/en/messages.json * Return promises from queryTabs and switchToTab of extension.js * Remove unused getAllPermissions function * Use default props in icon-with-fallback.component.js * Stop passing to permissions controller * Delete no longer used clear-approved-origins modal code * Remove duplicate imports in ui/app/components/app/index.scss * Use URL instead of regex in getOriginFromUrl() * Add runtime error checking to platform, promise based extension.tab methods * Support permission requests from external extensions * Improve font size and colour of the domain origin on the permission confirmation screen * Add support for toggling permissions * Ensure getRenderablePermissionsDomains only returns domains with exposedAccount caveat permissions * Remove unused code from LoginPerSite-ui branch * Ensure modal closes on Enter press for new-account-modal.component.js * Lint fix * fixup! Login Per Site UI (#7368) * Some code cleanup for LoginPerSite * Adds UX for connecting to dapps via the connected sites screen (#7593) * Adds UX for connecting to dapps via the connected sites screen * Use openMetaMaskTabIds from background.js to determine if current active tab is MetaMask * Delete unused permissions controller methods * Fixes two small bugs in the LoginPerSite ui (#7595) * Restore `providerRequest` message translations (#7600) This message was removed, but it was replaced with a very similar message called `likeToConnect`. The only difference is that the new message has "MetaMask" in it. Preserving these messages without "MetaMask" is probably better than deleting them, so these messages have all been restored and renamed to `likeToConnect`. * Login per site no sitemetadata fix (#7610) * Support connected sites for which we have no site metadata. * Change property containing subtitle info often populated by origin to a more accurate of purpose name * Lint fix * Improve disconnection modal messages (#7612) * Improve disconnectAccountModalDescription and disconnectAllModalDescription messages * Update disconnectAccountModalDescription app/_locales/en/messages.json Co-Authored-By: Mark Stacey <markjstacey@gmail.com> * Improve disconnectAccount modal message clarity * Adds cancel button to the account selection screen of the permissions request flow (#7613) * Fix eth_accounts permission language & selectability (#7614) * fix eth_accounts language & selectability * fix MetaMask capitalization in all messages * Close sidebar when opening connected sites (#7611) The 'Connected Sites' button in the accounts details now closes the sidebar, if it is open. This was accomplished by pulling the click handler for that button up to the wallet view component, where another button already followed a similar pattern of closing the sidebar. It seemed confusing to me that one handler was in the `AccountsDetails` container component, and one was handed down from above, so I added PropTypes to the container component. I'm not sure that the WalletView component is the best place for this logic, but I've put it there for now to be consistent with the add token button. * Reject permissions request upon tab close (#7618) Permissions requests are now rejected when the page is closed. This only applies to the full-screen view, as that is the view permission requests should be handled in. The case where the user deals with the request through a different view is handled in #7617 * Handle tab update failure (#7619) `extension.tabs.update` can sometimes fail if the user interacts with the tabs directly around the same time. The redirect flow has been updated to ensure that the permissions tab is still closed in that case. The user is on their own to find the dapp tab again in that case. * Login per site tab popup fixes (#7617) * Handle redirect in response to state update in permissions-connect * Ensure origin is available to permissions-connect subcomponents during redirect * Hide app bar whenever on redirect route * Improvements to handling of redirects in permissions-connect * Ensure permission request id change handling only happens when page is not null * Lint fix * Decouple confirm transaction screen from the selected address (#7622) * Avoid race condtion that could prevent contextual account switching (#7623) There was a race condition in the logic responsible for switching the selected account based upon the active tab. It was asynchronously querying the active tab, then assuming it had been retrieved later. The active tab info itself was already in the redux store in another spot, one that is guaranteed to be set before the UI renders. The race condition was avoided by deleting the duplicate state, and using the other active tab state. * Only redirect back to dapp if current tab is active (#7621) The "redirect back to dapp" behaviour can be disruptive when the permissions connect tab is not active. The purpose of the redirect was to maintain context between the dapp and the permissions request, but if the user has already moved to another tab, that no longer applies. * Fix JSX style lint errors * Remove unused state
This commit is contained in:
parent
2e69f3233e
commit
f519fa1ed3
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "የግላዊነት ኩነት አሁን በንቡር ነቅቷል"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "ቻርት የሚገኘው በ Ethereum አውታረ መረቦች ላይ ብቻ ነው።"
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "የተፈቀዱ ድረ ገጾችን ለማጥራት እንደሚፈልጉ እርግጠኛ ነዎት?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "የግንኙነት ተግባቦት"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "የግላዊነት ውሂብን አጥራ"
|
||||
},
|
||||
"reject": {
|
||||
"message": "አይቀበሉ"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1ከመለያዎ ጋር ለመገናኘት ይፈልጋል"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "ይህ ድረ ገጽ የእርስዎን መለያ ወቅታዊ አድራሻ ለማየት እየጠየቀ ነው። ምንጊዜም ግንኙነት የሚያደርጉባቸውን ድረ ገጾች የሚያምኗቸው መሆኑን ያረጋግጡ።"
|
||||
},
|
||||
"about": {
|
||||
"message": "ስለ"
|
||||
},
|
||||
@ -248,9 +236,6 @@
|
||||
"connect": {
|
||||
"message": "ይገናኙ"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "የግንኙነት ጥያቄ"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "ከ $1ጋር መገናኘት"
|
||||
},
|
||||
@ -368,9 +353,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "ቀደም ሲል የተወሰነ Ether ካለዎት፣ በአዲሱ ቋትዎ Ether ለማግኘት ፈጣኑ መንገድ ቀጥተኛ ተቀማጭ ነው።"
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "አሰናብት"
|
||||
},
|
||||
"done": {
|
||||
"message": "ተጠናቅቋል"
|
||||
},
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "يتم تمكين وضع الخصوصية الآن بشكل افتراضي"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "الرسم البياني متاح فقط على شبكات إيثيريوم."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "هل أنت متأكد من أنك تريد مسح المواقع المعتمدة؟"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "التفاعل على العقد"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "مسح بيانات الخصوصية"
|
||||
},
|
||||
"reject": {
|
||||
"message": "رفض"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "يرغب $1 في الاتصال بحسابك"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "يطلب هذا الموقع حق الوصول لعرض عنوان حسابك الحالي. تأكد دائماً من ثقتك في المواقع التي تتفاعل معها."
|
||||
},
|
||||
"about": {
|
||||
"message": "حول"
|
||||
},
|
||||
@ -248,9 +236,6 @@
|
||||
"connect": {
|
||||
"message": "اتصال"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "طلب اتصال"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "جارِ الاتصال بـ $1"
|
||||
},
|
||||
@ -368,9 +353,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "إذا كان لديك بالفعل بعض الأثير، فإن أسرع طريقة للحصول على الأثير في محفظتك الجديدة عن طريق الإيداع المباشر."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "رفض"
|
||||
},
|
||||
"done": {
|
||||
"message": "تم"
|
||||
},
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "Режимът на поверителност вече е активиран по подразбиране"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Диаграмата е достъпна само в мрежи на Ethereum."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Сигурни ли сте, че искате да изчистите одобрените уебсайтове?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Взаимодействие с договор"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Изчистване на данните за поверителност"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Отхвърляне"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 би искал да се свърже с вашия акаунт"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Този сайт иска достъп за преглед на адреса на текущия ви акаунт. Винаги се уверявайте, че се доверявате на сайтовете, с които взаимодействате."
|
||||
},
|
||||
"about": {
|
||||
"message": "Информация"
|
||||
},
|
||||
@ -248,9 +236,6 @@
|
||||
"connect": {
|
||||
"message": "Свързване"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Свържете заявка"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Свързване с $1"
|
||||
},
|
||||
@ -368,9 +353,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Ако вече имате някакъв етер, най-бързият начин да получите етер в новия си портфейл е чрез директен депозит."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Отхвърляне"
|
||||
},
|
||||
"done": {
|
||||
"message": "Готово"
|
||||
},
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "গোপনীয়তার মোড এখন ডিফল্ট হিসাবে সক্রিয় করা আছে"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "শুধুমাত্র Ethereum নেটওয়ার্কগুলিতে চার্ট উপলভ্য। "
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "আপনি কি অনুমোদিত ওয়েবসাইটগুলি মুছে পরিস্কার করার বিষয়ে নিশ্চিত?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "কন্ট্র্যাক্ট বাক্যালাপ"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "গোপনীয়তার ডেটা মুছে পরিস্কার করুন"
|
||||
},
|
||||
"reject": {
|
||||
"message": "প্রত্যাখ্যান"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 আপনার অ্যাকাউন্টের সাথে সংযোগ করতে চায়"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "এই সাইটটি আপনার বর্তমান অ্যাকাউন্টের ঠিকানা দেখার অ্যাক্সেসের জন্য অনুরোধ জানাচ্ছে। সবসময় নিশ্চিত হয়ে নেবেন যে আপনি যে সাইটের সাথে যোগাযোগ করছেন সেটি বিশ্বাসযোগ্য কিনা।"
|
||||
},
|
||||
"about": {
|
||||
"message": "সম্পর্কে"
|
||||
},
|
||||
@ -248,9 +236,6 @@
|
||||
"connect": {
|
||||
"message": "সংযুক্ত করুন"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "সংযোগের অনুরোধ"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": " $1 এর সাথে সংযোগ করছে"
|
||||
},
|
||||
@ -368,9 +353,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "আপনার ইতিমধ্যে কিছু ইথার থেকে থাকলে আপনার নতুন ওয়ালেটে ইথার পাওয়ার দ্রুততম উপায় হল সরাসরি জমা করা।"
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "খারিজ"
|
||||
},
|
||||
"done": {
|
||||
"message": "সম্পন্ন "
|
||||
},
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "El mode de privacitat ara està activat per defecte"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Mostra només els disponibles a les xarxes Ethereum."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Estàs segur que vols eliminar totes les pàgines web aprovades?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Contractar Interacció"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Elimina les dades de privacitat"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Rebutja"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "a $1 li agradaria connectar-se al teu compte"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Aquesta pàgina està demanant accès a la teva adreça"
|
||||
},
|
||||
"about": {
|
||||
"message": "Informació"
|
||||
},
|
||||
@ -245,9 +233,6 @@
|
||||
"connect": {
|
||||
"message": "Connecta"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Sol·licitud de connexió"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Connectant a $1 "
|
||||
},
|
||||
@ -365,9 +350,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Si ja tens una mica d'Ether, la manera més ràpida de posar Ether al teu nou moneder és per dipòsit directe."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Omet"
|
||||
},
|
||||
"done": {
|
||||
"message": "Fet"
|
||||
},
|
||||
@ -687,7 +669,7 @@
|
||||
"message": "Conectant-te a Ethereum i la web descentralitzada."
|
||||
},
|
||||
"metamaskSeedWords": {
|
||||
"message": "Frase de recuperació de Metamask"
|
||||
"message": "Frase de recuperació de MetaMask"
|
||||
},
|
||||
"metamaskVersion": {
|
||||
"message": "Versió MetaMask"
|
||||
|
@ -1,16 +1,7 @@
|
||||
{
|
||||
"confirmClear": {
|
||||
"message": "Naozaj chcete vymazať schválené webové stránky?"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Jasné údaje o schválení"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Odmítnout"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Níže uvedená doména se pokouší požádat o přístup k API Ethereum, aby mohla komunikovat s blokádou Ethereum. Před schválením přístupu Ethereum vždy zkontrolujte, zda jste na správném místě."
|
||||
},
|
||||
"account": {
|
||||
"message": "Účet"
|
||||
},
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "Privatlivstilstand er nu som udgangspunkt aktiveret"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Skema kun tilgængeligt på Ethereum-netværk."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Er du sikker på, at du vil rydde godkendte hjemmesider?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Kontraktinteraktion"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Ryd fortrolighedsdata"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Afvis"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 ønsker at forbinde til din konto"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Denne side anmoder om at se din nuværende kontoadresse. Sørg altid for, at du stoler på de sider du interagerer med."
|
||||
},
|
||||
"about": {
|
||||
"message": "Om"
|
||||
},
|
||||
@ -248,9 +236,6 @@
|
||||
"connect": {
|
||||
"message": "Få forbindelse"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Tilslutningsanmodning"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Forbinder til $1"
|
||||
},
|
||||
@ -368,9 +353,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Hvis du allerede har Ether, er den hurtigste måde at få Ether i din nye tegnebog ved direkte indbetaling."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Luk"
|
||||
},
|
||||
"done": {
|
||||
"message": "Færdig"
|
||||
},
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "Der Datenschutzmodus ist jetzt standardmäßig aktiviert"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Die Grafik ist nur in Ethereum-Netzwerken verfügbar."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Möchten Sie die genehmigten Websites wirklich löschen?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Vertragsinteraktion"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Genehmigungsdaten löschen"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Ablehnen"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 möchte sich mit deinem Account verbinden"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Diese Website fordert Zugriff auf Ihre aktuelle Kontoadresse. Stellen Sie immer sicher, dass Sie den Websites vertrauen, mit denen Sie interagieren."
|
||||
},
|
||||
"about": {
|
||||
"message": "Über"
|
||||
},
|
||||
@ -236,9 +224,6 @@
|
||||
"connect": {
|
||||
"message": "Verbinden"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Verbindungsanfrage"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Verbindung mit $1 wird hergestellt"
|
||||
},
|
||||
@ -353,9 +338,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Wenn du bereits Ether besitzt, ist die sofortige Einzahlung die schnellste Methode Ether in deine neue Wallet zu bekommen."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Schließen"
|
||||
},
|
||||
"done": {
|
||||
"message": "Fertig"
|
||||
},
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "Η Λειτουργία Απορρήτου είναι πλέον ενεργοποιημένη από προεπιλογή"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Το διάγραμμα είναι διαθέσιμο μόνο σε δίκτυα Ethereum."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τους εγκεκριμένους ιστότοπους;"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Αλληλεπίδραση Σύμβασης"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Εκκαθάριση Δεδομένων Απορρήτου"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Απόρριψη"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "Αίτημα σύνδεσης στον λογαριασμό σας από $1"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Ο ιστότοπος ζητά πρόσβαση για προβολή της τρέχουσας διεύθυνσης του λογαριασμού σας. Να σιγουρεύεστε πάντα ότι εμπιστεύεστε τους ιστότοπους με τους οποίους αλληλεπιδράτε."
|
||||
},
|
||||
"about": {
|
||||
"message": "Σχετικά με"
|
||||
},
|
||||
@ -245,9 +233,6 @@
|
||||
"connect": {
|
||||
"message": "Σύνδεση"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Αίτημα Σύνδεσης"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Σύνδεση με $1"
|
||||
},
|
||||
@ -365,9 +350,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Αν έχετε ήδη κάποια Ether, ο πιο γρήγορος τρόπος για να πάρετε τα Ether στο νέο σας πορτοφόλι με άμεση κατάθεση."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Παράβλεψη"
|
||||
},
|
||||
"done": {
|
||||
"message": "Τέλος"
|
||||
},
|
||||
|
@ -14,47 +14,33 @@
|
||||
"showIncomingTransactionsDescription": {
|
||||
"message": "Select this to use Etherscan to show incoming transactions in the transactions list"
|
||||
},
|
||||
"cancelledConnectionWithMetaMask": {
|
||||
"message": "Cancelled Connection With MetaMask"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Chart only available on Ethereum networks."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Are you sure you want to clear approved websites?"
|
||||
"connectedSites": {
|
||||
"message": "Connected Sites"
|
||||
},
|
||||
"connections": {
|
||||
"message": "Connections"
|
||||
"connectingWithMetaMask": {
|
||||
"message": "Connecting With MetaMask..."
|
||||
},
|
||||
"connectionsSettingsDescription": {
|
||||
"message": "Sites allowed to read your accounts"
|
||||
"connectTo": {
|
||||
"message": "Connect to $1",
|
||||
"description": "$1 is the name/origin of a site/dapp that the user can connect to metamask"
|
||||
},
|
||||
"addSite": {
|
||||
"message": "Add Site"
|
||||
},
|
||||
"addSiteDescription": {
|
||||
"message": "Manually add a site to allow it access to your accounts, useful for older dapps"
|
||||
},
|
||||
"connected": {
|
||||
"message": "Connected"
|
||||
},
|
||||
"connectedDescription": {
|
||||
"message": "The list of sites allowed access to your addresses"
|
||||
},
|
||||
"privacyModeDefault": {
|
||||
"message": "Privacy Mode is now enabled by default"
|
||||
"chooseAnAcount": {
|
||||
"message": "Choose an account"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Contract Interaction"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Remove all sites"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Reject"
|
||||
},
|
||||
"providerRequest": {
|
||||
"message": "$1 would like to connect to your account"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "This site is requesting access to view your current account address. Always make sure you trust the sites you interact with."
|
||||
"redirectingBackToDapp": {
|
||||
"message": "Redirecting back to dapp..."
|
||||
},
|
||||
"about": {
|
||||
"message": "About"
|
||||
@ -305,9 +291,6 @@
|
||||
"connect": {
|
||||
"message": "Connect"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Connect Request"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Connecting to $1"
|
||||
},
|
||||
@ -431,14 +414,36 @@
|
||||
"details": {
|
||||
"message": "Details"
|
||||
},
|
||||
"disconnectAccount": {
|
||||
"message": "Disconnect account"
|
||||
},
|
||||
"disconnectAll": {
|
||||
"message": "Disconnect All"
|
||||
},
|
||||
"disconnectAllModalDescription": {
|
||||
"message": "Are you sure? You will be disconnected from all sites on all accounts."
|
||||
},
|
||||
"disconnectAccountModalDescription": {
|
||||
"message": "Are you sure? Your account (\"$1\") will be disconnected from this site."
|
||||
},
|
||||
"disconnectAccountQuestion": {
|
||||
"message": "Disconnect account?"
|
||||
},
|
||||
"disconnectFromThisAccount": {
|
||||
"message": "Disconnect from this account?"
|
||||
},
|
||||
"disconnectAllAccountsQuestion": {
|
||||
"message": "Disconnect all accounts?"
|
||||
},
|
||||
"directDepositEther": {
|
||||
"message": "Directly Deposit Ether"
|
||||
},
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "If you already have some Ether, the quickest way to get Ether in your new wallet by direct deposit."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Dismiss"
|
||||
"domainLastConnect": {
|
||||
"message": "Last Connected: $1",
|
||||
"description": "$1 is the date at which the user was last connected to a given domain"
|
||||
},
|
||||
"done": {
|
||||
"message": "Done"
|
||||
@ -500,6 +505,13 @@
|
||||
"endOfFlowMessage10": {
|
||||
"message": "All Done"
|
||||
},
|
||||
"extensionId": {
|
||||
"message": "Extension ID: $1",
|
||||
"description": "$1 is a string of random letters that are the id of another extension connecting to MetaMask"
|
||||
},
|
||||
"externalExtension": {
|
||||
"message": "External Extension"
|
||||
},
|
||||
"onboardingReturnNotice": {
|
||||
"message": "\"$1\" will close this tab and direct back to $2",
|
||||
"description": "Return the user to the site that initiated onboarding"
|
||||
@ -733,9 +745,15 @@
|
||||
"max": {
|
||||
"message": "Max"
|
||||
},
|
||||
"lastConnected": {
|
||||
"message": "Last Connected"
|
||||
},
|
||||
"learnMore": {
|
||||
"message": "Learn more"
|
||||
},
|
||||
"learnAboutRisks": {
|
||||
"message": "Learn about the risks here."
|
||||
},
|
||||
"ledgerAccountRestriction": {
|
||||
"message": "You need to make use your last account before you can add a new one."
|
||||
},
|
||||
@ -745,6 +763,10 @@
|
||||
"likeToAddTokens": {
|
||||
"message": "Would you like to add these tokens?"
|
||||
},
|
||||
"likeToConnect": {
|
||||
"message": "$1 would like to connect to your MetaMask account",
|
||||
"description": "$1 is the name/url of a site/dapp asking to connect to MetaMask"
|
||||
},
|
||||
"links": {
|
||||
"message": "Links"
|
||||
},
|
||||
@ -876,6 +898,9 @@
|
||||
"rpcUrl": {
|
||||
"message": "New RPC URL"
|
||||
},
|
||||
"onlyConnectTrust": {
|
||||
"message": "Only connect with sites you trust."
|
||||
},
|
||||
"optionalChainId": {
|
||||
"message": "ChainID (optional)"
|
||||
},
|
||||
@ -1078,6 +1103,9 @@
|
||||
"readyToConnect": {
|
||||
"message": "Ready to Connect?"
|
||||
},
|
||||
"revokeInPermissions": {
|
||||
"message": "* You can view and revoke permissions in MetaMask settings."
|
||||
},
|
||||
"rinkeby": {
|
||||
"message": "Rinkeby Test Network"
|
||||
},
|
||||
@ -1283,6 +1311,9 @@
|
||||
"storePhrase": {
|
||||
"message": "Store this phrase in a password manager like 1Password."
|
||||
},
|
||||
"submit": {
|
||||
"message": "Submit"
|
||||
},
|
||||
"submitted": {
|
||||
"message": "Submitted"
|
||||
},
|
||||
@ -1331,6 +1362,14 @@
|
||||
"testFaucet": {
|
||||
"message": "Test Faucet"
|
||||
},
|
||||
"thisWillAllow": {
|
||||
"message": "This will allow $1 to:",
|
||||
"description": "$1 is the name or domain of a site/dapp that is requesting permissions"
|
||||
},
|
||||
"thisWillAllowExternalExtension": {
|
||||
"message": "This will allow an external extension with id $1 to:",
|
||||
"description": "$1 is a string of random letters that are the id of another extension connecting to MetaMask"
|
||||
},
|
||||
"thisWillCreate": {
|
||||
"message": "This will create a new wallet and seed phrase"
|
||||
},
|
||||
@ -1343,6 +1382,10 @@
|
||||
"toWithColon": {
|
||||
"message": "To:"
|
||||
},
|
||||
"toConnectWith": {
|
||||
"message": "To connect with $1",
|
||||
"description": "$1 is the name or domain of a site/dapp that asking to connect with MetaMask"
|
||||
},
|
||||
"toETHviaShapeShift": {
|
||||
"message": "$1 to ETH via ShapeShift",
|
||||
"description": "system will fill in deposit type in start of message"
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "Modo Privado está activado ahora por defecto"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Tabla solo disponible en redes Ethereum."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "¿Seguro que quieres borrar los sitios web aprobados?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Interacción con contrato"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Borrar datos de aprobación"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Rechazar"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 quisiera conectar con tu cuenta"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "El dominio que se muestra a continuación intenta solicitar acceso a la API Ethereum para que pueda interactuar con la blockchain de Ethereum. Siempre verifique que esté en el sitio correcto antes de aprobar el acceso Ethereum."
|
||||
},
|
||||
"about": {
|
||||
"message": "Acerca"
|
||||
},
|
||||
@ -205,9 +193,6 @@
|
||||
"connect": {
|
||||
"message": "Conectar"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Petición para conectar"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Conectánodse a $1"
|
||||
},
|
||||
@ -325,9 +310,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Si posees Ether, la forma más rápida de transferirlo a tu nueva billetera es depositándolo directamente"
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Descartar"
|
||||
},
|
||||
"done": {
|
||||
"message": "Completo"
|
||||
},
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "El modo de privacidad está habilitado de manera predeterminada"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Chart está disponible únicamente en las redes de Ethereum."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "¿Estás seguro de que deseas borrar los sitios web aprobados?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Interacción contractual"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Borrar datos de privacidad"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Rechazar"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 desea conectarse a tu cuenta"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Este sitio está solicitando acceso para ver la dirección de tu cuenta corriente. Asegúrate siempre de que confías en los sitios con los que interactúas."
|
||||
},
|
||||
"about": {
|
||||
"message": "Acerca de"
|
||||
},
|
||||
@ -245,9 +233,6 @@
|
||||
"connect": {
|
||||
"message": "Conectar"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Solicitud de conexión"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Conexión con $1"
|
||||
},
|
||||
@ -365,9 +350,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Si ya tienes algunos Ethers, la forma más rápida de ingresarlos en tu nueva billetera es a través de un depósito directo."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Rechazar"
|
||||
},
|
||||
"done": {
|
||||
"message": "Listo"
|
||||
},
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "Privaatsusrežiim on nüüd vaikimisi lubatud"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Tabel on saadaval vaid Ethereumi võrkudes."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Kas soovite kindlasti kinnitatud veebisaidid kustutada?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Lepingu suhtlus"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Tühjenda privaatsusandmed"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Lükka tagasi"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 soovib teie kontoga ühenduse luua"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "See sait taotleb juurdepääsu teie praeguse konto aadressi vaatamiseks. Veenduge alati, et usaldate saite, millega suhtlete."
|
||||
},
|
||||
"about": {
|
||||
"message": "Teave"
|
||||
},
|
||||
@ -248,9 +236,6 @@
|
||||
"connect": {
|
||||
"message": "Ühendamine"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Ühenduse taotlus"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Ühenduse loomine $1"
|
||||
},
|
||||
@ -368,9 +353,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Kui teil on juba veidi eetrit, on kiirem viis eetri rahakotti saamiseks otsene sissemakse."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Loobu"
|
||||
},
|
||||
"done": {
|
||||
"message": "Valmis"
|
||||
},
|
||||
@ -420,7 +402,7 @@
|
||||
"message": "Kui teil on küsimusi või näete midagi kahtlast, kirjutage meile support@metamask.io."
|
||||
},
|
||||
"endOfFlowMessage8": {
|
||||
"message": "Metamask ei saa teie seemnefraasi taastada. Lisateave."
|
||||
"message": "MetaMask ei saa teie seemnefraasi taastada. Lisateave."
|
||||
},
|
||||
"endOfFlowMessage9": {
|
||||
"message": "Lisateave."
|
||||
@ -1180,7 +1162,7 @@
|
||||
"message": "Saate sünkroonida oma kontod ja teabe oma mobiiliseadmega. Avage MetaMaski mobiilirakendus, avage \"Settings\" (Seaded) ja puudutage valikut \"Sync from Browser Extension\" (Sünkroonimine lehitseja laiendusest)"
|
||||
},
|
||||
"syncWithMobileDescNewUsers": {
|
||||
"message": "Järgige Metamaski mobiilirakenduse esmakordsel avamisel telefonis esitatud samme."
|
||||
"message": "Järgige MetaMaski mobiilirakenduse esmakordsel avamisel telefonis esitatud samme."
|
||||
},
|
||||
"syncWithMobileScanThisCode": {
|
||||
"message": "Skanneerige see kood MetaMaski mobiilirakendusega"
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "وضعیت محرمیت حالا بصورت خودکار فعال است"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "تنها قابل دسترس را در شبکه های ایتریوم جدول بندی نمایید"
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "آیا مطمئن هستید تا وب سایت های تصدیق شده را حذف کنید؟"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "تعامل قرارداد"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "حذف اطلاعات حریم خصوصی"
|
||||
},
|
||||
"reject": {
|
||||
"message": "عدم پذیرش"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "1$1 میخواهید تا با حساب تان وصل شوید"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "این سایت در حال درخواست دسترسی است تا آدرس فعلی حساب تان را مشاهده نماید. همیشه متوجه باشید که بالای سایتی که با آن معامله میکنید، اعتماد دارید یا خیر."
|
||||
},
|
||||
"about": {
|
||||
"message": "درباره"
|
||||
},
|
||||
@ -248,9 +236,6 @@
|
||||
"connect": {
|
||||
"message": "اتصال"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "درخواست اتصال"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "در حال اتصال به 1$1"
|
||||
},
|
||||
@ -368,9 +353,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "در صورتیکه شما کدام ایتر داشته باشید، سریعترین روش برای گرفتن ایتر در کیف جدید تان توسط پرداخت مستقیم."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "لغو کردن"
|
||||
},
|
||||
"done": {
|
||||
"message": "تمام"
|
||||
},
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "Yksityisyystila on nyt oletusarvoisesti käytössä"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Kaavio saatavilla vain Ethereum-verkoissa."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Haluatko varmasti tyhjentää hyväksytyt verkkosivustot?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Sopimustoiminta"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Tyhjennä yksityisyystiedot"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Hylkää"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 haluaisi yhdistää tiliisi"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Tämä sivusto pyytää oikeuksia nähdä nykyisen tiliosoitteesi. Varmista aina, että luotat sivustoihin, joiden kanssa toimit."
|
||||
},
|
||||
"about": {
|
||||
"message": "Tietoja asetuksista"
|
||||
},
|
||||
@ -245,9 +233,6 @@
|
||||
"connect": {
|
||||
"message": "Muodosta yhteys"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Yhdistämispyyntö"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Yhdistetään summaan $1 "
|
||||
},
|
||||
@ -365,9 +350,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Jos sinulla on jo etheriä, nopein tapa hankkia etheriä uuteen lompakkoosi on suoratalletus."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Piilota"
|
||||
},
|
||||
"done": {
|
||||
"message": "Valmis"
|
||||
},
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "Naka-enable ang Privacy Mode bilang default"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Available lang ang chart sa mga Ethereum network."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Sigurado ka bang gusto mong i-clear ang mga inaprubahang website?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Paggamit sa Contract"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "I-clear ang Privacy Data"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Tanggihan"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "Gusto ng $1 na kumonekta sa iyong account"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Humihiling ng access ang site na ito na tingnan ang kasalukuyan mong account address. Palaging tiyaking pinagkakatiwalaan mo ang mga site na pinupuntahan mo."
|
||||
},
|
||||
"about": {
|
||||
"message": "Tungkol sa"
|
||||
},
|
||||
@ -338,9 +326,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Kung mayroon ka nang Ether, ang pinakamabilis na paraan para magkaroon ng Ether sa iyong bagong wallet ay sa pamamagitan ng direkang deposito."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Balewalain"
|
||||
},
|
||||
"done": {
|
||||
"message": "Tapos na"
|
||||
},
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "Le mode conversation privée est maintenant activé par défaut"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Tableau disponible uniquement sur les réseaux Ethereum."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Êtes-vous sûr de vouloir supprimer les sites Web approuvés?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Interaction avec un contrat"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Effacer les données d'approbation"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Rejeter"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 voudrait se connecter à votre compte"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Le domaine répertorié ci-dessous tente de demander l'accès à l'API Ethereum pour pouvoir interagir avec la chaîne de blocs Ethereum. Vérifiez toujours que vous êtes sur le bon site avant d'autoriser l'accès à Ethereum."
|
||||
},
|
||||
"about": {
|
||||
"message": "À propos"
|
||||
},
|
||||
@ -236,9 +224,6 @@
|
||||
"connect": {
|
||||
"message": "Connecter"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Demande de connexion"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Connexion à $1"
|
||||
},
|
||||
@ -356,9 +341,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Si vous avez déjà de l'Ether, le moyen le plus rapide d'obtenir des Ether dans votre nouveau portefeuille est par dépôt direct."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Ignorer"
|
||||
},
|
||||
"done": {
|
||||
"message": "Terminé"
|
||||
},
|
||||
|
@ -54,9 +54,6 @@
|
||||
"details": {
|
||||
"message": "વિગતો"
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "કાઢી નાખો"
|
||||
},
|
||||
"done": {
|
||||
"message": "થઈ ગયું"
|
||||
},
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "מצב פרטיות זמין עכשיו כברירת מחדל"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "טבלה זמינה רק ברשתות אתריום."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "הנך בטוח/ה כי ברצונך למחוק אתרים שאושרו?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "אינטראקציית חוזה"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "נקה נתוני פרטיות"
|
||||
},
|
||||
"reject": {
|
||||
"message": "דחה"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 מבקש להתחבר לחשבון שלך"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "אתר זה מבקש גישה לצפייה בכתובת החשבון הנוכחית שלך. יש לוודא תמיד כי הנך בוטח/ת באתרים עמם הנך מתקשר/ת."
|
||||
},
|
||||
"about": {
|
||||
"message": "מידע כללי"
|
||||
},
|
||||
@ -248,9 +236,6 @@
|
||||
"connect": {
|
||||
"message": "התחברות"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "חבר/י בקשה"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "מתחבר ל- $1 "
|
||||
},
|
||||
@ -368,9 +353,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "אם כבר יש ברשותך את'ר (Ether) , הדרך המהירה ביותר להכניס את'ר לארנק החדש שלך היא באמצעות הפקדה ישירה."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "סגור"
|
||||
},
|
||||
"done": {
|
||||
"message": "סיום"
|
||||
},
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "गोपनीय मोड अब डिफ़ॉल्ट रूप से सक्षम है"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "केवल ईथरअम नेटवर्क पर उपलब्ध चार्ट।"
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "क्या आप वाकई स्वीकृत वेबसाइटों को क्लियर करना चाहते हैं?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "कॉन्ट्रैक्ट की बातचीत"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "गोपनीयता डेटा रिक्त करें"
|
||||
},
|
||||
"reject": {
|
||||
"message": "अस्वीकार करें"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 आपके खाते से कनेक्ट होता चाहता हैं"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "यह साइट आपके वर्तमान खाते का पता देखने के लिए एक्सेस का अनुरोध कर रही है। हमेशा सुनिश्चित करें कि आप जिन साइटों पर जाते हैं वे विश्वसनीय हैं।"
|
||||
},
|
||||
"about": {
|
||||
"message": "इसके बारे में"
|
||||
},
|
||||
@ -248,9 +236,6 @@
|
||||
"connect": {
|
||||
"message": "कनेक्ट करें"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "संपर्क अनुरोध"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "$1 से कनेक्ट हो रहा है"
|
||||
},
|
||||
@ -368,9 +353,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "यदि आपके पास पहले से ही कुछ Ether हैं, तो अपने नए वॉलेट में Ether पाने का सबसे तेज़ तरीका सीधे जमा करना है।"
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "खारिज करें"
|
||||
},
|
||||
"done": {
|
||||
"message": "पूर्ण"
|
||||
},
|
||||
|
@ -1,19 +1,10 @@
|
||||
{
|
||||
"confirmClear": {
|
||||
"message": "क्या आप वाकई अनुमोदित वेबसाइटों को साफ़ करना चाहते हैं?"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "अनुमोदन डेटा साफ़ करें"
|
||||
},
|
||||
"approve": {
|
||||
"message": "मंजूर"
|
||||
},
|
||||
"reject": {
|
||||
"message": "अस्वीकार"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "नीचे सूचीबद्ध डोमेन वेब 3 एपीआई तक पहुंच का अनुरोध करने का प्रयास कर रहा है ताकि यह एथेरियम ब्लॉकचेन से बातचीत कर सके। वेब 3 एक्सेस को मंजूरी देने से पहले हमेशा सही जांच करें कि आप सही साइट पर हैं।"
|
||||
},
|
||||
"account": {
|
||||
"message": "खाता"
|
||||
},
|
||||
|
@ -1,19 +1,10 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "Način se Privatnost sada zadano omogućava"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Grafikon je dostupan samo na mrežama Ethereum."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Sigurno želite očistiti odobrena mrežna mjesta?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Ugovorna interakcija"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Očisti podatke o privatnosti"
|
||||
},
|
||||
"appName": {
|
||||
"message": "MetaMask",
|
||||
"description": "The name of the application"
|
||||
@ -21,12 +12,9 @@
|
||||
"reject": {
|
||||
"message": "Odbaci"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "Korisnik $1 želi se povezati na vaš račun"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Na ovom se mjestu zahtijeva pristup za pregledavanje vaše trenutačne adrese računa. Uvijek pazite da vjerujete mrežnim mjestima s kojima rukujete."
|
||||
},
|
||||
"about": {
|
||||
"message": "O opcijama"
|
||||
},
|
||||
@ -248,9 +236,6 @@
|
||||
"connect": {
|
||||
"message": "Povežite se"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Zahtjev za povezivanjem"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Povezivanje na $1"
|
||||
},
|
||||
@ -368,9 +353,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Ako imate nešto Ethera, najbrži je način prebacivanja Ethera u vaš novi novčanik izravan polog."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Odbaci"
|
||||
},
|
||||
"done": {
|
||||
"message": "Gotovo"
|
||||
},
|
||||
|
@ -1,13 +1,4 @@
|
||||
{
|
||||
"confirmClear": {
|
||||
"message": "Èske ou sèten ou vle klè sitwèb apwouve?"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Klè Done sou vi prive"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Domèn ki nan lis anba a ap mande pou jwenn aksè a blòkchou Ethereum ak pou wè kont ou ye kounye a. Toujou double tcheke ke ou sou sit ki kòrèk la anvan apwouve aksè."
|
||||
},
|
||||
"accessingYourCamera": {
|
||||
"message": "Aksè a Kamera"
|
||||
},
|
||||
|
@ -1,19 +1,10 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "Az adatvédelmi mód mostantól alapbeállításként engedélyezve van"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "A diagram csak Ethereum hálózatokon érhető el"
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Biztosan törölni szeretnéd a jóváhagyott weboldalakat?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Szerződéses interakció"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Adatvédelmi adatok törlése"
|
||||
},
|
||||
"appName": {
|
||||
"message": "MetaMask",
|
||||
"description": "The name of the application"
|
||||
@ -21,12 +12,9 @@
|
||||
"reject": {
|
||||
"message": "Elutasítás"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 szeretne kapcsolódni az ön fiókjához"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "A webhely hozzáférést kér az ön jelenlegi fiókcímének megtekintéséhez. Mindig győződjön meg arról, hogy megbízható webhellyel létesít kapcsolatot."
|
||||
},
|
||||
"about": {
|
||||
"message": "Névjegy"
|
||||
},
|
||||
@ -248,9 +236,6 @@
|
||||
"connect": {
|
||||
"message": "Csatlakozás"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Csatlakozási kérelem"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Kapcsolódás: $1"
|
||||
},
|
||||
@ -368,9 +353,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Amennyiben már rendelkezik némi Ether-rel, a közvetlen letéttel gyorsan elhelyezheti azt új pénztárcájában."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Elvetés"
|
||||
},
|
||||
"done": {
|
||||
"message": "Kész"
|
||||
},
|
||||
|
@ -1,19 +1,10 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "Modus Privasi kini aktif secara default"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Grafik hanya tersedia pada jaringan Ethereum."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Yakin ingin mengosongkan website yang disetujui?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Interaksi Kontrak"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Bersihkan Data Privasi"
|
||||
},
|
||||
"appName": {
|
||||
"message": "MetaMask",
|
||||
"description": "The name of the application"
|
||||
@ -21,12 +12,9 @@
|
||||
"reject": {
|
||||
"message": "Tolak"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 ingin menghubungkan ke akun Anda"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Situs ini meminta akses untuk melihat alamat akun Anda saat ini. Selalu pastikan bahwa Anda bisa mempercayai situs yang berinteraksi dengan Anda."
|
||||
},
|
||||
"about": {
|
||||
"message": "Tentang"
|
||||
},
|
||||
@ -248,9 +236,6 @@
|
||||
"connect": {
|
||||
"message": "Sambungkan"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Permintaan Sambungan"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Menghubungkan ke $1"
|
||||
},
|
||||
@ -362,9 +347,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Jika Anda sudah memiliki Ether, cara tercepat mendapatkan Ether di dompet baru lewat deposit langsung."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Tutup"
|
||||
},
|
||||
"done": {
|
||||
"message": "Selesai"
|
||||
},
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "La modalità privacy è ora abilitata per impostazione predefinita"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Grafico disponibile solo per le reti Ethereum."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Sei sicuro di voler cancellare i siti Web approvati?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Interazione Contratto"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Cancella i dati di approvazione"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Annulla"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 vorrebbe connettersi al tuo account"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Il dominio elencato di seguito sta tentando di richiedere l'accesso all'API Ethereum in modo che possa interagire con la blockchain di Ethereum. Controlla sempre di essere sul sito corretto prima di approvare l'accesso a Ethereum."
|
||||
},
|
||||
"about": {
|
||||
"message": "Informazioni"
|
||||
},
|
||||
@ -227,9 +215,6 @@
|
||||
"connect": {
|
||||
"message": "Connetti"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Richiesta Connessione"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Connessione in corso a $1"
|
||||
},
|
||||
@ -347,9 +332,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Se possiedi già degli Ether, questa è la via più veloce per aggiungere Ether al tuo portafoglio con un deposito diretto."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Ignora"
|
||||
},
|
||||
"done": {
|
||||
"message": "Finito"
|
||||
},
|
||||
@ -1343,24 +1325,6 @@
|
||||
"zeroGasPriceOnSpeedUpError": {
|
||||
"message": "Prezzo del gas maggiore di zero"
|
||||
},
|
||||
"connections": {
|
||||
"message": "Connessioni"
|
||||
},
|
||||
"connectionsSettingsDescription": {
|
||||
"message": "Siti autorizzati ad accedere ai tuoi accounts"
|
||||
},
|
||||
"addSite": {
|
||||
"message": "Aggiungi Sito"
|
||||
},
|
||||
"addSiteDescription": {
|
||||
"message": "Aggiungi un sito autorizzato ad accedere ai tuoi accounts, utile per dapps obsolete"
|
||||
},
|
||||
"connected": {
|
||||
"message": "Connesso"
|
||||
},
|
||||
"connectedDescription": {
|
||||
"message": "La lista di siti web autorizzati ad accedere ai tuoi indirizzi"
|
||||
},
|
||||
"contacts": {
|
||||
"message": "Contatti"
|
||||
},
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "プライバシーモードがデフォルトで有効になりました"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "チャートはEthereumネットワークでのみ利用可能です。"
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "承認されたウェブサイトをクリアしてもよろしいですか?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "コントラクトへのアクセス"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "承認データのクリア"
|
||||
},
|
||||
"reject": {
|
||||
"message": "拒否"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 はあなたのアカウントにアクセスしようとしています。"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "下記のドメインは、Ethereumブロックチェーンとやり取りできるようにEthereum APIへのアクセスをリクエストしようとしています。 Web3アクセスを承認する前に、正しいサイトにいることを常に確認してください。"
|
||||
},
|
||||
"aboutSettingsDescription": {
|
||||
"message": "バージョンやサポート、問合せ先など"
|
||||
},
|
||||
@ -84,7 +72,7 @@
|
||||
"message": "推奨トークンを追加"
|
||||
},
|
||||
"addAcquiredTokens": {
|
||||
"message": "Metamaskで獲得したトークンを追加する"
|
||||
"message": "MetaMaskで獲得したトークンを追加する"
|
||||
},
|
||||
"amount": {
|
||||
"message": "金額"
|
||||
|
@ -1,19 +1,10 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "ಗೌಪ್ಯತೆ ಮೋಡ್ ಅನ್ನು ಡೀಫಾಲ್ಟ್ ಆಗಿ ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "ಎಥೆರಿಯಮ್ ನೆಟ್ವರ್ಕ್ಗಳಲ್ಲಿ ಮಾತ್ರವೇ ಚಾರ್ಟ್ಗಳು ಲಭ್ಯವಿರುತ್ತವೆ."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "ನೀವು ಅನುಮೋದಿಸಿದ ವೆಬ್ಸೈಟ್ಗಳನ್ನು ತೆರವುಗೊಳಿಸಲು ಬಯಸುವಿರಾ?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "ಒಪ್ಪಂದದ ಸಂವಹನ"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "ಗೌಪ್ಯತೆ ಡೇಟಾವನ್ನು ತೆರವುಗೊಳಿಸಿ"
|
||||
},
|
||||
"appName": {
|
||||
"message": "MetaMask",
|
||||
"description": "The name of the application"
|
||||
@ -21,12 +12,9 @@
|
||||
"reject": {
|
||||
"message": "ತಿರಸ್ಕರಿಸಿ"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 ನಿಮ್ಮ ಖಾತೆಗೆ ಸಂಪರ್ಕಿಸಲು ಬಯಸುತ್ತಿದೆ"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "ಈ ಸೈಟ್ ನಿಮ್ಮ ಪ್ರಸ್ತುತ ಖಾತೆ ವಿಳಾಸವನ್ನು ವೀಕ್ಷಿಸಲು ಪ್ರವೇಶವನ್ನು ವಿನಂತಿಸುತ್ತಿದೆ. ನೀವು ಸಂವಹನ ನಡೆಸುವ ಸೈಟ್ಗಳನ್ನು ನೀವು ನಂಬಿರುವಿರಿ ಎಂಬುದನ್ನು ಯಾವಾಗಲೂ ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ."
|
||||
},
|
||||
"about": {
|
||||
"message": "ಕುರಿತು"
|
||||
},
|
||||
@ -248,9 +236,6 @@
|
||||
"connect": {
|
||||
"message": "ಸಂಪರ್ಕಿಸು"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "ವಿನಂತಿಯನ್ನು ಸಂಪರ್ಕಪಡಿಸಿ"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "$1 ಗೆ ಸಂಪರ್ಕಪಡಿಸಲಾಗುತ್ತಿದೆ"
|
||||
},
|
||||
@ -368,9 +353,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "ನೀವು ಈಗಾಗಲೇ ಕೆಲವು ಎಥರ್ ಹೊಂದಿದ್ದರೆ, ನೇರ ಠೇವಣಿ ಮೂಲಕ ನಿಮ್ಮ ಹೊಸ ವ್ಯಾಲೆಟ್ನಲ್ಲಿ ಎಥರ್ ಅನ್ನು ಪಡೆಯುವ ತ್ವರಿತ ಮಾರ್ಗ."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "ವಜಾಗೊಳಿಸಿ"
|
||||
},
|
||||
"done": {
|
||||
"message": "ಮುಗಿದಿದೆ"
|
||||
},
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "이제 프라이버시 모드가 기본 설정으로 활성화됐습니다"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "이더리움 네트워크에서만 사용 가능한 차트."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "승인 된 웹 사이트를 삭제 하시겠습니까?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "계약 상호 작용"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "승인 데이터 삭제"
|
||||
},
|
||||
"reject": {
|
||||
"message": "거부"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1이 당신의 계정에 연결하길 원합니다."
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "아래 나열된 도메인은 Web3 API에 대한 액세스를 요청하여 Ethereum 블록 체인과 상호 작용할 수 있습니다. Ethereum 액세스를 승인하기 전에 항상 올바른 사이트에 있는지 다시 확인하십시오."
|
||||
},
|
||||
"about": {
|
||||
"message": "정보"
|
||||
},
|
||||
@ -245,9 +233,6 @@
|
||||
"connect": {
|
||||
"message": "연결"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "연결 요청"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "$1에 연결"
|
||||
},
|
||||
@ -365,9 +350,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "약간의 이더를 이미 보유하고 있다면, 새로 만든 지갑에 직접 입금하여 이더를 보유할 수 있습니다."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "숨기기"
|
||||
},
|
||||
"done": {
|
||||
"message": "완료"
|
||||
},
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "Dabar privatumo režimas suaktyvintas pagal numatytąją nuostatą"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Diagramos yra tik „Ethereum“ tinkluose."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Ar tikrai norite panaikinti patvirtintas svetaines?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Sutartinė sąveika"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Išvalyti asmeninius duomenis"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Atmesti"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 norėtų prisijungti prie jūsų paskyros"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Ši svetainė prašo prieigos peržiūrėti jūsų dabartinės paskyros adresą. Visada patikrinkite, ar pasitikite svetainėmis, su kuriomis sąveikaujate."
|
||||
},
|
||||
"about": {
|
||||
"message": "Apie"
|
||||
},
|
||||
@ -248,9 +236,6 @@
|
||||
"connect": {
|
||||
"message": "Prisijungti"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Prijungimo užklausa"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Jungiamasi prie $1"
|
||||
},
|
||||
@ -368,9 +353,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Jeigu jau turite šiek tiek eterių, sparčiausias būdas gauti eterių į naują piniginę yra tiesioginis įnašas."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Atsisakyti"
|
||||
},
|
||||
"done": {
|
||||
"message": "Atlikta"
|
||||
},
|
||||
|
@ -1,19 +1,10 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "Privātais režīms tagad ieslēgts pēc noklusējuma"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Grafiks pieejams vienīgi Ethereum tīklos."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Vai tiešām vēlaties dzēst apstiprinātās vietnes?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Līguma mijiedarbības"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Notīrīt konfidencialitātes datus"
|
||||
},
|
||||
"appName": {
|
||||
"message": "MetaMask",
|
||||
"description": "The name of the application"
|
||||
@ -21,12 +12,9 @@
|
||||
"reject": {
|
||||
"message": "Noraidīt"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 vēlas izveidot savienojumu ar jūsu kontu"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Šī lapa pieprasa piekļuvi jūsu pašreizēja konta adreses informācijai. Vienmēr pārliecinieties, ka uzticaties lapām, kuras apmeklējat."
|
||||
},
|
||||
"about": {
|
||||
"message": "Par"
|
||||
},
|
||||
@ -248,9 +236,6 @@
|
||||
"connect": {
|
||||
"message": "Pievienošana"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Savienojuma pieprasījums"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Pieslēdzas $1"
|
||||
},
|
||||
@ -368,9 +353,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Ja jums jau ir Ether, tad visātrāk Ether savā makā varat saņemt ar tiešo iemaksu."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Noraidīt"
|
||||
},
|
||||
"done": {
|
||||
"message": "Pabeigts"
|
||||
},
|
||||
|
@ -54,9 +54,6 @@
|
||||
"details": {
|
||||
"message": "വിശദാംശങ്ങൾ"
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "ബഹിഷ്ക്കരിക്കുക"
|
||||
},
|
||||
"done": {
|
||||
"message": "പൂർത്തിയാക്കി"
|
||||
},
|
||||
|
@ -54,9 +54,6 @@
|
||||
"details": {
|
||||
"message": "तपशील"
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "डिसमिस करा"
|
||||
},
|
||||
"done": {
|
||||
"message": "पूर्ण झाले"
|
||||
},
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "Mod Privasi kini diaktifkan secara lalai"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Carta hanya tersedia di rangkaian Ethereum."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Adakah anda pasti mahu mengosongkan tapak web diluluskan?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Interaksi Kontrak"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Kosongkan Data Privasi"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Tolak"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 ingin menyambung kepada akaun anda"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Tapak ini meminta akses untuk melihat alamat akaun semasa anda. Sentiasa pastikan anda mempercayai tapak web yang anda berinteraksi."
|
||||
},
|
||||
"about": {
|
||||
"message": "Mengenai"
|
||||
},
|
||||
@ -245,9 +233,6 @@
|
||||
"connect": {
|
||||
"message": "Sambung"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Sambungkan Permintaan"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Menyambungkan kepada $1"
|
||||
},
|
||||
@ -359,9 +344,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Jika anda sudah mempunyai Ether, cara paling cepat untuk mendapatkan Ether di dompet baru anda ialah dengan deposit langsung."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Singkirkan"
|
||||
},
|
||||
"done": {
|
||||
"message": "Selesai"
|
||||
},
|
||||
|
@ -1,16 +1,7 @@
|
||||
{
|
||||
"confirmClear": {
|
||||
"message": "Weet je zeker dat je goedgekeurde websites wilt wissen?"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Gegevens over goedkeuring wissen"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Afwijzen"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Het onderstaande domein probeert toegang tot de Ethereum API te vragen zodat deze kan communiceren met de Ethereum-blockchain. Controleer altijd eerst of u op de juiste site bent voordat u Ethereum-toegang goedkeurt."
|
||||
},
|
||||
"accountDetails": {
|
||||
"message": "Accountgegevens"
|
||||
},
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "Personvernmodus er nå aktivert som standard"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Diagram kun tilgjengelig på Ethereum-nettverk."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Er du sikker på at du vil tømme godkjente nettsteder?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Kontraktssamhandling"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Tøm personvernsdata"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Avslå"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 ønsker å forbindes med kontoen din "
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Dette nettstedet ber om tilgang til å vise din nåværende kontoadresse. Alltid kontroller at du stoler på nettstedene du samhandler med."
|
||||
},
|
||||
"about": {
|
||||
"message": "Info"
|
||||
},
|
||||
@ -245,9 +233,6 @@
|
||||
"connect": {
|
||||
"message": "Koble til"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Kontaktforespørsel "
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Forbinder til $1 "
|
||||
},
|
||||
@ -365,9 +350,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Hvis du allerede har noe Ether, er den raskeste måten å få Ether i den nye lommeboken din på ved hjelp av direkte innskudd."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Lukk"
|
||||
},
|
||||
"done": {
|
||||
"message": "Ferdig"
|
||||
},
|
||||
|
@ -1,10 +1,4 @@
|
||||
{
|
||||
"confirmClear": {
|
||||
"message": "Sigurado ka bang gusto mong i-clear ang mga naaprubahang website?"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Tanggalin ang data ng pag-apruba"
|
||||
},
|
||||
"appName": {
|
||||
"message": "MetaMask",
|
||||
"description": "The name of the application"
|
||||
@ -15,9 +9,6 @@
|
||||
"reject": {
|
||||
"message": "Tanggihan"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Ang domain na nakalista sa ibaba ay sinusubukang humiling ng access sa Ethereum API upang maaari itong makipag-ugnayan sa Ethereum blockchain. Laging i-double check na ikaw ay nasa tamang site bago aprubahan ang Ethereum access."
|
||||
},
|
||||
"accountDetails": {
|
||||
"message": "Detalye ng Account"
|
||||
},
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "Tryb prywatny jest domyślnie włączony"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Wykres dostępny tylko w sieciach Ethereum"
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Czy na pewno chcesz usunąć zatwierdzone strony internetowe?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Interakcja z kontraktem"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Usuń dane poufne"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Odrzuć"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 chce połączyć się z Twoim kontem"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Ta strona prosi o dostęp, aby zobaczyć adres Twojego aktualnego konta. Zawsze upewnij się, że ufasz stronom, z którymi wchodzisz w interakcję."
|
||||
},
|
||||
"about": {
|
||||
"message": "Informacje"
|
||||
},
|
||||
@ -245,9 +233,6 @@
|
||||
"connect": {
|
||||
"message": "Połącz"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Potwierdź żądanie"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Łączenie z $1"
|
||||
},
|
||||
@ -365,9 +350,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Jeśli już masz Eter, najszybciej umieścisz go w swoim nowym portfelu przy pomocy bezpośredniego depozytu."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Zamknij"
|
||||
},
|
||||
"done": {
|
||||
"message": "Gotowe"
|
||||
},
|
||||
|
@ -1,16 +1,7 @@
|
||||
{
|
||||
"confirmClear": {
|
||||
"message": "Tem certeza de que deseja limpar sites aprovados?"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Limpar dados de aprovação"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Rejeitar"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "O domínio listado abaixo está tentando solicitar acesso à API Ethereum para que ele possa interagir com o blockchain Ethereum. Sempre verifique se você está no site correto antes de aprovar o acesso à Ethereum."
|
||||
},
|
||||
"account": {
|
||||
"message": "Conta"
|
||||
},
|
||||
|
@ -1,19 +1,10 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "O Modo de Privacidade está ativado por padrão"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Tabela disponível apenas em redes de Ethereum."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Tem certeza de que deseja limpar os sites aprovados?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Interação do Contrato"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Limpar Dados de Privacidade"
|
||||
},
|
||||
"appName": {
|
||||
"message": "MetaMask",
|
||||
"description": "The name of the application"
|
||||
@ -21,12 +12,9 @@
|
||||
"reject": {
|
||||
"message": "Rejeitar"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 gostaria de se conectar à sua conta"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Este site está solicitando acesso para visualizar o seu endereço de conta atual. Certifique-se sempre de confiar nos sites com os quais você interage."
|
||||
},
|
||||
"about": {
|
||||
"message": "Sobre"
|
||||
},
|
||||
@ -242,9 +230,6 @@
|
||||
"connect": {
|
||||
"message": "Conectar-se"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Solicitação de Conexão"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Conectando a $1"
|
||||
},
|
||||
@ -362,9 +347,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Se você já tem Ether, a forma mais rápida de colocá-lo em sua nova carteira é o depósito direto."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Dispensar"
|
||||
},
|
||||
"done": {
|
||||
"message": "Concluído"
|
||||
},
|
||||
|
@ -63,9 +63,6 @@
|
||||
"details": {
|
||||
"message": "Detalhes"
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Ignorar"
|
||||
},
|
||||
"done": {
|
||||
"message": "Concluído"
|
||||
},
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "Modul de confidențialitate este activat acum în mod implicit"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Grafic disponibil numai pe rețelele Ethereum."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Sunteți sigur că doriți să ștergeți site-urile aprobate?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Interacțiune contract"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Ștergeți datele confidențiale"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Respingeți"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 ar dori să se conecteze la contul dvs."
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Acest site solicită acces pentru a vedea adresa curentă a contului dvs. Asigurați-vă întotdeauna că aveți încredere în site-urile cu care interacționați."
|
||||
},
|
||||
"about": {
|
||||
"message": "Despre"
|
||||
},
|
||||
@ -248,9 +236,6 @@
|
||||
"connect": {
|
||||
"message": "Conectează-te"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Solicitare de conectare"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Se conectează la $1"
|
||||
},
|
||||
@ -368,9 +353,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Dacă deja aveți Ether, cel mai rapid mod de a avea Ether în portofelul nou prin depunere directă."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Închide"
|
||||
},
|
||||
"done": {
|
||||
"message": "Efectuat"
|
||||
},
|
||||
|
@ -1,16 +1,7 @@
|
||||
{
|
||||
"confirmClear": {
|
||||
"message": "Вы уверены, что хотите очистить утвержденные веб-сайты?Tem certeza de que deseja limpar sites aprovados?"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Четкие данные об утверждении"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Отклонить"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Домен, указанный ниже, пытается запросить доступ к API-интерфейсу Ethereum, чтобы он мог взаимодействовать с блокчейном Ethereum. Всегда проверяйте, что вы находитесь на правильном сайте, прежде чем одобрять доступ к веб-сайту."
|
||||
},
|
||||
"account": {
|
||||
"message": "Счет"
|
||||
},
|
||||
@ -545,16 +536,13 @@
|
||||
"youSign": {
|
||||
"message": "Вы подписываете"
|
||||
},
|
||||
"privacyModeDefault": {
|
||||
"message": "Режим конфиденциальности теперь включен по умолчанию"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Диаграмма доступна только в сетях Ethereum."
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Взаимодействие с контрактом"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 запрашивает доступ к вашему аккаунту"
|
||||
},
|
||||
"about": {
|
||||
@ -707,9 +695,6 @@
|
||||
"connect": {
|
||||
"message": "Подключиться"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Запрос на подключение"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Подключение к $1"
|
||||
},
|
||||
@ -752,9 +737,6 @@
|
||||
"deleteAccount": {
|
||||
"message": "Удалить аккаунт"
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Отклюнить"
|
||||
},
|
||||
"downloadGoogleChrome": {
|
||||
"message": "Скачать Google Chrome"
|
||||
},
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "Režim súkromia je povolený. Je prednastavený automaticky"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Graf je k dispozícii iba v sieťach Ethereum."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Naozaj chcete vymazať schválené webové stránky?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Zmluvná interakcia"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Jasné údaje o schválení"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Odmítnout"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 sa chce pripojiť k vášmu účtu"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Níže uvedená doména se pokouší požádat o přístup k API Ethereum, aby mohla komunikovat s blokádou Ethereum. Před schválením přístupu Ethereum vždy zkontrolujte, zda jste na správném místě."
|
||||
},
|
||||
"about": {
|
||||
"message": "Informácie"
|
||||
},
|
||||
@ -239,9 +227,6 @@
|
||||
"connect": {
|
||||
"message": "Pripojenie"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Požiadavka na pripojenie"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Pripája sa k $1"
|
||||
},
|
||||
@ -359,9 +344,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Pokud už vlastníte nějaký Ether, nejrychleji ho dostanete do peněženky přímým vkladem."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Zatvoriť"
|
||||
},
|
||||
"done": {
|
||||
"message": "Hotovo"
|
||||
},
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "Zasebnostni način je zdaj privzeto omogočen"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Grafikon na voljo le v glavnih omrežjih."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Ste prepričani da želite počistiti odobrene spletne strani?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Interakcija s pogodbo"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Počisti podatke o odobritvi"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Zavrni"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 se želi povezati z vašim računom."
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Domena zahteva dostop do verige blokov in ogled vašega računa. Pred potrditvjo vedno preverite ali ste na želeni spletni strani."
|
||||
},
|
||||
"about": {
|
||||
"message": "O možnostih"
|
||||
},
|
||||
@ -248,9 +236,6 @@
|
||||
"connect": {
|
||||
"message": "Poveži"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Zahteva za povezavo"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Povezovanje na $1"
|
||||
},
|
||||
@ -368,9 +353,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Če že imate Ether, ga lahko najhitreje dobite v MetaMask z neposrednim vplačilom."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Opusti"
|
||||
},
|
||||
"done": {
|
||||
"message": "Končano"
|
||||
},
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "Režim privatnosti je podrazumevano omogućen"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Grafikon dostupan jedino na mrežama Ethereum."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Da li ste sigurni da želite da obrišete odobrene veb lokacije?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Ugovorna interakcija"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Obrišite privatne podatke"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Одбиј"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 bi hteo da se poveže sa vašim nalogom"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Ovaj sajt traži pristup kako bi video vašu trenutnu adresu naloga. Uvek budite sigurni da verujete sajtovima s kojima komunicirate."
|
||||
},
|
||||
"about": {
|
||||
"message": "Основни подаци"
|
||||
},
|
||||
@ -245,9 +233,6 @@
|
||||
"connect": {
|
||||
"message": "Повезивање"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Zahtev za povezivanjem"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Povezuje se na $1"
|
||||
},
|
||||
@ -365,9 +350,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Ako već imate neki Ether, najbrži način da preuzmete Ether u svoj novi novčanik jeste direktnim deponovanjem."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Одбаци"
|
||||
},
|
||||
"done": {
|
||||
"message": "Gotovo"
|
||||
},
|
||||
|
@ -1,19 +1,10 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "Integritetsläge är nu aktiverat som standard"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Tabellen är endast tillgänglig på Ethereum-nätverk."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Är du säker på att du vill rensa godkända webbplatser?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Kontraktinteraktion"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Rensa personlig data"
|
||||
},
|
||||
"appName": {
|
||||
"message": "MetaMask",
|
||||
"description": "The name of the application"
|
||||
@ -21,12 +12,9 @@
|
||||
"reject": {
|
||||
"message": "Avvisa"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 vill ansluta till ditt konto"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Den här sidan begär åtkomst till din aktuella kontoadress. Se till att du kan lita på de sidor du använder dig av."
|
||||
},
|
||||
"about": {
|
||||
"message": "Om"
|
||||
},
|
||||
@ -242,9 +230,6 @@
|
||||
"connect": {
|
||||
"message": "Ansluta"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Anslutningsförfrågan"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Ansluter till $1"
|
||||
},
|
||||
@ -362,9 +347,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Om du redan har Ether är det snabbaste sättet att få Ether i din nya plånbok att göra en direktinsättning."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Ta bort permanent"
|
||||
},
|
||||
"done": {
|
||||
"message": "Klart"
|
||||
},
|
||||
|
@ -1,19 +1,10 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "Hali ya Faragha sasa imewezeshwa kwa chaguomsingi"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Zogoa inapatikana kwenye mitandao ya Ethereum pekee."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Una uhakika unataka kufuta tovuti zilizodihinishwa?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Mwingiliono wa Mkataba"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Futa Data za Faragha"
|
||||
},
|
||||
"appName": {
|
||||
"message": "MetaMask",
|
||||
"description": "The name of the application"
|
||||
@ -21,12 +12,9 @@
|
||||
"reject": {
|
||||
"message": "Kataa"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 ingependa kuunganishwa kwenye akaunti yako"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Tovuti hii inaomba idhini ya kuangalia anwani yako ya akaunti ya sasa. Daima hakikisha unaziamami tovuti ambazo unaingiliana nazo."
|
||||
},
|
||||
"about": {
|
||||
"message": "Kuhusu"
|
||||
},
|
||||
@ -242,9 +230,6 @@
|
||||
"connect": {
|
||||
"message": "Unganisha"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Unganisha Ombi"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Inaunganisha kwenye $1"
|
||||
},
|
||||
@ -362,9 +347,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Ikiwa tayari una sarafu kadhaa za Ether, njia rahisi ya kupata Ether kwenye waleti yako mpya kupitia kuweka moja kwa moja."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Ondoa"
|
||||
},
|
||||
"done": {
|
||||
"message": "Imekamilika"
|
||||
},
|
||||
|
@ -1,19 +1,10 @@
|
||||
{
|
||||
"confirmClear": {
|
||||
"message": "அங்கீகரிக்கப்பட்ட வலைத்தளங்களை நிச்சயமாக நீக்க விரும்புகிறீர்களா?"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "ஒப்புதல் தரவை அழி"
|
||||
},
|
||||
"approve": {
|
||||
"message": "ஒப்புதல்"
|
||||
},
|
||||
"reject": {
|
||||
"message": "நிராகரி"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "கீழே பட்டியலிடப்பட்டுள்ள டொமைன் Web3 ஏபிஐ அணுகலைக் கோருவதற்கு முயற்சிக்கிறது, எனவே இது Ethereum blockchain உடன் தொடர்பு கொள்ள முடியும். Web3 அணுகுமுறையை அங்கீகரிப்பதற்கு முன் சரியான தளத்தில் இருப்பதை எப்போதும் இருமுறை சரிபார்க்கவும்."
|
||||
},
|
||||
"account": {
|
||||
"message": "கணக்கு"
|
||||
},
|
||||
@ -578,9 +569,6 @@
|
||||
"delete": {
|
||||
"message": "நீக்கு"
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "நிராகரி"
|
||||
},
|
||||
"fast": {
|
||||
"message": "வேகமான"
|
||||
},
|
||||
|
@ -54,9 +54,6 @@
|
||||
"details": {
|
||||
"message": "వివరాలు"
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "తొలగించు"
|
||||
},
|
||||
"done": {
|
||||
"message": "పూర్తయింది"
|
||||
},
|
||||
|
@ -1,19 +1,10 @@
|
||||
{
|
||||
"confirmClear": {
|
||||
"message": "คุณแน่ใจหรือไม่ว่าต้องการล้างเว็บไซต์ที่ผ่านการอนุมัติ"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "ล้างข้อมูลการอนุมัติ"
|
||||
},
|
||||
"reject": {
|
||||
"message": "ปฏิเสธ"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 ต้องการเชื่อมต่อกับบัญชีของคุณ"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "โดเมนที่แสดงด้านล่างกำลังพยายามขอเข้าถึง API ของ Ethereum เพื่อให้สามารถโต้ตอบกับบล็อค Ethereum ได้ ตรวจสอบว่าคุณอยู่ในไซต์ที่ถูกต้องก่อนที่จะอนุมัติการเข้าถึง Ethereum เสมอ"
|
||||
},
|
||||
"about": {
|
||||
"message": "เกี่ยวกับ"
|
||||
},
|
||||
@ -184,9 +175,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "ถ้าคุณมีอีเธอร์อยู่แล้ววิธีการที่เร็วที่สุดในการเอาเงินเข้ากระเป๋าใหม่ก็คือการโอนตรงๆ"
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "ปิด"
|
||||
},
|
||||
"done": {
|
||||
"message": "เสร็จสิ้น"
|
||||
},
|
||||
|
@ -1,16 +1,7 @@
|
||||
{
|
||||
"confirmClear": {
|
||||
"message": "Onaylanmış web sitelerini silmek istediğinizden emin misiniz?"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Onay verilerini temizle"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Reddetmek"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Aşağıda listelenen etki alanı, Ethereum API'sine erişim talep etmeye çalışmaktadır, böylece Ethereum blockchain ile etkileşime girebilir. Web3 erişimini onaylamadan önce her zaman doğru sitede olduğunuzu kontrol edin."
|
||||
},
|
||||
"account": {
|
||||
"message": "Hesap"
|
||||
},
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "Режим конфіденційності тепер увімкнено за замовчуванням"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "Таблиця доступна тільки в мережах Ethereum."
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "Ви впевнені, що хочете очистити затверджені веб-сайти?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Контрактна взаємодія"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Очистити приватні дані"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Відхилити"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 бажає підключитися до вашого облікового запису"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Цей сайт запитує доступ на перегляд вашої поточної адреси облікового запису. Завжди взаємодійте лише з веб-сайтами, яким довіряєте."
|
||||
},
|
||||
"about": {
|
||||
"message": "Про Google Chrome"
|
||||
},
|
||||
@ -248,9 +236,6 @@
|
||||
"connect": {
|
||||
"message": "Під’єднатися"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "Запит на з'єднання"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "Під'єднуємось до $1"
|
||||
},
|
||||
@ -368,9 +353,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Якщо ви вже маєте ефір, пряме переведення – найшвидший спосіб передати ефір у свій гаманець."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Відхилити"
|
||||
},
|
||||
"done": {
|
||||
"message": "Готово"
|
||||
},
|
||||
|
@ -1,16 +1,7 @@
|
||||
{
|
||||
"confirmClear": {
|
||||
"message": "Bạn có chắc chắn muốn xóa các trang web được phê duyệt không?"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Xóa dữ liệu phê duyệt"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Từ chối"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "Miền được liệt kê bên dưới đang cố gắng yêu cầu quyền truy cập vào API Ethereum để nó có thể tương tác với chuỗi khối Ethereum. Luôn kiểm tra kỹ xem bạn có đang ở đúng trang web trước khi phê duyệt quyền truy cập Ethereum hay không."
|
||||
},
|
||||
"account": {
|
||||
"message": "Tài khoản"
|
||||
},
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "现已默认启用隐私模式"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "聊天功能仅对以太坊网络开放。"
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "您确定要清除已批准的网站吗?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "合约交互"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "清除批准数据"
|
||||
},
|
||||
"reject": {
|
||||
"message": "拒绝"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 希望关联您的账户"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "下面列出的域正在尝试请求访问Ethereum API,以便它可以与以太坊区块链进行交互。在批准Ethereum访问之前,请务必仔细检查您是否在正确的站点上。"
|
||||
},
|
||||
"about": {
|
||||
"message": "关于"
|
||||
},
|
||||
@ -87,7 +75,7 @@
|
||||
"message": "添加推荐代币"
|
||||
},
|
||||
"addAcquiredTokens": {
|
||||
"message": "在Metamask上添加已用的代币"
|
||||
"message": "在MetaMask上添加已用的代币"
|
||||
},
|
||||
"amount": {
|
||||
"message": "数量"
|
||||
@ -248,9 +236,6 @@
|
||||
"connect": {
|
||||
"message": "连接"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "关联请求"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "正在连接 $1"
|
||||
},
|
||||
@ -368,9 +353,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "如果你已经有了一些 Ether,通过直接转入是你的新钱包获取 Ether 的最快捷方式。"
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "关闭"
|
||||
},
|
||||
"done": {
|
||||
"message": "完成"
|
||||
},
|
||||
|
@ -1,28 +1,16 @@
|
||||
{
|
||||
"privacyModeDefault": {
|
||||
"message": "隱私模式現已根據預設開啟"
|
||||
},
|
||||
"chartOnlyAvailableEth": {
|
||||
"message": "圖表僅適用於以太坊網路。"
|
||||
},
|
||||
"confirmClear": {
|
||||
"message": "您確定要清除已批准的網站紀錄?"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "合約互動"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "清除批准數據"
|
||||
},
|
||||
"reject": {
|
||||
"message": "拒絕"
|
||||
},
|
||||
"providerRequest": {
|
||||
"likeToConnect": {
|
||||
"message": "$1 請求訪問帳戶權限"
|
||||
},
|
||||
"providerRequestInfo": {
|
||||
"message": "此網站希望能讀取您的帳戶資訊。請務必確認您信任這個網站、並了解後續可能的交易行為。"
|
||||
},
|
||||
"about": {
|
||||
"message": "關於"
|
||||
},
|
||||
@ -245,9 +233,6 @@
|
||||
"connect": {
|
||||
"message": "連線"
|
||||
},
|
||||
"connectRequest": {
|
||||
"message": "連線請求"
|
||||
},
|
||||
"connectingTo": {
|
||||
"message": "連線到$1"
|
||||
},
|
||||
@ -365,9 +350,6 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "如果您已經擁有乙太幣,直接存入功能是讓新錢包最快取得乙太幣的方式。"
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "關閉"
|
||||
},
|
||||
"done": {
|
||||
"message": "完成"
|
||||
},
|
||||
|
3
app/images/broken-line.svg
Normal file
3
app/images/broken-line.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="131" height="2" viewBox="0 0 131 2" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 1H134" stroke="#CDD1E4" stroke-linejoin="round" stroke-dasharray="8 0"/>
|
||||
</svg>
|
After Width: | Height: | Size: 188 B |
3
app/images/connect-white.svg
Normal file
3
app/images/connect-white.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.6483 7.41741C14.3482 7.41741 15.8272 8.36346 16.5874 9.75779L18.8081 8.36985C17.5833 6.23784 15.2835 4.802 12.6483 4.802C9.11954 4.802 6.19214 7.37666 5.64216 10.7499H2.125C1.50368 10.7499 1 11.2536 1 11.8749C1 12.4962 1.50368 12.9999 2.125 12.9999H5.63386C6.16206 16.3987 9.1014 18.9999 12.6483 18.9999C15.2235 18.9999 17.4784 17.6287 18.7228 15.5766L16.5042 14.1901C15.7225 15.5041 14.2882 16.3845 12.6483 16.3845C10.1721 16.3845 8.16472 14.3772 8.16472 11.901C8.16472 9.42476 10.1721 7.41741 12.6483 7.41741ZM11.7002 11.9999C11.7002 12.5522 12.1479 12.9999 12.7002 12.9999C13.2525 12.9999 13.7002 12.5522 13.7002 11.9999C13.7002 11.4476 13.2525 10.9999 12.7002 10.9999C12.1479 10.9999 11.7002 11.4476 11.7002 11.9999ZM12.7002 14.9999C11.0433 14.9999 9.7002 13.6568 9.7002 11.9999C9.7002 10.3431 11.0433 8.99992 12.7002 8.99992C13.9109 8.99992 14.9542 9.71715 15.4282 10.7499H21.8751C22.4965 10.7499 23.0001 11.2536 23.0001 11.8749C23.0001 12.4962 22.4965 12.9999 21.8751 12.9999H15.5295C15.1177 14.1651 14.0064 14.9999 12.7002 14.9999Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 937 B After Width: | Height: | Size: 937 B |
@ -64,6 +64,7 @@ const isEdge = !isIE && !!window.StyleMedia
|
||||
let popupIsOpen = false
|
||||
let notificationIsOpen = false
|
||||
const openMetamaskTabsIDs = {}
|
||||
const requestAccountTabIds = {}
|
||||
|
||||
// state persistence
|
||||
const localStore = new LocalStore()
|
||||
@ -75,7 +76,6 @@ initialize().catch(log.error)
|
||||
// setup metamask mesh testing container
|
||||
const { submitMeshMetricsEntry } = setupMetamaskMeshMetrics()
|
||||
|
||||
|
||||
/**
|
||||
* An object representing a transaction, in whatever state it is in.
|
||||
* @typedef TransactionMeta
|
||||
@ -248,6 +248,12 @@ function setupController (initState, initLangCode) {
|
||||
// platform specific api
|
||||
platform,
|
||||
encryptor: isEdge ? new EdgeEncryptor() : undefined,
|
||||
getRequestAccountTabIds: () => {
|
||||
return requestAccountTabIds
|
||||
},
|
||||
getOpenMetamaskTabsIds: () => {
|
||||
return openMetamaskTabsIDs
|
||||
},
|
||||
})
|
||||
|
||||
const provider = controller.provider
|
||||
@ -387,6 +393,17 @@ function setupController (initState, initLangCode) {
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (remotePort.sender && remotePort.sender.tab && remotePort.sender.url) {
|
||||
const tabId = remotePort.sender.tab.id
|
||||
const url = new URL(remotePort.sender.url)
|
||||
const origin = url.hostname
|
||||
|
||||
remotePort.onMessage.addListener(msg => {
|
||||
if (msg.data && msg.data.method === 'eth_requestAccounts') {
|
||||
requestAccountTabIds[origin] = tabId
|
||||
}
|
||||
})
|
||||
}
|
||||
connectExternal(remotePort)
|
||||
}
|
||||
}
|
||||
@ -411,7 +428,7 @@ function setupController (initState, initLangCode) {
|
||||
controller.messageManager.on('updateBadge', updateBadge)
|
||||
controller.personalMessageManager.on('updateBadge', updateBadge)
|
||||
controller.typedMessageManager.on('updateBadge', updateBadge)
|
||||
controller.providerApprovalController.memStore.on('update', updateBadge)
|
||||
controller.permissionsController.permissions.subscribe(updateBadge)
|
||||
|
||||
/**
|
||||
* Updates the Web Extension's "badge" number, on the little fox in the toolbar.
|
||||
@ -423,8 +440,8 @@ function setupController (initState, initLangCode) {
|
||||
const unapprovedMsgCount = controller.messageManager.unapprovedMsgCount
|
||||
const unapprovedPersonalMsgs = controller.personalMessageManager.unapprovedPersonalMsgCount
|
||||
const unapprovedTypedMsgs = controller.typedMessageManager.unapprovedTypedMessagesCount
|
||||
const pendingProviderRequests = controller.providerApprovalController.memStore.getState().providerRequests.length
|
||||
const count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgs + unapprovedTypedMsgs + pendingProviderRequests
|
||||
const pendingPermissionRequests = Object.keys(controller.permissionsController.permissions.state.permissionsRequests).length
|
||||
const count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgs + unapprovedTypedMsgs + pendingPermissionRequests
|
||||
if (count) {
|
||||
label = String(count)
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ const fs = require('fs')
|
||||
const path = require('path')
|
||||
const pump = require('pump')
|
||||
const log = require('loglevel')
|
||||
const Dnode = require('dnode')
|
||||
const querystring = require('querystring')
|
||||
const { Writable } = require('readable-stream')
|
||||
const LocalMessageDuplexStream = require('post-message-stream')
|
||||
@ -21,7 +20,7 @@ const inpageBundle = inpageContent + inpageSuffix
|
||||
// If we create a FireFox-only code path using that API,
|
||||
// MetaMask will be much faster loading and performant on Firefox.
|
||||
|
||||
if (shouldInjectWeb3()) {
|
||||
if (shouldInjectProvider()) {
|
||||
injectScript(inpageBundle)
|
||||
start()
|
||||
}
|
||||
@ -40,7 +39,7 @@ function injectScript (content) {
|
||||
container.insertBefore(scriptTag, container.children[0])
|
||||
container.removeChild(scriptTag)
|
||||
} catch (e) {
|
||||
console.error('MetaMask script injection failed', e)
|
||||
console.error('MetaMask provider injection failed.', e)
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,12 +131,6 @@ async function setupStreams () {
|
||||
// connect "phishing" channel to warning system
|
||||
const phishingStream = extensionMux.createStream('phishing')
|
||||
phishingStream.once('data', redirectToPhishingWarning)
|
||||
|
||||
// connect "publicApi" channel to submit page metadata
|
||||
const publicApiStream = extensionMux.createStream('publicApi')
|
||||
const background = await setupPublicApi(publicApiStream)
|
||||
|
||||
return { background }
|
||||
}
|
||||
|
||||
function forwardTrafficBetweenMuxers (channelName, muxA, muxB) {
|
||||
@ -151,39 +144,6 @@ function forwardTrafficBetweenMuxers (channelName, muxA, muxB) {
|
||||
)
|
||||
}
|
||||
|
||||
async function setupPublicApi (outStream) {
|
||||
const api = {
|
||||
getSiteMetadata: (cb) => cb(null, getSiteMetadata()),
|
||||
}
|
||||
const dnode = Dnode(api)
|
||||
pump(
|
||||
outStream,
|
||||
dnode,
|
||||
outStream,
|
||||
(err) => {
|
||||
// report any error
|
||||
if (err) {
|
||||
log.error(err)
|
||||
}
|
||||
}
|
||||
)
|
||||
const background = await new Promise(resolve => dnode.once('remote', resolve))
|
||||
return background
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets site metadata and returns it
|
||||
*
|
||||
*/
|
||||
function getSiteMetadata () {
|
||||
// get metadata
|
||||
const metadata = {
|
||||
name: getSiteName(window),
|
||||
icon: getSiteIcon(window),
|
||||
}
|
||||
return metadata
|
||||
}
|
||||
|
||||
/**
|
||||
* Error handler for page to extension stream disconnections
|
||||
*
|
||||
@ -199,11 +159,11 @@ function logStreamDisconnectWarning (remoteLabel, err) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if Web3 should be injected
|
||||
* Determines if the provider should be injected
|
||||
*
|
||||
* @returns {boolean} {@code true} if Web3 should be injected
|
||||
* @returns {boolean} {@code true} if the provider should be injected
|
||||
*/
|
||||
function shouldInjectWeb3 () {
|
||||
function shouldInjectProvider () {
|
||||
return doctypeCheck() && suffixCheck() &&
|
||||
documentElementCheck() && !blacklistedDomainCheck()
|
||||
}
|
||||
@ -226,8 +186,8 @@ function doctypeCheck () {
|
||||
* Returns whether or not the extension (suffix) of the current document is prohibited
|
||||
*
|
||||
* This checks {@code window.location.pathname} against a set of file extensions
|
||||
* that should not have web3 injected into them. This check is indifferent of query parameters
|
||||
* in the location.
|
||||
* that we should not inject the provider into. This check is indifferent of
|
||||
* query parameters in the location.
|
||||
*
|
||||
* @returns {boolean} whether or not the extension of the current document is prohibited
|
||||
*/
|
||||
@ -300,46 +260,6 @@ function redirectToPhishingWarning () {
|
||||
})}`
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extracts a name for the site from the DOM
|
||||
*/
|
||||
function getSiteName (window) {
|
||||
const document = window.document
|
||||
const siteName = document.querySelector('head > meta[property="og:site_name"]')
|
||||
if (siteName) {
|
||||
return siteName.content
|
||||
}
|
||||
|
||||
const metaTitle = document.querySelector('head > meta[name="title"]')
|
||||
if (metaTitle) {
|
||||
return metaTitle.content
|
||||
}
|
||||
|
||||
return document.title
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts an icon for the site from the DOM
|
||||
*/
|
||||
function getSiteIcon (window) {
|
||||
const document = window.document
|
||||
|
||||
// Use the site's favicon if it exists
|
||||
const shortcutIcon = document.querySelector('head > link[rel="shortcut icon"]')
|
||||
if (shortcutIcon) {
|
||||
return shortcutIcon.href
|
||||
}
|
||||
|
||||
// Search through available icons in no particular order
|
||||
const icon = Array.from(document.querySelectorAll('head > link[rel="icon"]')).find((icon) => Boolean(icon.href))
|
||||
if (icon) {
|
||||
return icon.href
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that resolves when the DOM is loaded (does not wait for images to load)
|
||||
*/
|
||||
@ -349,5 +269,5 @@ async function domIsReady () {
|
||||
return
|
||||
}
|
||||
// wait for load
|
||||
await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve, { once: true }))
|
||||
return new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve, { once: true }))
|
||||
}
|
||||
|
377
app/scripts/controllers/permissions/index.js
Normal file
377
app/scripts/controllers/permissions/index.js
Normal file
@ -0,0 +1,377 @@
|
||||
const JsonRpcEngine = require('json-rpc-engine')
|
||||
const asMiddleware = require('json-rpc-engine/src/asMiddleware')
|
||||
const ObservableStore = require('obs-store')
|
||||
const RpcCap = require('rpc-cap').CapabilitiesController
|
||||
const { ethErrors } = require('eth-json-rpc-errors')
|
||||
|
||||
const getRestrictedMethods = require('./restrictedMethods')
|
||||
const createMethodMiddleware = require('./methodMiddleware')
|
||||
const createLoggerMiddleware = require('./loggerMiddleware')
|
||||
|
||||
// Methods that do not require any permissions to use:
|
||||
const SAFE_METHODS = require('./permissions-safe-methods.json')
|
||||
|
||||
// some constants
|
||||
const METADATA_STORE_KEY = 'domainMetadata'
|
||||
const LOG_STORE_KEY = 'permissionsLog'
|
||||
const HISTORY_STORE_KEY = 'permissionsHistory'
|
||||
const WALLET_METHOD_PREFIX = 'wallet_'
|
||||
const CAVEAT_NAMES = {
|
||||
exposedAccounts: 'exposedAccounts',
|
||||
}
|
||||
const ACCOUNTS_CHANGED_NOTIFICATION = 'wallet_accountsChanged'
|
||||
|
||||
class PermissionsController {
|
||||
|
||||
constructor (
|
||||
{
|
||||
platform, notifyDomain, notifyAllDomains, keyringController,
|
||||
} = {},
|
||||
restoredPermissions = {},
|
||||
restoredState = {}) {
|
||||
this.store = new ObservableStore({
|
||||
[METADATA_STORE_KEY]: restoredState[METADATA_STORE_KEY] || {},
|
||||
[LOG_STORE_KEY]: restoredState[LOG_STORE_KEY] || [],
|
||||
[HISTORY_STORE_KEY]: restoredState[HISTORY_STORE_KEY] || {},
|
||||
})
|
||||
this.notifyDomain = notifyDomain
|
||||
this.notifyAllDomains = notifyAllDomains
|
||||
this.keyringController = keyringController
|
||||
this._platform = platform
|
||||
this._restrictedMethods = getRestrictedMethods(this)
|
||||
this._initializePermissions(restoredPermissions)
|
||||
}
|
||||
|
||||
createMiddleware ({ origin, extensionId }) {
|
||||
|
||||
if (extensionId) {
|
||||
this.store.updateState({
|
||||
[METADATA_STORE_KEY]: {
|
||||
...this.store.getState()[METADATA_STORE_KEY],
|
||||
[origin]: { extensionId },
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const engine = new JsonRpcEngine()
|
||||
|
||||
engine.push(createLoggerMiddleware({
|
||||
walletPrefix: WALLET_METHOD_PREFIX,
|
||||
restrictedMethods: Object.keys(this._restrictedMethods),
|
||||
ignoreMethods: [ 'wallet_sendDomainMetadata' ],
|
||||
store: this.store,
|
||||
logStoreKey: LOG_STORE_KEY,
|
||||
historyStoreKey: HISTORY_STORE_KEY,
|
||||
}))
|
||||
|
||||
engine.push(createMethodMiddleware({
|
||||
store: this.store,
|
||||
storeKey: METADATA_STORE_KEY,
|
||||
getAccounts: this.getAccounts.bind(this, origin),
|
||||
requestAccountsPermission: this._requestPermissions.bind(
|
||||
this, origin, { eth_accounts: {} }
|
||||
),
|
||||
}))
|
||||
|
||||
engine.push(this.permissions.providerMiddlewareFunction.bind(
|
||||
this.permissions, { origin }
|
||||
))
|
||||
return asMiddleware(engine)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the accounts that should be exposed for the given origin domain,
|
||||
* if any. This method exists for when a trusted context needs to know
|
||||
* which accounts are exposed to a given domain.
|
||||
*
|
||||
* @param {string} origin - The origin string.
|
||||
*/
|
||||
getAccounts (origin) {
|
||||
return new Promise((resolve, _) => {
|
||||
|
||||
const req = { method: 'eth_accounts' }
|
||||
const res = {}
|
||||
this.permissions.providerMiddlewareFunction(
|
||||
{ origin }, req, res, () => {}, _end
|
||||
)
|
||||
|
||||
function _end () {
|
||||
if (res.error || !Array.isArray(res.result)) {
|
||||
resolve([])
|
||||
} else {
|
||||
resolve(res.result)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits a permissions request to rpc-cap. Internal use only.
|
||||
*
|
||||
* @param {string} origin - The origin string.
|
||||
* @param {IRequestedPermissions} permissions - The requested permissions.
|
||||
*/
|
||||
_requestPermissions (origin, permissions) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
const req = { method: 'wallet_requestPermissions', params: [permissions] }
|
||||
const res = {}
|
||||
this.permissions.providerMiddlewareFunction(
|
||||
{ origin }, req, res, () => {}, _end
|
||||
)
|
||||
|
||||
function _end (err) {
|
||||
if (err || res.error) {
|
||||
reject(err || res.error)
|
||||
} else {
|
||||
resolve(res.result)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* User approval callback. The request can fail if the request is invalid.
|
||||
*
|
||||
* @param {object} approved the approved request object
|
||||
*/
|
||||
async approvePermissionsRequest (approved, accounts) {
|
||||
|
||||
const { id } = approved.metadata
|
||||
const approval = this.pendingApprovals[id]
|
||||
|
||||
try {
|
||||
|
||||
// attempt to finalize the request and resolve it
|
||||
await this.finalizePermissionsRequest(approved.permissions, accounts)
|
||||
approval.resolve(approved.permissions)
|
||||
|
||||
} catch (err) {
|
||||
|
||||
// if finalization fails, reject the request
|
||||
approval.reject(ethErrors.rpc.invalidRequest({
|
||||
message: err.message, data: err,
|
||||
}))
|
||||
}
|
||||
|
||||
delete this.pendingApprovals[id]
|
||||
}
|
||||
|
||||
/**
|
||||
* User rejection callback.
|
||||
*
|
||||
* @param {string} id the id of the rejected request
|
||||
*/
|
||||
async rejectPermissionsRequest (id) {
|
||||
const approval = this.pendingApprovals[id]
|
||||
approval.reject(ethErrors.provider.userRejectedRequest())
|
||||
delete this.pendingApprovals[id]
|
||||
}
|
||||
|
||||
/**
|
||||
* Grants the given origin the eth_accounts permission for the given account(s).
|
||||
* This method should ONLY be called as a result of direct user action in the UI,
|
||||
* with the intention of supporting legacy dapps that don't support EIP 1102.
|
||||
*
|
||||
* @param {string} origin - The origin to expose the account(s) to.
|
||||
* @param {Array<string>} accounts - The account(s) to expose.
|
||||
*/
|
||||
async legacyExposeAccounts (origin, accounts) {
|
||||
|
||||
const permissions = {
|
||||
eth_accounts: {},
|
||||
}
|
||||
|
||||
await this.finalizePermissionsRequest(permissions, accounts)
|
||||
|
||||
let error
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
this.permissions.grantNewPermissions(origin, permissions, {}, err => err ? resolve() : reject(err))
|
||||
})
|
||||
} catch (err) {
|
||||
error = err
|
||||
}
|
||||
|
||||
if (error) {
|
||||
if (error.code === 4001) {
|
||||
throw error
|
||||
} else {
|
||||
throw ethErrors.rpc.internal({
|
||||
message: `Failed to add 'eth_accounts' to '${origin}'.`,
|
||||
data: {
|
||||
originalError: error,
|
||||
accounts,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the accounts exposed to the given origin.
|
||||
* Throws error if the update fails.
|
||||
*
|
||||
* @param {string} origin - The origin to change the exposed accounts for.
|
||||
* @param {string[]} accounts - The new account(s) to expose.
|
||||
*/
|
||||
async updateExposedAccounts (origin, accounts) {
|
||||
|
||||
await this.validateExposedAccounts(accounts)
|
||||
|
||||
this.permissions.updateCaveatFor(
|
||||
origin, 'eth_accounts', CAVEAT_NAMES.exposedAccounts, accounts
|
||||
)
|
||||
|
||||
this.notifyDomain(origin, {
|
||||
method: ACCOUNTS_CHANGED_NOTIFICATION,
|
||||
result: accounts,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizes a permissions request.
|
||||
* Throws if request validation fails.
|
||||
*
|
||||
* @param {Object} requestedPermissions - The requested permissions.
|
||||
* @param {string[]} accounts - The accounts to expose, if any.
|
||||
*/
|
||||
async finalizePermissionsRequest (requestedPermissions, accounts) {
|
||||
|
||||
const { eth_accounts: ethAccounts } = requestedPermissions
|
||||
|
||||
if (ethAccounts) {
|
||||
|
||||
await this.validateExposedAccounts(accounts)
|
||||
|
||||
if (!ethAccounts.caveats) {
|
||||
ethAccounts.caveats = []
|
||||
}
|
||||
|
||||
// caveat names are unique, and we will only construct this caveat here
|
||||
ethAccounts.caveats = ethAccounts.caveats.filter(c => (
|
||||
c.name !== CAVEAT_NAMES.exposedAccounts
|
||||
))
|
||||
|
||||
ethAccounts.caveats.push(
|
||||
{
|
||||
type: 'filterResponse',
|
||||
value: accounts,
|
||||
name: CAVEAT_NAMES.exposedAccounts,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate an array of accounts representing accounts to be exposed
|
||||
* to a domain. Throws error if validation fails.
|
||||
*
|
||||
* @param {string[]} accounts - An array of addresses.
|
||||
*/
|
||||
async validateExposedAccounts (accounts) {
|
||||
|
||||
if (!Array.isArray(accounts) || accounts.length === 0) {
|
||||
throw new Error('Must provide non-empty array of account(s).')
|
||||
}
|
||||
|
||||
// assert accounts exist
|
||||
const allAccounts = await this.keyringController.getAccounts()
|
||||
accounts.forEach(acc => {
|
||||
if (!allAccounts.includes(acc)) {
|
||||
throw new Error(`Unknown account: ${acc}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given permissions for the given domain.
|
||||
* @param {object} domains { origin: [permissions] }
|
||||
*/
|
||||
removePermissionsFor (domains) {
|
||||
|
||||
Object.entries(domains).forEach(([origin, perms]) => {
|
||||
|
||||
this.permissions.removePermissionsFor(
|
||||
origin,
|
||||
perms.map(methodName => {
|
||||
|
||||
if (methodName === 'eth_accounts') {
|
||||
this.notifyDomain(
|
||||
origin,
|
||||
{ method: ACCOUNTS_CHANGED_NOTIFICATION, result: [] }
|
||||
)
|
||||
}
|
||||
|
||||
return { parentCapability: methodName }
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all known domains and their related permissions.
|
||||
*/
|
||||
clearPermissions () {
|
||||
this.permissions.clearDomains()
|
||||
this.notifyAllDomains({
|
||||
method: ACCOUNTS_CHANGED_NOTIFICATION,
|
||||
result: [],
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience method for retrieving a login object
|
||||
* or creating a new one if needed.
|
||||
*
|
||||
* @param {string} origin = The origin string representing the domain.
|
||||
*/
|
||||
_initializePermissions (restoredState) {
|
||||
|
||||
// these permission requests are almost certainly stale
|
||||
const initState = { ...restoredState, permissionsRequests: [] }
|
||||
|
||||
this.pendingApprovals = {}
|
||||
|
||||
this.permissions = new RpcCap({
|
||||
|
||||
// Supports passthrough methods:
|
||||
safeMethods: SAFE_METHODS,
|
||||
|
||||
// optional prefix for internal methods
|
||||
methodPrefix: WALLET_METHOD_PREFIX,
|
||||
|
||||
restrictedMethods: this._restrictedMethods,
|
||||
|
||||
/**
|
||||
* A promise-returning callback used to determine whether to approve
|
||||
* permissions requests or not.
|
||||
*
|
||||
* Currently only returns a boolean, but eventually should return any
|
||||
* specific parameters or amendments to the permissions.
|
||||
*
|
||||
* @param {string} req - The internal rpc-cap user request object.
|
||||
*/
|
||||
requestUserApproval: async (req) => {
|
||||
const { metadata: { id } } = req
|
||||
|
||||
this._platform.openExtensionInBrowser('connect')
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pendingApprovals[id] = { resolve, reject }
|
||||
})
|
||||
},
|
||||
}, initState)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
PermissionsController,
|
||||
addInternalMethodPrefix: prefix,
|
||||
CAVEAT_NAMES,
|
||||
}
|
||||
|
||||
|
||||
function prefix (method) {
|
||||
return WALLET_METHOD_PREFIX + method
|
||||
}
|
169
app/scripts/controllers/permissions/loggerMiddleware.js
Normal file
169
app/scripts/controllers/permissions/loggerMiddleware.js
Normal file
@ -0,0 +1,169 @@
|
||||
|
||||
const clone = require('clone')
|
||||
const { isValidAddress } = require('ethereumjs-util')
|
||||
|
||||
const LOG_LIMIT = 100
|
||||
|
||||
/**
|
||||
* Create middleware for logging requests and responses to restricted and
|
||||
* permissions-related methods.
|
||||
*/
|
||||
module.exports = function createLoggerMiddleware ({
|
||||
walletPrefix, restrictedMethods, store, logStoreKey, historyStoreKey, ignoreMethods,
|
||||
}) {
|
||||
return (req, res, next, _end) => {
|
||||
let activityEntry, requestedMethods
|
||||
const { origin, method } = req
|
||||
const isInternal = method.startsWith(walletPrefix)
|
||||
if ((isInternal || restrictedMethods.includes(method)) && !ignoreMethods.includes(method)) {
|
||||
activityEntry = logActivity(req, isInternal)
|
||||
if (method === `${walletPrefix}requestPermissions`) {
|
||||
requestedMethods = getRequestedMethods(req)
|
||||
}
|
||||
} else if (method === 'eth_requestAccounts') {
|
||||
activityEntry = logActivity(req, isInternal)
|
||||
requestedMethods = [ 'eth_accounts' ]
|
||||
} else {
|
||||
return next()
|
||||
}
|
||||
|
||||
next(cb => {
|
||||
const time = Date.now()
|
||||
addResponse(activityEntry, res, time)
|
||||
if (!res.error && requestedMethods) {
|
||||
logHistory(requestedMethods, origin, res.result, time, method === 'eth_requestAccounts')
|
||||
}
|
||||
cb()
|
||||
})
|
||||
}
|
||||
|
||||
function logActivity (request, isInternal) {
|
||||
const activityEntry = {
|
||||
id: request.id,
|
||||
method: request.method,
|
||||
methodType: isInternal ? 'internal' : 'restricted',
|
||||
origin: request.origin,
|
||||
request: cloneObj(request),
|
||||
requestTime: Date.now(),
|
||||
response: null,
|
||||
responseTime: null,
|
||||
success: null,
|
||||
}
|
||||
commitActivity(activityEntry)
|
||||
return activityEntry
|
||||
}
|
||||
|
||||
function addResponse (activityEntry, response, time) {
|
||||
if (!response) {
|
||||
return
|
||||
}
|
||||
activityEntry.response = cloneObj(response)
|
||||
activityEntry.responseTime = time
|
||||
activityEntry.success = !response.error
|
||||
}
|
||||
|
||||
function commitActivity (entry) {
|
||||
const logs = store.getState()[logStoreKey]
|
||||
if (logs.length > LOG_LIMIT - 2) {
|
||||
logs.pop()
|
||||
}
|
||||
logs.push(entry)
|
||||
store.updateState({ [logStoreKey]: logs })
|
||||
}
|
||||
|
||||
function getRequestedMethods (request) {
|
||||
if (
|
||||
!request.params ||
|
||||
typeof request.params[0] !== 'object' ||
|
||||
Array.isArray(request.params[0])
|
||||
) {
|
||||
return null
|
||||
}
|
||||
return Object.keys(request.params[0])
|
||||
}
|
||||
|
||||
function logHistory (requestedMethods, origin, result, time, isEthRequestAccounts) {
|
||||
let accounts, entries
|
||||
if (isEthRequestAccounts) {
|
||||
accounts = result
|
||||
const accountToTimeMap = accounts.reduce((acc, account) => ({ ...acc, [account]: time }), {})
|
||||
entries = { 'eth_accounts': { accounts: accountToTimeMap, lastApproved: time } }
|
||||
} else {
|
||||
entries = result
|
||||
? result
|
||||
.map(perm => {
|
||||
if (perm.parentCapability === 'eth_accounts') {
|
||||
accounts = getAccountsFromPermission(perm)
|
||||
}
|
||||
return perm.parentCapability
|
||||
})
|
||||
.reduce((acc, m) => {
|
||||
if (requestedMethods.includes(m)) {
|
||||
if (m === 'eth_accounts') {
|
||||
const accountToTimeMap = accounts.reduce((acc, account) => ({ ...acc, [account]: time }), {})
|
||||
acc[m] = { lastApproved: time, accounts: accountToTimeMap }
|
||||
} else {
|
||||
acc[m] = { lastApproved: time }
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
: {}
|
||||
}
|
||||
|
||||
if (Object.keys(entries).length > 0) {
|
||||
commitHistory(origin, entries)
|
||||
}
|
||||
}
|
||||
|
||||
function commitHistory (origin, entries) {
|
||||
const history = store.getState()[historyStoreKey] || {}
|
||||
const newOriginHistory = {
|
||||
...history[origin],
|
||||
...entries,
|
||||
}
|
||||
|
||||
if (history[origin] && history[origin]['eth_accounts'] && entries['eth_accounts']) {
|
||||
newOriginHistory['eth_accounts'] = {
|
||||
lastApproved: entries['eth_accounts'].lastApproved,
|
||||
accounts: {
|
||||
...history[origin]['eth_accounts'].accounts,
|
||||
...entries['eth_accounts'].accounts,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
history[origin] = newOriginHistory
|
||||
|
||||
store.updateState({ [historyStoreKey]: history })
|
||||
}
|
||||
}
|
||||
|
||||
// the call to clone is set to disallow circular references
|
||||
// we attempt cloning at a depth of 3 and 2, then return a
|
||||
// shallow copy of the object
|
||||
function cloneObj (obj) {
|
||||
for (let i = 3; i > 1; i--) {
|
||||
try {
|
||||
return clone(obj, false, i)
|
||||
} catch (_) {}
|
||||
}
|
||||
return { ...obj }
|
||||
}
|
||||
|
||||
function getAccountsFromPermission (perm) {
|
||||
if (perm.parentCapability !== 'eth_accounts' || !perm.caveats) {
|
||||
return []
|
||||
}
|
||||
const accounts = {}
|
||||
for (const c of perm.caveats) {
|
||||
if (c.type === 'filterResponse' && Array.isArray(c.value)) {
|
||||
for (const v of c.value) {
|
||||
if (isValidAddress(v)) {
|
||||
accounts[v] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Object.keys(accounts)
|
||||
}
|
90
app/scripts/controllers/permissions/methodMiddleware.js
Normal file
90
app/scripts/controllers/permissions/methodMiddleware.js
Normal file
@ -0,0 +1,90 @@
|
||||
|
||||
const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware')
|
||||
const { ethErrors } = require('eth-json-rpc-errors')
|
||||
|
||||
/**
|
||||
* Create middleware for handling certain methods and preprocessing permissions requests.
|
||||
*/
|
||||
module.exports = function createMethodMiddleware ({
|
||||
store, storeKey, getAccounts, requestAccountsPermission,
|
||||
}) {
|
||||
return createAsyncMiddleware(async (req, res, next) => {
|
||||
|
||||
if (typeof req.method !== 'string') {
|
||||
res.error = ethErrors.rpc.invalidRequest({ data: req})
|
||||
return
|
||||
}
|
||||
|
||||
switch (req.method) {
|
||||
|
||||
// intercepting eth_accounts requests for backwards compatibility,
|
||||
// i.e. return an empty array instead of an error
|
||||
case 'eth_accounts':
|
||||
|
||||
res.result = await getAccounts()
|
||||
return
|
||||
|
||||
case 'eth_requestAccounts':
|
||||
|
||||
// first, just try to get accounts
|
||||
let accounts = await getAccounts()
|
||||
if (accounts.length > 0) {
|
||||
res.result = accounts
|
||||
return
|
||||
}
|
||||
|
||||
// if no accounts, request the accounts permission
|
||||
try {
|
||||
await requestAccountsPermission()
|
||||
} catch (err) {
|
||||
res.error = err
|
||||
return
|
||||
}
|
||||
|
||||
// get the accounts again
|
||||
accounts = await getAccounts()
|
||||
if (accounts.length > 0) {
|
||||
res.result = accounts
|
||||
} else {
|
||||
// this should never happen
|
||||
res.error = ethErrors.rpc.internal(
|
||||
'Accounts unexpectedly unavailable. Please report this bug.'
|
||||
)
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
// custom method for getting metadata from the requesting domain
|
||||
case 'wallet_sendDomainMetadata':
|
||||
|
||||
const storeState = store.getState()[storeKey]
|
||||
const extensionId = storeState[req.origin]
|
||||
? storeState[req.origin].extensionId
|
||||
: undefined
|
||||
|
||||
if (
|
||||
req.domainMetadata &&
|
||||
typeof req.domainMetadata.name === 'string'
|
||||
) {
|
||||
|
||||
store.updateState({
|
||||
[storeKey]: {
|
||||
...storeState,
|
||||
[req.origin]: {
|
||||
extensionId,
|
||||
...req.domainMetadata,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
res.result = true
|
||||
return
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
next()
|
||||
})
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
[
|
||||
"web3_sha3",
|
||||
"net_listening",
|
||||
"net_peerCount",
|
||||
"net_version",
|
||||
"eth_blockNumber",
|
||||
"eth_call",
|
||||
"eth_chainId",
|
||||
"eth_coinbase",
|
||||
"eth_estimateGas",
|
||||
"eth_gasPrice",
|
||||
"eth_getBalance",
|
||||
"eth_getBlockByHash",
|
||||
"eth_getBlockByNumber",
|
||||
"eth_getBlockTransactionCountByHash",
|
||||
"eth_getBlockTransactionCountByNumber",
|
||||
"eth_getCode",
|
||||
"eth_getFilterChanges",
|
||||
"eth_getFilterLogs",
|
||||
"eth_getLogs",
|
||||
"eth_getStorageAt",
|
||||
"eth_getTransactionByBlockHashAndIndex",
|
||||
"eth_getTransactionByBlockNumberAndIndex",
|
||||
"eth_getTransactionByHash",
|
||||
"eth_getTransactionCount",
|
||||
"eth_getTransactionReceipt",
|
||||
"eth_getUncleByBlockHashAndIndex",
|
||||
"eth_getUncleByBlockNumberAndIndex",
|
||||
"eth_getUncleCountByBlockHash",
|
||||
"eth_getUncleCountByBlockNumber",
|
||||
"eth_getWork",
|
||||
"eth_hashrate",
|
||||
"eth_mining",
|
||||
"eth_newBlockFilter",
|
||||
"eth_newFilter",
|
||||
"eth_newPendingTransactionFilter",
|
||||
"eth_protocolVersion",
|
||||
"eth_sendRawTransaction",
|
||||
"eth_sendTransaction",
|
||||
"eth_sign",
|
||||
"personal_sign",
|
||||
"eth_signTypedData",
|
||||
"eth_signTypedData_v1",
|
||||
"eth_signTypedData_v3",
|
||||
"eth_submitHashrate",
|
||||
"eth_submitWork",
|
||||
"eth_syncing",
|
||||
"eth_uninstallFilter"
|
||||
]
|
20
app/scripts/controllers/permissions/restrictedMethods.js
Normal file
20
app/scripts/controllers/permissions/restrictedMethods.js
Normal file
@ -0,0 +1,20 @@
|
||||
|
||||
module.exports = function getRestrictedMethods (permissionsController) {
|
||||
return {
|
||||
|
||||
'eth_accounts': {
|
||||
description: 'View the address of the selected account',
|
||||
method: (_, res, __, end) => {
|
||||
permissionsController.keyringController.getAccounts()
|
||||
.then((accounts) => {
|
||||
res.result = accounts
|
||||
end()
|
||||
})
|
||||
.catch((err) => {
|
||||
res.error = err
|
||||
end(err)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
const ObservableStore = require('obs-store')
|
||||
const { addInternalMethodPrefix } = require('./permissions')
|
||||
const normalizeAddress = require('eth-sig-util').normalize
|
||||
const { isValidAddress, sha3, bufferToHex } = require('ethereumjs-util')
|
||||
const extend = require('xtend')
|
||||
@ -57,7 +58,6 @@ class PreferencesController {
|
||||
useNativeCurrencyAsPrimaryCurrency: true,
|
||||
},
|
||||
completedOnboarding: false,
|
||||
migratedPrivacyMode: false,
|
||||
metaMetricsId: null,
|
||||
metaMetricsSendCount: 0,
|
||||
}, opts.initState)
|
||||
@ -187,7 +187,10 @@ class PreferencesController {
|
||||
* @param {Function} - end
|
||||
*/
|
||||
async requestWatchAsset (req, res, next, end) {
|
||||
if (req.method === 'metamask_watchAsset' || req.method === 'wallet_watchAsset') {
|
||||
if (
|
||||
req.method === 'metamask_watchAsset' ||
|
||||
req.method === addInternalMethodPrefix('watchAsset')
|
||||
) {
|
||||
const { type, options } = req.params
|
||||
switch (type) {
|
||||
case 'ERC20':
|
||||
@ -644,13 +647,6 @@ class PreferencesController {
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
|
||||
unsetMigratedPrivacyMode () {
|
||||
this.store.updateState({
|
||||
migratedPrivacyMode: false,
|
||||
})
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
//
|
||||
// PRIVATE METHODS
|
||||
//
|
||||
|
@ -1,177 +0,0 @@
|
||||
const ObservableStore = require('obs-store')
|
||||
const SafeEventEmitter = require('safe-event-emitter')
|
||||
const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware')
|
||||
const { errors: rpcErrors } = require('eth-json-rpc-errors')
|
||||
|
||||
/**
|
||||
* A controller that services user-approved requests for a full Ethereum provider API
|
||||
*/
|
||||
class ProviderApprovalController extends SafeEventEmitter {
|
||||
/**
|
||||
* Creates a ProviderApprovalController
|
||||
*
|
||||
* @param {Object} [config] - Options to configure controller
|
||||
*/
|
||||
constructor ({ closePopup, initState, keyringController, openPopup, preferencesController } = {}) {
|
||||
super()
|
||||
this.closePopup = closePopup
|
||||
this.keyringController = keyringController
|
||||
this.openPopup = openPopup
|
||||
this.preferencesController = preferencesController
|
||||
this.memStore = new ObservableStore({
|
||||
providerRequests: [],
|
||||
})
|
||||
|
||||
const defaultState = { approvedOrigins: {} }
|
||||
this.store = new ObservableStore(Object.assign(defaultState, initState))
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a user approves access to a full Ethereum provider API
|
||||
*
|
||||
* @param {object} opts - opts for the middleware contains the origin for the middleware
|
||||
*/
|
||||
createMiddleware ({ senderUrl, extensionId, getSiteMetadata }) {
|
||||
return createAsyncMiddleware(async (req, res, next) => {
|
||||
// only handle requestAccounts
|
||||
if (req.method !== 'eth_requestAccounts') {
|
||||
return next()
|
||||
}
|
||||
// if already approved or privacy mode disabled, return early
|
||||
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
|
||||
const origin = senderUrl.hostname
|
||||
if (this.shouldExposeAccounts(origin) && isUnlocked) {
|
||||
res.result = [this.preferencesController.getSelectedAddress()]
|
||||
return
|
||||
}
|
||||
// register the provider request
|
||||
const metadata = { hostname: senderUrl.hostname, origin }
|
||||
if (extensionId) {
|
||||
metadata.extensionId = extensionId
|
||||
} else {
|
||||
const siteMetadata = await getSiteMetadata(origin)
|
||||
Object.assign(metadata, { siteTitle: siteMetadata.name, siteImage: siteMetadata.icon})
|
||||
}
|
||||
this._handleProviderRequest(metadata)
|
||||
// wait for resolution of request
|
||||
const approved = await new Promise(resolve => this.once(`resolvedRequest:${origin}`, ({ approved }) => resolve(approved)))
|
||||
if (approved) {
|
||||
res.result = [this.preferencesController.getSelectedAddress()]
|
||||
} else {
|
||||
throw rpcErrors.eth.userRejectedRequest('User denied account authorization')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} SiteMetadata
|
||||
* @param {string} hostname - The hostname of the site
|
||||
* @param {string} origin - The origin of the site
|
||||
* @param {string} [siteTitle] - The title of the site
|
||||
* @param {string} [siteImage] - The icon for the site
|
||||
* @param {string} [extensionId] - The extension ID of the extension
|
||||
*/
|
||||
/**
|
||||
* Called when a tab requests access to a full Ethereum provider API
|
||||
*
|
||||
* @param {SiteMetadata} siteMetadata - The metadata for the site requesting full provider access
|
||||
*/
|
||||
_handleProviderRequest (siteMetadata) {
|
||||
const { providerRequests } = this.memStore.getState()
|
||||
const origin = siteMetadata.origin
|
||||
this.memStore.updateState({
|
||||
providerRequests: [
|
||||
...providerRequests,
|
||||
siteMetadata,
|
||||
],
|
||||
})
|
||||
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
|
||||
const { approvedOrigins } = this.store.getState()
|
||||
const originAlreadyHandled = approvedOrigins[origin]
|
||||
if (originAlreadyHandled && isUnlocked) {
|
||||
return
|
||||
}
|
||||
this.openPopup && this.openPopup()
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a user approves access to a full Ethereum provider API
|
||||
*
|
||||
* @param {string} origin - origin of the domain that had provider access approved
|
||||
*/
|
||||
approveProviderRequestByOrigin (origin) {
|
||||
if (this.closePopup) {
|
||||
this.closePopup()
|
||||
}
|
||||
|
||||
const { approvedOrigins } = this.store.getState()
|
||||
const { providerRequests } = this.memStore.getState()
|
||||
const providerRequest = providerRequests.find((request) => request.origin === origin)
|
||||
const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin)
|
||||
this.store.updateState({
|
||||
approvedOrigins: {
|
||||
...approvedOrigins,
|
||||
[origin]: {
|
||||
siteTitle: providerRequest ? providerRequest.siteTitle : null,
|
||||
siteImage: providerRequest ? providerRequest.siteImage : null,
|
||||
hostname: providerRequest ? providerRequest.hostname : null,
|
||||
},
|
||||
},
|
||||
})
|
||||
this.memStore.updateState({ providerRequests: remainingProviderRequests })
|
||||
this.emit(`resolvedRequest:${origin}`, { approved: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a tab rejects access to a full Ethereum provider API
|
||||
*
|
||||
* @param {string} origin - origin of the domain that had provider access approved
|
||||
*/
|
||||
rejectProviderRequestByOrigin (origin) {
|
||||
if (this.closePopup) {
|
||||
this.closePopup()
|
||||
}
|
||||
|
||||
const { approvedOrigins } = this.store.getState()
|
||||
const { providerRequests } = this.memStore.getState()
|
||||
const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin)
|
||||
|
||||
// We're cloning and deleting keys here because we don't want to keep unneeded keys
|
||||
const _approvedOrigins = Object.assign({}, approvedOrigins)
|
||||
delete _approvedOrigins[origin]
|
||||
|
||||
this.store.putState({ approvedOrigins: _approvedOrigins })
|
||||
this.memStore.putState({ providerRequests: remainingProviderRequests })
|
||||
this.emit(`resolvedRequest:${origin}`, { approved: false })
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears any approvals for user-approved origins
|
||||
*/
|
||||
clearApprovedOrigins () {
|
||||
this.store.updateState({
|
||||
approvedOrigins: {},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a given origin should have accounts exposed
|
||||
*
|
||||
* @param {string} origin - Domain origin to check for approval status
|
||||
* @returns {boolean} - True if the origin has been approved
|
||||
*/
|
||||
shouldExposeAccounts (origin) {
|
||||
return Boolean(this.store.getState().approvedOrigins[origin])
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a merged state representation
|
||||
* @return {object}
|
||||
* @private
|
||||
*/
|
||||
_getMergedState () {
|
||||
return Object.assign({}, this.memStore.getState(), this.store.getState())
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ProviderApprovalController
|
@ -3,7 +3,7 @@ const ObservableStore = require('obs-store')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const Transaction = require('ethereumjs-tx')
|
||||
const EthQuery = require('ethjs-query')
|
||||
const { errors: rpcErrors } = require('eth-json-rpc-errors')
|
||||
const { ethErrors } = require('eth-json-rpc-errors')
|
||||
const abi = require('human-standard-token-abi')
|
||||
const abiDecoder = require('abi-decoder')
|
||||
abiDecoder.addABI(abi)
|
||||
@ -54,6 +54,7 @@ const { hexToBn, bnToHex, BnMultiplyByFraction } = require('../../lib/util')
|
||||
@param {Object} opts.blockTracker - An instance of eth-blocktracker
|
||||
@param {Object} opts.provider - A network provider.
|
||||
@param {Function} opts.signTransaction - function the signs an ethereumjs-tx
|
||||
@param {object} opts.getPermittedAccounts - get accounts that an origin has permissions for
|
||||
@param {Function} [opts.getGasPrice] - optional gas price calculator
|
||||
@param {Function} opts.signTransaction - ethTx signer that returns a rawTx
|
||||
@param {Number} [opts.txHistoryLimit] - number *optional* for limiting how many transactions are in state
|
||||
@ -66,6 +67,7 @@ class TransactionController extends EventEmitter {
|
||||
this.networkStore = opts.networkStore || new ObservableStore({})
|
||||
this.preferencesStore = opts.preferencesStore || new ObservableStore({})
|
||||
this.provider = opts.provider
|
||||
this.getPermittedAccounts = opts.getPermittedAccounts
|
||||
this.blockTracker = opts.blockTracker
|
||||
this.signEthTx = opts.signTransaction
|
||||
this.getGasPrice = opts.getGasPrice
|
||||
@ -133,7 +135,7 @@ class TransactionController extends EventEmitter {
|
||||
/**
|
||||
Adds a tx to the txlist
|
||||
@emits ${txMeta.id}:unapproved
|
||||
*/
|
||||
*/
|
||||
addTx (txMeta) {
|
||||
this.txStateManager.addTx(txMeta)
|
||||
this.emit(`${txMeta.id}:unapproved`, txMeta)
|
||||
@ -148,18 +150,18 @@ class TransactionController extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
add a new unapproved transaction to the pipeline
|
||||
|
||||
@returns {Promise<string>} the hash of the transaction after being submitted to the network
|
||||
@param txParams {object} - txParams for the transaction
|
||||
@param opts {object} - with the key origin to put the origin on the txMeta
|
||||
* Add a new unapproved transaction to the pipeline
|
||||
*
|
||||
* @returns {Promise<string>} the hash of the transaction after being submitted to the network
|
||||
* @param txParams {object} - txParams for the transaction
|
||||
* @param opts {object} - with the key origin to put the origin on the txMeta
|
||||
*/
|
||||
|
||||
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')
|
||||
|
||||
const initialTxMeta = await this.addUnapprovedTransaction(txParams, opts.origin)
|
||||
|
||||
// listen for tx completion (success, fail)
|
||||
return new Promise((resolve, reject) => {
|
||||
this.txStateManager.once(`${initialTxMeta.id}:finished`, (finishedTxMeta) => {
|
||||
@ -167,30 +169,27 @@ class TransactionController extends EventEmitter {
|
||||
case 'submitted':
|
||||
return resolve(finishedTxMeta.hash)
|
||||
case 'rejected':
|
||||
return reject(cleanErrorStack(rpcErrors.eth.userRejectedRequest('MetaMask Tx Signature: User denied transaction signature.')))
|
||||
return reject(cleanErrorStack(ethErrors.provider.userRejectedRequest('MetaMask Tx Signature: User denied transaction signature.')))
|
||||
case 'failed':
|
||||
return reject(cleanErrorStack(rpcErrors.internal(finishedTxMeta.err.message)))
|
||||
return reject(cleanErrorStack(ethErrors.rpc.internal(finishedTxMeta.err.message)))
|
||||
default:
|
||||
return reject(cleanErrorStack(rpcErrors.internal(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`)))
|
||||
return reject(cleanErrorStack(ethErrors.rpc.internal(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`)))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
Validates and generates a txMeta with defaults and puts it in txStateManager
|
||||
store
|
||||
* Validates and generates a txMeta with defaults and puts it in txStateManager
|
||||
* store.
|
||||
*
|
||||
* @returns {txMeta}
|
||||
*/
|
||||
async addUnapprovedTransaction (txParams, origin) {
|
||||
|
||||
@returns {txMeta}
|
||||
*/
|
||||
|
||||
async addUnapprovedTransaction (txParams) {
|
||||
// validate
|
||||
const normalizedTxParams = txUtils.normalizeTxParams(txParams)
|
||||
// Assert the from address is the selected address
|
||||
if (normalizedTxParams.from !== this.getSelectedAddress()) {
|
||||
throw new Error(`Transaction from address isn't valid for this account`)
|
||||
}
|
||||
|
||||
txUtils.validateTxParams(normalizedTxParams)
|
||||
/**
|
||||
`generateTxMeta` adds the default txMeta properties to the passed object.
|
||||
@ -202,6 +201,30 @@ class TransactionController extends EventEmitter {
|
||||
txParams: normalizedTxParams,
|
||||
type: TRANSACTION_TYPE_STANDARD,
|
||||
})
|
||||
|
||||
if (origin === 'metamask') {
|
||||
// Assert the from address is the selected address
|
||||
if (normalizedTxParams.from !== this.getSelectedAddress()) {
|
||||
throw ethErrors.rpc.internal({
|
||||
message: `Internally initiated transaction is using invalid account.`,
|
||||
data: {
|
||||
origin,
|
||||
fromAddress: normalizedTxParams.from,
|
||||
selectedAddress: this.getSelectedAddress(),
|
||||
},
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Assert that the origin has permissions to initiate transactions from
|
||||
// the specified address
|
||||
const permittedAddresses = await this.getPermittedAccounts(origin)
|
||||
if (!permittedAddresses.includes(normalizedTxParams.from)) {
|
||||
throw ethErrors.provider.unauthorized({ data: { origin }})
|
||||
}
|
||||
}
|
||||
|
||||
txMeta['origin'] = origin
|
||||
|
||||
const { transactionCategory, getCodeResponse } = await this._determineTransactionCategory(txParams)
|
||||
txMeta.transactionCategory = transactionCategory
|
||||
this.addTx(txMeta)
|
||||
@ -222,15 +245,16 @@ class TransactionController extends EventEmitter {
|
||||
txMeta.loadingDefaults = false
|
||||
|
||||
// save txMeta
|
||||
this.txStateManager.updateTx(txMeta)
|
||||
this.txStateManager.updateTx(txMeta, 'Added new unapproved transaction.')
|
||||
|
||||
return txMeta
|
||||
}
|
||||
|
||||
/**
|
||||
adds the tx gas defaults: gas && gasPrice
|
||||
@param txMeta {Object} - the txMeta object
|
||||
@returns {Promise<object>} resolves with txMeta
|
||||
*/
|
||||
* Adds the tx gas defaults: gas && gasPrice
|
||||
* @param txMeta {Object} - the txMeta object
|
||||
* @returns {Promise<object>} resolves with txMeta
|
||||
*/
|
||||
async addTxGasDefaults (txMeta, getCodeResponse) {
|
||||
const txParams = txMeta.txParams
|
||||
// ensure value
|
||||
@ -416,6 +440,7 @@ class TransactionController extends EventEmitter {
|
||||
this.inProcessOfSigning.delete(txId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
adds the chain id and signs the transaction and set the status to signed
|
||||
@param txId {number} - the tx's Id
|
||||
|
@ -1,73 +0,0 @@
|
||||
class StandardProvider {
|
||||
_isConnected
|
||||
_provider
|
||||
|
||||
constructor (provider) {
|
||||
this._provider = provider
|
||||
this._subscribe()
|
||||
// indicate that we've connected, mostly just for standard compliance
|
||||
setTimeout(() => {
|
||||
this._onConnect()
|
||||
})
|
||||
}
|
||||
|
||||
_onClose () {
|
||||
if (this._isConnected === undefined || this._isConnected) {
|
||||
this._provider.emit('close', {
|
||||
code: 1011,
|
||||
reason: 'Network connection error',
|
||||
})
|
||||
}
|
||||
this._isConnected = false
|
||||
}
|
||||
|
||||
_onConnect () {
|
||||
!this._isConnected && this._provider.emit('connect')
|
||||
this._isConnected = true
|
||||
}
|
||||
|
||||
_subscribe () {
|
||||
this._provider.on('data', (error, { method, params }) => {
|
||||
if (!error && method === 'eth_subscription') {
|
||||
this._provider.emit('notification', params.result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate an RPC method call
|
||||
*
|
||||
* @param {string} method - RPC method name to call
|
||||
* @param {string[]} params - Array of RPC method parameters
|
||||
* @returns {Promise<*>} Promise resolving to the result if successful
|
||||
*/
|
||||
send (method, params = []) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
this._provider.sendAsync({ id: 1, jsonrpc: '2.0', method, params }, (error, response) => {
|
||||
error = error || response.error
|
||||
error ? reject(error) : resolve(response)
|
||||
})
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a legacy provider into an EIP-1193-compliant standard provider
|
||||
* @param {Object} provider - Legacy provider to convert
|
||||
* @returns {Object} Standard provider
|
||||
*/
|
||||
export default function createStandardProvider (provider) {
|
||||
const standardProvider = new StandardProvider(provider)
|
||||
const sendLegacy = provider.send
|
||||
provider.send = (methodOrPayload, callbackOrArgs) => {
|
||||
if (typeof methodOrPayload === 'string' && !callbackOrArgs || Array.isArray(callbackOrArgs)) {
|
||||
return standardProvider.send(methodOrPayload, callbackOrArgs)
|
||||
}
|
||||
return sendLegacy.call(provider, methodOrPayload, callbackOrArgs)
|
||||
}
|
||||
return provider
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
/*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...
|
||||
@ -32,17 +31,14 @@ const restoreContextAfterImports = () => {
|
||||
}
|
||||
|
||||
cleanContextForImports()
|
||||
require('web3/dist/web3.min.js')
|
||||
|
||||
const log = require('loglevel')
|
||||
const LocalMessageDuplexStream = require('post-message-stream')
|
||||
const setupDappAutoReload = require('./lib/auto-reload.js')
|
||||
const MetamaskInpageProvider = require('metamask-inpage-provider')
|
||||
const ObjectMultiplex = require('obj-multiplex')
|
||||
const pump = require('pump')
|
||||
const promisify = require('pify')
|
||||
const createStandardProvider = require('./createStandardProvider').default
|
||||
|
||||
let warned = false
|
||||
// TODO:deprecate:2020-01-13
|
||||
require('web3/dist/web3.min.js')
|
||||
const setupDappAutoReload = require('./lib/auto-reload.js')
|
||||
|
||||
restoreContextAfterImports()
|
||||
|
||||
@ -64,106 +60,6 @@ const inpageProvider = new MetamaskInpageProvider(metamaskStream)
|
||||
// set a high max listener count to avoid unnecesary warnings
|
||||
inpageProvider.setMaxListeners(100)
|
||||
|
||||
const pageMux = new ObjectMultiplex()
|
||||
const onboardingStream = pageMux.createStream('onboarding')
|
||||
pump(
|
||||
pageMux,
|
||||
metamaskStream,
|
||||
error => log.error('MetaMask muxed in-page traffic failed', error)
|
||||
)
|
||||
|
||||
let warnedOfAutoRefreshDeprecation = false
|
||||
// augment the provider with its enable method
|
||||
inpageProvider.enable = function ({ force } = {}) {
|
||||
if (
|
||||
!warnedOfAutoRefreshDeprecation &&
|
||||
inpageProvider.autoRefreshOnNetworkChange
|
||||
) {
|
||||
console.warn(`MetaMask: MetaMask will soon stop reloading pages on network change.
|
||||
If you rely upon this behavior, add a 'networkChanged' event handler to trigger the reload manually: https://metamask.github.io/metamask-docs/API_Reference/Ethereum_Provider#ethereum.on(eventname%2C-callback)
|
||||
Set 'ethereum.autoRefreshOnNetworkChange' to 'false' to silence this warning: https://metamask.github.io/metamask-docs/API_Reference/Ethereum_Provider#ethereum.autorefreshonnetworkchange'
|
||||
`)
|
||||
warnedOfAutoRefreshDeprecation = true
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
inpageProvider.sendAsync({ method: 'eth_requestAccounts', params: [force] }, (error, response) => {
|
||||
if (error || response.error) {
|
||||
reject(error || response.error)
|
||||
} else {
|
||||
resolve(response.result)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// give the dapps control of a refresh they can toggle this off on the window.ethereum
|
||||
// this will be default true so it does not break any old apps.
|
||||
inpageProvider.autoRefreshOnNetworkChange = true
|
||||
|
||||
|
||||
// publicConfig isn't populated until we get a message from background.
|
||||
// Using this getter will ensure the state is available
|
||||
const getPublicConfigWhenReady = async () => {
|
||||
const store = inpageProvider.publicConfigStore
|
||||
let state = store.getState()
|
||||
// if state is missing, wait for first update
|
||||
if (!state.networkVersion) {
|
||||
state = await new Promise(resolve => store.once('update', resolve))
|
||||
console.log('new state', state)
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
// add metamask-specific convenience methods
|
||||
inpageProvider._metamask = new Proxy({
|
||||
/**
|
||||
* Synchronously determines if this domain is currently enabled, with a potential false negative if called to soon
|
||||
*
|
||||
* @returns {boolean} - returns true if this domain is currently enabled
|
||||
*/
|
||||
isEnabled: function () {
|
||||
const { isEnabled } = inpageProvider.publicConfigStore.getState()
|
||||
return Boolean(isEnabled)
|
||||
},
|
||||
|
||||
/**
|
||||
* Asynchronously determines if this domain is currently enabled
|
||||
*
|
||||
* @returns {Promise<boolean>} - Promise resolving to true if this domain is currently enabled
|
||||
*/
|
||||
isApproved: async function () {
|
||||
const { isEnabled } = await getPublicConfigWhenReady()
|
||||
return Boolean(isEnabled)
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines if MetaMask is unlocked by the user
|
||||
*
|
||||
* @returns {Promise<boolean>} - Promise resolving to true if MetaMask is currently unlocked
|
||||
*/
|
||||
isUnlocked: async function () {
|
||||
const { isUnlocked } = await getPublicConfigWhenReady()
|
||||
return Boolean(isUnlocked)
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers a page as having initated onboarding. This facilitates MetaMask focusing the initiating tab after onboarding.
|
||||
*
|
||||
* @returns {Promise} - Promise resolving to undefined
|
||||
*/
|
||||
registerOnboarding: async () => {
|
||||
await promisify(onboardingStream.write({ type: 'registerOnboarding' }))
|
||||
},
|
||||
}, {
|
||||
get: function (obj, prop) {
|
||||
!warned && console.warn('Heads up! ethereum._metamask exposes methods that have ' +
|
||||
'not been standardized yet. This means that these methods may not be implemented ' +
|
||||
'in other dapp browsers and may be removed from MetaMask in the future.')
|
||||
warned = true
|
||||
return obj[prop]
|
||||
},
|
||||
})
|
||||
|
||||
// Work around for web3@1.0 deleting the bound `sendAsync` but not the unbound
|
||||
// `sendAsync` method on the prototype, causing `this` reference issues
|
||||
const proxiedInpageProvider = new Proxy(inpageProvider, {
|
||||
@ -172,11 +68,11 @@ const proxiedInpageProvider = new Proxy(inpageProvider, {
|
||||
deleteProperty: () => true,
|
||||
})
|
||||
|
||||
window.ethereum = createStandardProvider(proxiedInpageProvider)
|
||||
//
|
||||
// TODO:deprecate:2020-01-13
|
||||
//
|
||||
|
||||
//
|
||||
// setup web3
|
||||
//
|
||||
|
||||
if (typeof window.web3 !== 'undefined') {
|
||||
throw new Error(`MetaMask detected another web3.
|
||||
@ -192,9 +88,13 @@ web3.setProvider = function () {
|
||||
}
|
||||
log.debug('MetaMask - injected web3')
|
||||
|
||||
setupDappAutoReload(web3, inpageProvider.publicConfigStore)
|
||||
proxiedInpageProvider._web3Ref = web3.eth
|
||||
|
||||
// set web3 defaultAccount
|
||||
inpageProvider.publicConfigStore.subscribe(function (state) {
|
||||
web3.eth.defaultAccount = state.selectedAddress
|
||||
})
|
||||
// setup dapp auto reload AND proxy web3
|
||||
setupDappAutoReload(web3, inpageProvider._publicConfigStore)
|
||||
|
||||
//
|
||||
// end deprecate:2020-01-13
|
||||
//
|
||||
|
||||
window.ethereum = proxiedInpageProvider
|
||||
|
@ -1,3 +1,6 @@
|
||||
|
||||
// TODO:deprecate:2020-01-13
|
||||
|
||||
module.exports = setupDappAutoReload
|
||||
|
||||
function setupDappAutoReload (web3, observable) {
|
||||
@ -13,7 +16,7 @@ function setupDappAutoReload (web3, observable) {
|
||||
lastTimeUsed = Date.now()
|
||||
// show warning once on web3 access
|
||||
if (!hasBeenWarned && key !== 'currentProvider') {
|
||||
console.warn('MetaMask: web3 will be deprecated in the near future in favor of the ethereumProvider\nhttps://medium.com/metamask/4a899ad6e59e')
|
||||
console.warn(`MetaMask: On 2020-01-13, MetaMask will no longer inject web3. For more information, see: https://medium.com/metamask/no-longer-injecting-web3-js-4a899ad6e59e`)
|
||||
hasBeenWarned = true
|
||||
}
|
||||
// return value normally
|
||||
|
@ -1,3 +1,4 @@
|
||||
|
||||
module.exports = createOriginMiddleware
|
||||
|
||||
/**
|
||||
|
@ -1,5 +1,6 @@
|
||||
const extension = require('extensionizer')
|
||||
const log = require('loglevel')
|
||||
const { checkForError } = require('./util')
|
||||
|
||||
/**
|
||||
* A wrapper around the extension's storage local API
|
||||
@ -90,21 +91,3 @@ module.exports = class ExtensionStore {
|
||||
function isEmpty (obj) {
|
||||
return Object.keys(obj).length === 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Error if extension.runtime.lastError is present
|
||||
* this is a workaround for the non-standard error object thats used
|
||||
* @returns {Error}
|
||||
*/
|
||||
function checkForError () {
|
||||
const lastError = extension.runtime.lastError
|
||||
if (!lastError) {
|
||||
return
|
||||
}
|
||||
// if it quacks like an Error, its an Error
|
||||
if (lastError.stack && lastError.message) {
|
||||
return lastError
|
||||
}
|
||||
// repair incomplete error object (eg chromium v77)
|
||||
return new Error(lastError.message)
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
const EventEmitter = require('events')
|
||||
const ObservableStore = require('obs-store')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const { errors: rpcErrors } = require('eth-json-rpc-errors')
|
||||
const { ethErrors } = require('eth-json-rpc-errors')
|
||||
const createId = require('./random-id')
|
||||
|
||||
/**
|
||||
@ -85,7 +85,7 @@ module.exports = class MessageManager extends EventEmitter {
|
||||
case 'signed':
|
||||
return resolve(data.rawSig)
|
||||
case 'rejected':
|
||||
return reject(rpcErrors.eth.userRejectedRequest('MetaMask Message Signature: User denied message signature.'))
|
||||
return reject(ethErrors.provider.userRejectedRequest('MetaMask Message Signature: User denied message signature.'))
|
||||
default:
|
||||
return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
const EventEmitter = require('events')
|
||||
const ObservableStore = require('obs-store')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const { errors: rpcErrors } = require('eth-json-rpc-errors')
|
||||
const { ethErrors } = require('eth-json-rpc-errors')
|
||||
const createId = require('./random-id')
|
||||
const hexRe = /^[0-9A-Fa-f]+$/g
|
||||
const log = require('loglevel')
|
||||
@ -91,7 +91,7 @@ module.exports = class PersonalMessageManager extends EventEmitter {
|
||||
case 'signed':
|
||||
return resolve(data.rawSig)
|
||||
case 'rejected':
|
||||
return reject(rpcErrors.eth.userRejectedRequest('MetaMask Message Signature: User denied message signature.'))
|
||||
return reject(ethErrors.provider.userRejectedRequest('MetaMask Message Signature: User denied message signature.'))
|
||||
default:
|
||||
return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ const EventEmitter = require('events')
|
||||
const ObservableStore = require('obs-store')
|
||||
const createId = require('./random-id')
|
||||
const assert = require('assert')
|
||||
const { errors: rpcErrors } = require('eth-json-rpc-errors')
|
||||
const { ethErrors } = require('eth-json-rpc-errors')
|
||||
const sigUtil = require('eth-sig-util')
|
||||
const log = require('loglevel')
|
||||
const jsonschema = require('jsonschema')
|
||||
@ -81,7 +81,7 @@ module.exports = class TypedMessageManager extends EventEmitter {
|
||||
case 'signed':
|
||||
return resolve(data.rawSig)
|
||||
case 'rejected':
|
||||
return reject(rpcErrors.eth.userRejectedRequest('MetaMask Message Signature: User denied message signature.'))
|
||||
return reject(ethErrors.provider.userRejectedRequest('MetaMask Message Signature: User denied message signature.'))
|
||||
case 'errored':
|
||||
return reject(new Error(`MetaMask Message Signature: ${data.error}`))
|
||||
default:
|
||||
|
@ -1,3 +1,4 @@
|
||||
const extension = require('extensionizer')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const assert = require('assert')
|
||||
const BN = require('bn.js')
|
||||
@ -148,6 +149,32 @@ function getRandomArrayItem (array) {
|
||||
return array[Math.floor((Math.random() * array.length))]
|
||||
}
|
||||
|
||||
function mapObjectValues (object, cb) {
|
||||
const mappedObject = {}
|
||||
Object.keys(object).forEach(key => {
|
||||
mappedObject[key] = cb(key, object[key])
|
||||
})
|
||||
return mappedObject
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Error if extension.runtime.lastError is present
|
||||
* this is a workaround for the non-standard error object thats used
|
||||
* @returns {Error}
|
||||
*/
|
||||
function checkForError () {
|
||||
const lastError = extension.runtime.lastError
|
||||
if (!lastError) {
|
||||
return
|
||||
}
|
||||
// if it quacks like an Error, its an Error
|
||||
if (lastError.stack && lastError.message) {
|
||||
return lastError
|
||||
}
|
||||
// repair incomplete error object (eg chromium v77)
|
||||
return new Error(lastError.message)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
removeListeners,
|
||||
applyListeners,
|
||||
@ -159,4 +186,6 @@ module.exports = {
|
||||
bnToHex,
|
||||
BnMultiplyByFraction,
|
||||
getRandomArrayItem,
|
||||
mapObjectValues,
|
||||
checkForError,
|
||||
}
|
||||
|
@ -8,11 +8,9 @@ const assert = require('assert').strict
|
||||
const EventEmitter = require('events')
|
||||
const pump = require('pump')
|
||||
const Dnode = require('dnode')
|
||||
const pify = require('pify')
|
||||
const extension = require('extensionizer')
|
||||
const ObservableStore = require('obs-store')
|
||||
const ComposableObservableStore = require('./lib/ComposableObservableStore')
|
||||
const createDnodeRemoteGetter = require('./lib/createDnodeRemoteGetter')
|
||||
const asStream = require('obs-store/lib/asStream')
|
||||
const AccountTracker = require('./lib/account-tracker')
|
||||
const RpcEngine = require('json-rpc-engine')
|
||||
@ -20,8 +18,8 @@ const debounce = require('debounce')
|
||||
const createEngineStream = require('json-rpc-middleware-stream/engineStream')
|
||||
const createFilterMiddleware = require('eth-json-rpc-filters')
|
||||
const createSubscriptionManager = require('eth-json-rpc-filters/subscriptionManager')
|
||||
const createOriginMiddleware = require('./lib/createOriginMiddleware')
|
||||
const createLoggerMiddleware = require('./lib/createLoggerMiddleware')
|
||||
const createOriginMiddleware = require('./lib/createOriginMiddleware')
|
||||
const providerAsMiddleware = require('eth-json-rpc-middleware/providerAsMiddleware')
|
||||
const {setupMultiplex} = require('./lib/stream-utils.js')
|
||||
const KeyringController = require('eth-keyring-controller')
|
||||
@ -41,8 +39,8 @@ const TypedMessageManager = require('./lib/typed-message-manager')
|
||||
const TransactionController = require('./controllers/transactions')
|
||||
const TokenRatesController = require('./controllers/token-rates')
|
||||
const DetectTokensController = require('./controllers/detect-tokens')
|
||||
const ProviderApprovalController = require('./controllers/provider-approval')
|
||||
const ABTestController = require('./controllers/ab-test')
|
||||
const { PermissionsController } = require('./controllers/permissions/')
|
||||
const nodeify = require('./lib/nodeify')
|
||||
const accountImporter = require('./account-import-strategies')
|
||||
const getBuyEthUrl = require('./lib/buy-eth-url')
|
||||
@ -58,6 +56,7 @@ const TrezorKeyring = require('eth-trezor-keyring')
|
||||
const LedgerBridgeKeyring = require('eth-ledger-bridge-keyring')
|
||||
const EthQuery = require('eth-query')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const nanoid = require('nanoid')
|
||||
const contractMap = require('eth-contract-metadata')
|
||||
const {
|
||||
AddressBookController,
|
||||
@ -90,16 +89,24 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
// platform-specific api
|
||||
this.platform = opts.platform
|
||||
|
||||
this.getRequestAccountTabIds = opts.getRequestAccountTabIds
|
||||
this.getOpenMetamaskTabsIds = opts.getOpenMetamaskTabsIds
|
||||
|
||||
// observable state store
|
||||
this.store = new ComposableObservableStore(initState)
|
||||
|
||||
// external connections by origin
|
||||
// Do not modify directly. Use the associated methods.
|
||||
this.connections = {}
|
||||
|
||||
// lock to ensure only one vault created at once
|
||||
this.createVaultMutex = new Mutex()
|
||||
|
||||
// network store
|
||||
// next, we will initialize the controllers
|
||||
// controller initializaiton order matters
|
||||
|
||||
this.networkController = new NetworkController(initState.NetworkController)
|
||||
|
||||
// preferences controller
|
||||
this.preferencesController = new PreferencesController({
|
||||
initState: initState.PreferencesController,
|
||||
initLangCode: opts.initLangCode,
|
||||
@ -107,17 +114,14 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
network: this.networkController,
|
||||
})
|
||||
|
||||
// app-state controller
|
||||
this.appStateController = new AppStateController({
|
||||
preferencesStore: this.preferencesController.store,
|
||||
onInactiveTimeout: () => this.setLocked(),
|
||||
initState: initState.AppStateController,
|
||||
})
|
||||
|
||||
// currency controller
|
||||
this.currencyRateController = new CurrencyRateController(undefined, initState.CurrencyController)
|
||||
|
||||
// infura controller
|
||||
this.infuraController = new InfuraController({
|
||||
initState: initState.InfuraController,
|
||||
})
|
||||
@ -125,7 +129,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
this.phishingController = new PhishingController()
|
||||
|
||||
// rpc provider
|
||||
// now we can initialize the RPC provider, which other controllers require
|
||||
this.initializeProvider()
|
||||
this.provider = this.networkController.getProviderAndBlockTracker().provider
|
||||
this.blockTracker = this.networkController.getProviderAndBlockTracker().blockTracker
|
||||
@ -154,7 +158,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
initState: initState.IncomingTransactionsController,
|
||||
})
|
||||
|
||||
// account tracker watches balances, nonces, and any code at their address.
|
||||
// account tracker watches balances, nonces, and any code at their address
|
||||
this.accountTracker = new AccountTracker({
|
||||
provider: this.provider,
|
||||
blockTracker: this.blockTracker,
|
||||
@ -188,7 +192,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
this.accountTracker._updateAccounts()
|
||||
})
|
||||
|
||||
// key mgmt
|
||||
const additionalKeyrings = [TrezorKeyring, LedgerBridgeKeyring]
|
||||
this.keyringController = new KeyringController({
|
||||
keyringTypes: additionalKeyrings,
|
||||
@ -196,16 +199,25 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
getNetwork: this.networkController.getNetworkState.bind(this.networkController),
|
||||
encryptor: opts.encryptor || undefined,
|
||||
})
|
||||
|
||||
this.keyringController.memStore.subscribe((s) => this._onKeyringControllerUpdate(s))
|
||||
|
||||
// detect tokens controller
|
||||
this.permissionsController = new PermissionsController({
|
||||
keyringController: this.keyringController,
|
||||
platform: opts.platform,
|
||||
notifyDomain: this.notifyConnections.bind(this),
|
||||
notifyAllDomains: this.notifyAllConnections.bind(this),
|
||||
}, initState.PermissionsController, initState.PermissionsMetadata)
|
||||
|
||||
this.detectTokensController = new DetectTokensController({
|
||||
preferences: this.preferencesController,
|
||||
network: this.networkController,
|
||||
keyringMemStore: this.keyringController.memStore,
|
||||
})
|
||||
|
||||
this.abTestController = new ABTestController({
|
||||
initState: initState.ABTestController,
|
||||
})
|
||||
|
||||
this.addressBookController = new AddressBookController(undefined, initState.AddressBookController)
|
||||
|
||||
this.threeBoxController = new ThreeBoxController({
|
||||
@ -217,9 +229,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
version,
|
||||
})
|
||||
|
||||
// tx mgmt
|
||||
this.txController = new TransactionController({
|
||||
initState: initState.TransactionController || initState.TransactionManager,
|
||||
getPermittedAccounts: this.permissionsController.getAccounts.bind(this.permissionsController),
|
||||
networkStore: this.networkController.networkStore,
|
||||
preferencesStore: this.preferencesController.store,
|
||||
txHistoryLimit: 40,
|
||||
@ -270,18 +282,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
this.isClientOpenAndUnlocked = memState.isUnlocked && this._isClientOpen
|
||||
})
|
||||
|
||||
this.providerApprovalController = new ProviderApprovalController({
|
||||
closePopup: opts.closePopup,
|
||||
initState: initState.ProviderApprovalController,
|
||||
keyringController: this.keyringController,
|
||||
openPopup: opts.openPopup,
|
||||
preferencesController: this.preferencesController,
|
||||
})
|
||||
|
||||
this.abTestController = new ABTestController({
|
||||
initState: initState.ABTestController,
|
||||
})
|
||||
|
||||
this.store.updateStructure({
|
||||
AppStateController: this.appStateController.store,
|
||||
TransactionController: this.txController.store,
|
||||
@ -294,10 +294,11 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
InfuraController: this.infuraController.store,
|
||||
CachedBalancesController: this.cachedBalancesController.store,
|
||||
OnboardingController: this.onboardingController.store,
|
||||
ProviderApprovalController: this.providerApprovalController.store,
|
||||
IncomingTransactionsController: this.incomingTransactionsController.store,
|
||||
ThreeBoxController: this.threeBoxController.store,
|
||||
ABTestController: this.abTestController.store,
|
||||
PermissionsController: this.permissionsController.permissions,
|
||||
PermissionsMetadata: this.permissionsController.store,
|
||||
ThreeBoxController: this.threeBoxController.store,
|
||||
})
|
||||
|
||||
this.memStore = new ComposableObservableStore(null, {
|
||||
@ -318,11 +319,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
ShapeshiftController: this.shapeshiftController,
|
||||
InfuraController: this.infuraController.store,
|
||||
OnboardingController: this.onboardingController.store,
|
||||
// ProviderApprovalController
|
||||
ProviderApprovalController: this.providerApprovalController.store,
|
||||
ProviderApprovalControllerMemStore: this.providerApprovalController.memStore,
|
||||
IncomingTransactionsController: this.incomingTransactionsController.store,
|
||||
// ThreeBoxController
|
||||
PermissionsController: this.permissionsController.permissions,
|
||||
PermissionsMetadata: this.permissionsController.store,
|
||||
ThreeBoxController: this.threeBoxController.store,
|
||||
ABTestController: this.abTestController.store,
|
||||
// ENS Controller
|
||||
@ -343,20 +342,15 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
version,
|
||||
// account mgmt
|
||||
getAccounts: async ({ origin }) => {
|
||||
// Expose no accounts if this origin has not been approved, preventing
|
||||
// account-requring RPC methods from completing successfully
|
||||
const exposeAccounts = this.providerApprovalController.shouldExposeAccounts(origin)
|
||||
if (origin !== 'metamask' && !exposeAccounts) {
|
||||
return []
|
||||
}
|
||||
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
|
||||
const selectedAddress = this.preferencesController.getSelectedAddress()
|
||||
// only show address if account is unlocked
|
||||
if (isUnlocked && selectedAddress) {
|
||||
return [selectedAddress]
|
||||
} else {
|
||||
return []
|
||||
if (origin === 'metamask') {
|
||||
const selectedAddress = this.preferencesController.getSelectedAddress()
|
||||
return selectedAddress ? [selectedAddress] : []
|
||||
} else if (
|
||||
this.keyringController.memStore.getState().isUnlocked
|
||||
) {
|
||||
return await this.permissionsController.getAccounts(origin)
|
||||
}
|
||||
return [] // changing this is a breaking change
|
||||
},
|
||||
// tx signing
|
||||
processTransaction: this.newUnapprovedTransaction.bind(this),
|
||||
@ -377,7 +371,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
* Constructor helper: initialize a public config store.
|
||||
* This store is used to make some config info available to Dapps synchronously.
|
||||
*/
|
||||
createPublicConfigStore ({ checkIsEnabled }) {
|
||||
createPublicConfigStore () {
|
||||
// subset of state for metamask inpage provider
|
||||
const publicConfigStore = new ObservableStore()
|
||||
|
||||
@ -390,23 +384,16 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
}
|
||||
|
||||
function updatePublicConfigStore (memState) {
|
||||
const publicState = selectPublicState(memState)
|
||||
publicConfigStore.putState(publicState)
|
||||
publicConfigStore.putState(selectPublicState(memState))
|
||||
}
|
||||
|
||||
function selectPublicState ({ isUnlocked, selectedAddress, network, provider }) {
|
||||
const isEnabled = checkIsEnabled()
|
||||
const isReady = isUnlocked && isEnabled
|
||||
const result = {
|
||||
function selectPublicState ({ isUnlocked, network, provider }) {
|
||||
return {
|
||||
isUnlocked,
|
||||
isEnabled,
|
||||
selectedAddress: isReady ? selectedAddress : null,
|
||||
networkVersion: network,
|
||||
chainId: selectChainId({ network, provider }),
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
return publicConfigStore
|
||||
}
|
||||
|
||||
@ -438,13 +425,13 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
*/
|
||||
getApi () {
|
||||
const keyringController = this.keyringController
|
||||
const preferencesController = this.preferencesController
|
||||
const txController = this.txController
|
||||
const networkController = this.networkController
|
||||
const providerApprovalController = this.providerApprovalController
|
||||
const onboardingController = this.onboardingController
|
||||
const permissionsController = this.permissionsController
|
||||
const preferencesController = this.preferencesController
|
||||
const threeBoxController = this.threeBoxController
|
||||
const abTestController = this.abTestController
|
||||
const txController = this.txController
|
||||
|
||||
return {
|
||||
// etc
|
||||
@ -502,7 +489,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
setPreference: nodeify(preferencesController.setPreference, preferencesController),
|
||||
completeOnboarding: nodeify(preferencesController.completeOnboarding, preferencesController),
|
||||
addKnownMethodData: nodeify(preferencesController.addKnownMethodData, preferencesController),
|
||||
unsetMigratedPrivacyMode: nodeify(preferencesController.unsetMigratedPrivacyMode, preferencesController),
|
||||
|
||||
// BlacklistController
|
||||
whitelistPhishingDomain: this.whitelistPhishingDomain.bind(this),
|
||||
@ -550,11 +536,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
signTypedMessage: nodeify(this.signTypedMessage, this),
|
||||
cancelTypedMessage: this.cancelTypedMessage.bind(this),
|
||||
|
||||
// provider approval
|
||||
approveProviderRequestByOrigin: providerApprovalController.approveProviderRequestByOrigin.bind(providerApprovalController),
|
||||
rejectProviderRequestByOrigin: providerApprovalController.rejectProviderRequestByOrigin.bind(providerApprovalController),
|
||||
clearApprovedOrigins: providerApprovalController.clearApprovedOrigins.bind(providerApprovalController),
|
||||
|
||||
// onboarding controller
|
||||
setSeedPhraseBackedUp: nodeify(onboardingController.setSeedPhraseBackedUp, onboardingController),
|
||||
|
||||
@ -568,10 +549,21 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
// a/b test controller
|
||||
getAssignedABTestGroupName: nodeify(abTestController.getAssignedABTestGroupName, abTestController),
|
||||
|
||||
// permissions
|
||||
approvePermissionsRequest: nodeify(permissionsController.approvePermissionsRequest, permissionsController),
|
||||
clearPermissions: permissionsController.clearPermissions.bind(permissionsController),
|
||||
getApprovedAccounts: nodeify(permissionsController.getAccounts.bind(permissionsController)),
|
||||
rejectPermissionsRequest: nodeify(permissionsController.rejectPermissionsRequest, permissionsController),
|
||||
removePermissionsFor: permissionsController.removePermissionsFor.bind(permissionsController),
|
||||
updateExposedAccounts: nodeify(permissionsController.updateExposedAccounts, permissionsController),
|
||||
legacyExposeAccounts: nodeify(permissionsController.legacyExposeAccounts, permissionsController),
|
||||
|
||||
getRequestAccountTabIds: (cb) => cb(null, this.getRequestAccountTabIds()),
|
||||
getOpenMetamaskTabsIds: (cb) => cb(null, this.getOpenMetamaskTabsIds()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//=============================================================================
|
||||
// VAULT / KEYRING RELATED METHODS
|
||||
//=============================================================================
|
||||
@ -1351,10 +1343,10 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
// setup multiplexing
|
||||
const mux = setupMultiplex(connectionStream)
|
||||
// connect features
|
||||
const publicApi = this.setupPublicApi(mux.createStream('publicApi'))
|
||||
this.setupProviderConnection(mux.createStream('provider'), senderUrl, extensionId, publicApi)
|
||||
this.setupPublicConfig(mux.createStream('publicConfig'), senderUrl)
|
||||
|
||||
// messages between inpage and background
|
||||
this.setupProviderConnection(mux.createStream('provider'), senderUrl, extensionId)
|
||||
this.setupPublicConfig(mux.createStream('publicConfig'))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1432,13 +1424,15 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
* resource is an extension.
|
||||
* @param {object} publicApi - The public API
|
||||
*/
|
||||
setupProviderConnection (outStream, senderUrl, extensionId, publicApi) {
|
||||
const getSiteMetadata = publicApi && publicApi.getSiteMetadata
|
||||
const engine = this.setupProviderEngine(senderUrl, extensionId, getSiteMetadata)
|
||||
setupProviderConnection (outStream, senderUrl, extensionId) {
|
||||
const origin = senderUrl.hostname
|
||||
const engine = this.setupProviderEngine(senderUrl, extensionId)
|
||||
|
||||
// setup connection
|
||||
const providerStream = createEngineStream({ engine })
|
||||
|
||||
const connectionId = this.addConnection(origin, { engine })
|
||||
|
||||
pump(
|
||||
outStream,
|
||||
providerStream,
|
||||
@ -1450,6 +1444,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
mid.destroy()
|
||||
}
|
||||
})
|
||||
connectionId && this.removeConnection(origin, connectionId)
|
||||
if (err) {
|
||||
log.error(err)
|
||||
}
|
||||
@ -1460,7 +1455,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
/**
|
||||
* A method for creating a provider that is safely restricted for the requesting domain.
|
||||
**/
|
||||
setupProviderEngine (senderUrl, extensionId, getSiteMetadata) {
|
||||
setupProviderEngine (senderUrl, extensionId) {
|
||||
|
||||
const origin = senderUrl.hostname
|
||||
// setup json rpc engine stack
|
||||
const engine = new RpcEngine()
|
||||
@ -1474,20 +1470,17 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
const subscriptionManager = createSubscriptionManager({ provider, blockTracker })
|
||||
subscriptionManager.events.on('notification', (message) => engine.emit('notification', message))
|
||||
|
||||
// metadata
|
||||
// append origin to each request
|
||||
engine.push(createOriginMiddleware({ origin }))
|
||||
// logging
|
||||
engine.push(createLoggerMiddleware({ origin }))
|
||||
// filter and subscription polyfills
|
||||
engine.push(filterMiddleware)
|
||||
engine.push(subscriptionManager.middleware)
|
||||
// permissions
|
||||
engine.push(this.permissionsController.createMiddleware({ origin, extensionId }))
|
||||
// watch asset
|
||||
engine.push(this.preferencesController.requestWatchAsset.bind(this.preferencesController))
|
||||
// requestAccounts
|
||||
engine.push(this.providerApprovalController.createMiddleware({
|
||||
senderUrl,
|
||||
extensionId,
|
||||
getSiteMetadata,
|
||||
}))
|
||||
// forward to metamask primary provider
|
||||
engine.push(providerAsMiddleware(provider))
|
||||
return engine
|
||||
@ -1502,13 +1495,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
* this is a good candidate for deprecation.
|
||||
*
|
||||
* @param {*} outStream - The stream to provide public config over.
|
||||
* @param {URL} senderUrl - The URL of requesting resource
|
||||
*/
|
||||
setupPublicConfig (outStream, senderUrl) {
|
||||
const configStore = this.createPublicConfigStore({
|
||||
// check the providerApprovalController's approvedOrigins
|
||||
checkIsEnabled: () => this.providerApprovalController.shouldExposeAccounts(senderUrl.hostname),
|
||||
})
|
||||
setupPublicConfig (outStream) {
|
||||
const configStore = this.createPublicConfigStore()
|
||||
const configStream = asStream(configStore)
|
||||
|
||||
pump(
|
||||
@ -1524,6 +1513,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
)
|
||||
}
|
||||
|
||||
// manage external connections
|
||||
|
||||
onMessage (message, sender, sendResponse) {
|
||||
if (!message || !message.type) {
|
||||
log.debug(`Ignoring invalid message: '${JSON.stringify(message)}'`)
|
||||
@ -1562,39 +1553,98 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* A method for providing our public api over a stream.
|
||||
* This includes a method for setting site metadata like title and image
|
||||
* Adds a reference to a connection by origin. Ignores the 'metamask' origin.
|
||||
* Caller must ensure that the returned id is stored such that the reference
|
||||
* can be deleted later.
|
||||
*
|
||||
* @param {*} outStream - The stream to provide the api over.
|
||||
* @param {string} origin - The connection's origin string.
|
||||
* @param {Object} options - Data associated with the connection
|
||||
* @param {Object} options.engine - The connection's JSON Rpc Engine
|
||||
* @returns {string} - The connection's id (so that it can be deleted later)
|
||||
*/
|
||||
setupPublicApi (outStream) {
|
||||
const dnode = Dnode()
|
||||
// connect dnode api to remote connection
|
||||
pump(
|
||||
outStream,
|
||||
dnode,
|
||||
outStream,
|
||||
(err) => {
|
||||
// report any error
|
||||
if (err) {
|
||||
log.error(err)
|
||||
}
|
||||
}
|
||||
)
|
||||
addConnection (origin, { engine }) {
|
||||
|
||||
const getRemote = createDnodeRemoteGetter(dnode)
|
||||
|
||||
const publicApi = {
|
||||
// wrap with an await remote
|
||||
getSiteMetadata: async () => {
|
||||
const remote = await getRemote()
|
||||
return await pify(remote.getSiteMetadata)()
|
||||
},
|
||||
if (origin === 'metamask') {
|
||||
return null
|
||||
}
|
||||
|
||||
return publicApi
|
||||
if (!this.connections[origin]) {
|
||||
this.connections[origin] = {}
|
||||
}
|
||||
|
||||
const id = nanoid()
|
||||
this.connections[origin][id] = {
|
||||
engine,
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a reference to a connection, by origin and id.
|
||||
* Ignores unknown origins.
|
||||
*
|
||||
* @param {string} origin - The connection's origin string.
|
||||
* @param {string} id - The connection's id, as returned from addConnection.
|
||||
*/
|
||||
removeConnection (origin, id) {
|
||||
|
||||
const connections = this.connections[origin]
|
||||
if (!connections) {
|
||||
return
|
||||
}
|
||||
|
||||
delete connections[id]
|
||||
|
||||
if (Object.keys(connections.length === 0)) {
|
||||
delete this.connections[origin]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Causes the RPC engines associated with the connections to the given origin
|
||||
* to emit a notification event with the given payload.
|
||||
* Does nothing if the extension is locked or the origin is unknown.
|
||||
*
|
||||
* @param {string} origin - The connection's origin string.
|
||||
* @param {any} payload - The event payload.
|
||||
*/
|
||||
notifyConnections (origin, payload) {
|
||||
|
||||
const { isUnlocked } = this.getState()
|
||||
const connections = this.connections[origin]
|
||||
if (!isUnlocked || !connections) {
|
||||
return
|
||||
}
|
||||
|
||||
Object.values(connections).forEach(conn => {
|
||||
conn.engine && conn.engine.emit('notification', payload)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Causes the RPC engines associated with all connections to emit a
|
||||
* notification event with the given payload.
|
||||
* Does nothing if the extension is locked.
|
||||
*
|
||||
* @param {any} payload - The event payload.
|
||||
*/
|
||||
notifyAllConnections (payload) {
|
||||
|
||||
const { isUnlocked } = this.getState()
|
||||
if (!isUnlocked) {
|
||||
return
|
||||
}
|
||||
|
||||
Object.values(this.connections).forEach(origin => {
|
||||
Object.values(origin).forEach(conn => {
|
||||
conn.engine && conn.engine.emit('notification', payload)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// handlers
|
||||
|
||||
/**
|
||||
* Handle a KeyringController update
|
||||
* @param {object} state the KC state
|
||||
@ -1623,6 +1673,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
// misc
|
||||
|
||||
/**
|
||||
* A method for emitting the full MetaMask state to all registered listeners.
|
||||
* @private
|
||||
@ -1631,6 +1683,10 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
this.emit('update', this.getState())
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// MISCELLANEOUS
|
||||
//=============================================================================
|
||||
|
||||
/**
|
||||
* A method for estimating a good gas price at recent prices.
|
||||
* Returns the lowest price that would have been included in
|
||||
|
@ -24,4 +24,3 @@ module.exports = {
|
||||
return isApproved && now - createdTime > unacceptableDelay
|
||||
}),
|
||||
}
|
||||
|
||||
|
23
app/scripts/migrations/040.js
Normal file
23
app/scripts/migrations/040.js
Normal file
@ -0,0 +1,23 @@
|
||||
const version = 40
|
||||
const clone = require('clone')
|
||||
|
||||
/**
|
||||
* Site connections are now managed by the PermissionsController, and the
|
||||
* ProviderApprovalController is removed. This migration deletes all
|
||||
* ProviderApprovalController state.
|
||||
*/
|
||||
module.exports = {
|
||||
version,
|
||||
migrate: async function (originalVersionedData) {
|
||||
const versionedData = clone(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
const state = versionedData.data
|
||||
versionedData.data = transformState(state)
|
||||
return versionedData
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
delete state.ProviderApprovalController
|
||||
return state
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
const extension = require('extensionizer')
|
||||
const {createExplorerLink: explorerLink} = require('etherscan-link')
|
||||
|
||||
const {getEnvironmentType} = require('../lib/util')
|
||||
const { getEnvironmentType, checkForError } = require('../lib/util')
|
||||
const {ENVIRONMENT_TYPE_BACKGROUND} = require('../lib/enums')
|
||||
|
||||
class ExtensionPlatform {
|
||||
@ -66,6 +66,58 @@ class ExtensionPlatform {
|
||||
}
|
||||
}
|
||||
|
||||
queryTabs () {
|
||||
return new Promise((resolve, reject) => {
|
||||
extension.tabs.query({}, tabs => {
|
||||
const err = checkForError()
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve(tabs)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
currentTab () {
|
||||
return new Promise((resolve, reject) => {
|
||||
extension.tabs.getCurrent(tab => {
|
||||
const err = checkForError()
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve(tab)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
switchToTab (tabId) {
|
||||
return new Promise((resolve, reject) => {
|
||||
extension.tabs.update(tabId, {highlighted: true}, (tab) => {
|
||||
const err = checkForError()
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve(tab)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
closeTab (tabId) {
|
||||
return new Promise((resolve, reject) => {
|
||||
extension.tabs.remove(tabId, () => {
|
||||
const err = checkForError()
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
_showConfirmedTransaction (txMeta) {
|
||||
|
||||
this._subscribeToNotificationClicked()
|
||||
|
@ -18,8 +18,8 @@ const providerFromEngine = require('eth-json-rpc-middleware/providerFromEngine')
|
||||
/**
|
||||
* returns a provider restricted to the requesting domain
|
||||
**/
|
||||
function incomingConnection (domain, getSiteMetadata) {
|
||||
const engine = metamaskController.setupProviderEngine(domain, getSiteMetadata)
|
||||
function incomingConnection (domain) {
|
||||
const engine = metamaskController.setupProviderEngine(domain)
|
||||
const provider = providerFromEngine(engine)
|
||||
return provider
|
||||
}
|
||||
@ -32,15 +32,6 @@ const filterMiddleware = engine._middleware.filter(mid => mid.name === 'filterMi
|
||||
filterMiddleware.destroy()
|
||||
```
|
||||
|
||||
### getSiteMetadata()
|
||||
|
||||
This method is used to enhance our confirmation screens with images and text representing the requesting domain.
|
||||
|
||||
It should return a promise that resolves with an object with the following properties:
|
||||
|
||||
- `name`: The requesting site's name.
|
||||
- `icon`: A URI representing the site's logo.
|
||||
|
||||
### Using the Streams Interface
|
||||
|
||||
Only use this if you intend to construct the [metamask-inpage-provider](https://github.com/MetaMask/metamask-inpage-provider) over a stream!
|
||||
|
@ -93,7 +93,7 @@
|
||||
"eth-block-tracker": "^4.4.2",
|
||||
"eth-contract-metadata": "^1.11.0",
|
||||
"eth-ens-namehash": "^2.0.8",
|
||||
"eth-json-rpc-errors": "^1.1.0",
|
||||
"eth-json-rpc-errors": "^2.0.0",
|
||||
"eth-json-rpc-filters": "^4.1.1",
|
||||
"eth-json-rpc-infura": "^4.0.1",
|
||||
"eth-json-rpc-middleware": "^4.2.0",
|
||||
@ -116,6 +116,7 @@
|
||||
"ethjs-query": "^0.3.4",
|
||||
"extension-port-stream": "^1.0.0",
|
||||
"extensionizer": "^1.0.1",
|
||||
"fast-deep-equal": "^2.0.1",
|
||||
"fast-json-patch": "^2.0.4",
|
||||
"fuse.js": "^3.2.0",
|
||||
"gaba": "^1.9.0",
|
||||
@ -128,10 +129,11 @@
|
||||
"lodash.shuffle": "^4.2.0",
|
||||
"loglevel": "^1.4.1",
|
||||
"luxon": "^1.8.2",
|
||||
"metamask-inpage-provider": "^3.0.0",
|
||||
"metamask-inpage-provider": "^4.0.2",
|
||||
"metamask-logo": "^2.1.4",
|
||||
"mkdirp": "^0.5.1",
|
||||
"multihashes": "^0.4.12",
|
||||
"nanoid": "^2.1.6",
|
||||
"nonce-tracker": "^1.0.0",
|
||||
"number-to-bn": "^1.7.0",
|
||||
"obj-multiplex": "^1.0.0",
|
||||
@ -173,7 +175,9 @@
|
||||
"redux-thunk": "^2.2.0",
|
||||
"request-promise": "^4.2.1",
|
||||
"reselect": "^3.0.1",
|
||||
"rpc-cap": "^1.0.1",
|
||||
"safe-event-emitter": "^1.0.1",
|
||||
"safe-json-stringify": "^1.2.0",
|
||||
"single-call-balance-checker-abi": "^1.0.0",
|
||||
"string.prototype.matchall": "^3.0.1",
|
||||
"swappable-obj-proxy": "^1.1.0",
|
||||
|
@ -49,6 +49,8 @@ const initialize = () => {
|
||||
const approveTokensWithoutGas = document.getElementById('approveTokensWithoutGas')
|
||||
const signTypedData = document.getElementById('signTypedData')
|
||||
const signTypedDataResults = document.getElementById('signTypedDataResult')
|
||||
const getAccountsButton = document.getElementById('getAccounts')
|
||||
const getAccountsResults = document.getElementById('getAccountsResult')
|
||||
|
||||
const contractStatus = document.getElementById('contractStatus')
|
||||
const tokenAddress = document.getElementById('tokenAddress')
|
||||
@ -317,6 +319,16 @@ const initialize = () => {
|
||||
})
|
||||
})
|
||||
|
||||
getAccountsButton.addEventListener('click', async () => {
|
||||
try {
|
||||
const accounts = await ethereum.send({ method: 'eth_accounts' })
|
||||
getAccountsResults.innerHTML = accounts[0] || 'Not able to get accounts'
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
getAccountsResults.innerHTML = `Error: ${error}`
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
updateButtons()
|
||||
|
@ -42,6 +42,11 @@
|
||||
<button id="approveTokensWithoutGas">Approve Tokens Without Gas</button>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Get Accounts</h2>
|
||||
<button id="getAccounts">eth_accounts</button>
|
||||
<div id="getAccountsResult"></div>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Status</h2>
|
||||
<div>
|
||||
|
@ -113,7 +113,8 @@ describe('MetaMask', function () {
|
||||
let extension
|
||||
let popup
|
||||
let dapp
|
||||
it('switches to a dapp', async () => {
|
||||
|
||||
it('connects to the dapp', async () => {
|
||||
await openNewPage(driver, 'http://127.0.0.1:8080/')
|
||||
await delay(regularDelayMs)
|
||||
|
||||
@ -126,19 +127,27 @@ describe('MetaMask', function () {
|
||||
const windowHandles = await driver.getAllWindowHandles()
|
||||
|
||||
extension = windowHandles[0]
|
||||
popup = await switchToWindowWithTitle(driver, 'MetaMask Notification', windowHandles)
|
||||
dapp = windowHandles.find(handle => handle !== extension && handle !== popup)
|
||||
dapp = await switchToWindowWithTitle(driver, 'E2E Test Dapp', windowHandles)
|
||||
popup = windowHandles.find(handle => handle !== extension && handle !== dapp)
|
||||
|
||||
await driver.switchTo().window(popup)
|
||||
|
||||
await delay(regularDelayMs)
|
||||
const approveButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`))
|
||||
await approveButton.click()
|
||||
|
||||
const accountButton = await findElement(driver, By.css('.permissions-connect-choose-account__account'))
|
||||
await accountButton.click()
|
||||
|
||||
const submitButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Submit')]`))
|
||||
await submitButton.click()
|
||||
|
||||
await waitUntilXWindowHandles(driver, 2)
|
||||
await driver.switchTo().window(dapp)
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
|
||||
it('has the ganache network id within the dapp', async () => {
|
||||
const networkDiv = await findElement(driver, By.css('#network'))
|
||||
await delay(regularDelayMs)
|
||||
assert.equal(await networkDiv.getText(), '5777')
|
||||
})
|
||||
|
||||
|
@ -134,7 +134,7 @@ describe('MetaMask', function () {
|
||||
it('show account details dropdown menu', async () => {
|
||||
await driver.findElement(By.css('div.menu-bar__open-in-browser')).click()
|
||||
const options = await driver.findElements(By.css('div.menu.account-details-dropdown div.menu__item'))
|
||||
assert.equal(options.length, 3) // HD Wallet type does not have to show the Remove Account option
|
||||
assert.equal(options.length, 4) // HD Wallet type does not have to show the Remove Account option
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
})
|
||||
|
@ -432,7 +432,7 @@ describe('MetaMask', function () {
|
||||
await delay(largeDelayMs)
|
||||
})
|
||||
|
||||
it('starts a send transaction inside the dapp', async () => {
|
||||
it('connects the dapp', async () => {
|
||||
await openNewPage(driver, 'http://127.0.0.1:8080/')
|
||||
await delay(regularDelayMs)
|
||||
|
||||
@ -445,15 +445,22 @@ describe('MetaMask', function () {
|
||||
windowHandles = await driver.getAllWindowHandles()
|
||||
|
||||
extension = windowHandles[0]
|
||||
popup = await switchToWindowWithTitle(driver, 'MetaMask Notification', windowHandles)
|
||||
dapp = windowHandles.find(handle => handle !== extension && handle !== popup)
|
||||
dapp = await switchToWindowWithTitle(driver, 'E2E Test Dapp', windowHandles)
|
||||
popup = windowHandles.find(handle => handle !== extension && handle !== dapp)
|
||||
|
||||
await driver.switchTo().window(popup)
|
||||
|
||||
await delay(regularDelayMs)
|
||||
const approveButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`))
|
||||
await approveButton.click()
|
||||
|
||||
const accountButton = await findElement(driver, By.css('.permissions-connect-choose-account__account'))
|
||||
await accountButton.click()
|
||||
|
||||
const submitButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Submit')]`))
|
||||
await submitButton.click()
|
||||
|
||||
await waitUntilXWindowHandles(driver, 2)
|
||||
await driver.switchTo().window(dapp)
|
||||
await delay(2000)
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
|
||||
it('initiates a send from the dapp', async () => {
|
||||
|
201
test/e2e/permissions.spec.js
Normal file
201
test/e2e/permissions.spec.js
Normal file
@ -0,0 +1,201 @@
|
||||
const assert = require('assert')
|
||||
const webdriver = require('selenium-webdriver')
|
||||
const { By, until } = webdriver
|
||||
const {
|
||||
delay,
|
||||
} = require('./func')
|
||||
const {
|
||||
checkBrowserForConsoleErrors,
|
||||
findElement,
|
||||
findElements,
|
||||
openNewPage,
|
||||
verboseReportOnFailure,
|
||||
waitUntilXWindowHandles,
|
||||
switchToWindowWithTitle,
|
||||
setupFetchMocking,
|
||||
prepareExtensionForTesting,
|
||||
} = require('./helpers')
|
||||
const enLocaleMessages = require('../../app/_locales/en/messages.json')
|
||||
|
||||
describe('MetaMask', function () {
|
||||
let driver
|
||||
let publicAddress
|
||||
|
||||
const tinyDelayMs = 200
|
||||
const regularDelayMs = tinyDelayMs * 2
|
||||
const largeDelayMs = regularDelayMs * 2
|
||||
|
||||
this.timeout(0)
|
||||
this.bail(true)
|
||||
|
||||
before(async function () {
|
||||
const result = await prepareExtensionForTesting()
|
||||
driver = result.driver
|
||||
await setupFetchMocking(driver)
|
||||
})
|
||||
|
||||
afterEach(async function () {
|
||||
if (process.env.SELENIUM_BROWSER === 'chrome') {
|
||||
const errors = await 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 verboseReportOnFailure(driver, this.currentTest)
|
||||
}
|
||||
})
|
||||
|
||||
after(async function () {
|
||||
await driver.quit()
|
||||
})
|
||||
|
||||
describe('Going through the first time flow, but skipping the seed phrase challenge', () => {
|
||||
it('clicks the continue button on the welcome screen', async () => {
|
||||
await findElement(driver, By.css('.welcome-page__header'))
|
||||
const welcomeScreenBtn = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`))
|
||||
welcomeScreenBtn.click()
|
||||
await delay(largeDelayMs)
|
||||
})
|
||||
|
||||
it('clicks the "Create New Wallet" option', async () => {
|
||||
const customRpcButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Create a Wallet')]`))
|
||||
customRpcButton.click()
|
||||
await delay(largeDelayMs)
|
||||
})
|
||||
|
||||
it('clicks the "No thanks" option on the metametrics opt-in screen', async () => {
|
||||
const optOutButton = await findElement(driver, By.css('.btn-default'))
|
||||
optOutButton.click()
|
||||
await delay(largeDelayMs)
|
||||
})
|
||||
|
||||
it('accepts a secure password', async () => {
|
||||
const passwordBox = await findElement(driver, By.css('.first-time-flow__form #create-password'))
|
||||
const passwordBoxConfirm = await findElement(driver, By.css('.first-time-flow__form #confirm-password'))
|
||||
const button = await findElement(driver, By.css('.first-time-flow__form button'))
|
||||
|
||||
await passwordBox.sendKeys('correct horse battery staple')
|
||||
await passwordBoxConfirm.sendKeys('correct horse battery staple')
|
||||
|
||||
const tosCheckBox = await findElement(driver, By.css('.first-time-flow__checkbox'))
|
||||
await tosCheckBox.click()
|
||||
|
||||
await button.click()
|
||||
await delay(largeDelayMs)
|
||||
})
|
||||
|
||||
it('skips the seed phrase challenge', async () => {
|
||||
const button = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.remindMeLater.message}')]`))
|
||||
await button.click()
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const detailsButton = await findElement(driver, By.css('.account-details__details-button'))
|
||||
await detailsButton.click()
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
|
||||
it('gets the current accounts address', async () => {
|
||||
const addressInput = await findElement(driver, By.css('.qr-ellip-address'))
|
||||
publicAddress = await addressInput.getAttribute('value')
|
||||
const accountModal = await driver.findElement(By.css('span .modal'))
|
||||
|
||||
await driver.executeScript("document.querySelector('.account-modal-close').click()")
|
||||
|
||||
await driver.wait(until.stalenessOf(accountModal))
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
})
|
||||
|
||||
describe('sets permissions', () => {
|
||||
let extension
|
||||
let popup
|
||||
let dapp
|
||||
|
||||
it('connects to the dapp', async () => {
|
||||
await openNewPage(driver, 'http://127.0.0.1:8080/')
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const connectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`))
|
||||
await connectButton.click()
|
||||
|
||||
await waitUntilXWindowHandles(driver, 3)
|
||||
const windowHandles = await driver.getAllWindowHandles()
|
||||
|
||||
extension = windowHandles[0]
|
||||
dapp = await switchToWindowWithTitle(driver, 'E2E Test Dapp', windowHandles)
|
||||
popup = windowHandles.find(handle => handle !== extension && handle !== dapp)
|
||||
|
||||
await driver.switchTo().window(popup)
|
||||
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const accountButton = await findElement(driver, By.css('.permissions-connect-choose-account__account'))
|
||||
await accountButton.click()
|
||||
|
||||
const submitButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Submit')]`))
|
||||
await submitButton.click()
|
||||
|
||||
await waitUntilXWindowHandles(driver, 2)
|
||||
await driver.switchTo().window(extension)
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
|
||||
it('shows connected sites', async () => {
|
||||
const connectedSites = await findElement(driver, By.xpath(`//button[contains(text(), 'Connected Sites')]`))
|
||||
await connectedSites.click()
|
||||
|
||||
await findElement(driver, By.css('.connected-sites__title'))
|
||||
|
||||
const domains = await findElements(driver, By.css('.connected-sites-list__domain'))
|
||||
assert.equal(domains.length, 1)
|
||||
|
||||
const domainName = await findElement(driver, By.css('.connected-sites-list__domain-name'))
|
||||
assert.equal(await domainName.getText(), 'E2E Test Dapp')
|
||||
|
||||
await domains[0].click()
|
||||
|
||||
const permissionDescription = await findElement(driver, By.css('.connected-sites-list__permission-description'))
|
||||
assert.equal(await permissionDescription.getText(), 'View the address of the selected account')
|
||||
})
|
||||
|
||||
it('can get accounts within the dapp', async () => {
|
||||
await driver.switchTo().window(dapp)
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const getAccountsButton = await findElement(driver, By.xpath(`//button[contains(text(), 'eth_accounts')]`))
|
||||
await getAccountsButton.click()
|
||||
|
||||
const getAccountsResult = await findElement(driver, By.css('#getAccountsResult'))
|
||||
assert.equal((await getAccountsResult.getText()).toLowerCase(), publicAddress.toLowerCase())
|
||||
})
|
||||
|
||||
it('can disconnect all accounts', async () => {
|
||||
await driver.switchTo().window(extension)
|
||||
|
||||
const disconnectAllButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Disconnect All')]`))
|
||||
await disconnectAllButton.click()
|
||||
|
||||
const disconnectModal = await driver.findElement(By.css('span .modal'))
|
||||
|
||||
const disconnectAllModalButton = await findElement(driver, By.css('.disconnect-all-modal .btn-danger'))
|
||||
await disconnectAllModalButton.click()
|
||||
|
||||
await driver.wait(until.stalenessOf(disconnectModal))
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
|
||||
it('can no longer get accounts within the dapp', async () => {
|
||||
await driver.switchTo().window(dapp)
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const getAccountsButton = await findElement(driver, By.xpath(`//button[contains(text(), 'eth_accounts')]`))
|
||||
await getAccountsButton.click()
|
||||
|
||||
const getAccountsResult = await findElement(driver, By.css('#getAccountsResult'))
|
||||
assert.equal(await getAccountsResult.getText(), 'Not able to get accounts')
|
||||
})
|
||||
})
|
||||
})
|
@ -60,6 +60,14 @@ concurrently --kill-others \
|
||||
'yarn dapp' \
|
||||
'sleep 5 && mocha test/e2e/ethereum-on.spec'
|
||||
|
||||
concurrently --kill-others \
|
||||
--names 'ganache,dapp,e2e' \
|
||||
--prefix '[{time}][{name}]' \
|
||||
--success first \
|
||||
'yarn ganache:start' \
|
||||
'yarn dapp' \
|
||||
'sleep 5 && mocha test/e2e/permissions.spec'
|
||||
|
||||
export GANACHE_ARGS="${BASE_GANACHE_ARGS} --deterministic --account=0x250F458997A364988956409A164BA4E16F0F99F916ACDD73ADCD3A1DE30CF8D1,0 --account=0x53CB0AB5226EEBF4D872113D98332C1555DC304443BEE1CF759D15798D3C55A9,25000000000000000000"
|
||||
concurrently --kill-others \
|
||||
--names 'ganache,sendwithprivatedapp,e2e' \
|
||||
|
@ -119,12 +119,13 @@ describe('MetaMask', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('provider listening for events', () => {
|
||||
describe('successfuly signs typed data', () => {
|
||||
let extension
|
||||
let popup
|
||||
let dapp
|
||||
let windowHandles
|
||||
it('switches to a dapp', async () => {
|
||||
|
||||
it('connects to the dapp', async () => {
|
||||
await openNewPage(driver, 'http://127.0.0.1:8080/')
|
||||
await delay(regularDelayMs)
|
||||
|
||||
@ -134,18 +135,24 @@ describe('MetaMask', function () {
|
||||
await delay(regularDelayMs)
|
||||
|
||||
await waitUntilXWindowHandles(driver, 3)
|
||||
windowHandles = await driver.getAllWindowHandles()
|
||||
const windowHandles = await driver.getAllWindowHandles()
|
||||
|
||||
extension = windowHandles[0]
|
||||
popup = await switchToWindowWithTitle(driver, 'MetaMask Notification', windowHandles)
|
||||
dapp = windowHandles.find(handle => handle !== extension && handle !== popup)
|
||||
dapp = await switchToWindowWithTitle(driver, 'E2E Test Dapp', windowHandles)
|
||||
popup = windowHandles.find(handle => handle !== extension && handle !== dapp)
|
||||
|
||||
await driver.switchTo().window(popup)
|
||||
|
||||
await delay(regularDelayMs)
|
||||
const approveButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`))
|
||||
await approveButton.click()
|
||||
|
||||
const accountButton = await findElement(driver, By.css('.permissions-connect-choose-account__account'))
|
||||
await accountButton.click()
|
||||
|
||||
const submitButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Submit')]`))
|
||||
await submitButton.click()
|
||||
|
||||
await waitUntilXWindowHandles(driver, 2)
|
||||
await driver.switchTo().window(dapp)
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
|
||||
it('creates a sign typed data signature request', async () => {
|
||||
@ -153,6 +160,7 @@ describe('MetaMask', function () {
|
||||
await signTypedMessage.click()
|
||||
await delay(largeDelayMs)
|
||||
|
||||
await delay(regularDelayMs)
|
||||
windowHandles = await driver.getAllWindowHandles()
|
||||
await switchToWindowWithTitle(driver, 'MetaMask Notification', windowHandles)
|
||||
await delay(regularDelayMs)
|
||||
|
@ -1,6 +1,7 @@
|
||||
const assert = require('assert')
|
||||
const ObservableStore = require('obs-store')
|
||||
const PreferencesController = require('../../../../app/scripts/controllers/preferences')
|
||||
const { addInternalMethodPrefix } = require('../../../../app/scripts/controllers/permissions')
|
||||
const sinon = require('sinon')
|
||||
|
||||
describe('preferences controller', function () {
|
||||
@ -375,7 +376,7 @@ describe('preferences controller', function () {
|
||||
await preferencesController.requestWatchAsset(req, res, asy.next, asy.end)
|
||||
sandbox.assert.called(stubEnd)
|
||||
sandbox.assert.notCalled(stubNext)
|
||||
req.method = 'wallet_watchAsset'
|
||||
req.method = addInternalMethodPrefix('watchAsset')
|
||||
req.params.type = 'someasset'
|
||||
await preferencesController.requestWatchAsset(req, res, asy.next, asy.end)
|
||||
sandbox.assert.calledTwice(stubEnd)
|
||||
|
@ -1,330 +0,0 @@
|
||||
const assert = require('assert')
|
||||
const sinon = require('sinon')
|
||||
const ProviderApprovalController = require('../../../../app/scripts/controllers/provider-approval')
|
||||
|
||||
const mockLockedKeyringController = {
|
||||
memStore: {
|
||||
getState: () => ({
|
||||
isUnlocked: false,
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
const mockUnlockedKeyringController = {
|
||||
memStore: {
|
||||
getState: () => ({
|
||||
isUnlocked: true,
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
describe('ProviderApprovalController', () => {
|
||||
describe('#_handleProviderRequest', () => {
|
||||
it('should add a pending provider request when unlocked', () => {
|
||||
const controller = new ProviderApprovalController({
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
const metadata = {
|
||||
hostname: 'https://example.com',
|
||||
origin: 'example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
}
|
||||
|
||||
controller._handleProviderRequest(metadata)
|
||||
assert.deepEqual(controller._getMergedState(), {
|
||||
approvedOrigins: {},
|
||||
providerRequests: [metadata],
|
||||
})
|
||||
})
|
||||
|
||||
it('should add a pending provider request when locked', () => {
|
||||
const controller = new ProviderApprovalController({
|
||||
keyringController: mockLockedKeyringController,
|
||||
})
|
||||
|
||||
const metadata = {
|
||||
hostname: 'https://example.com',
|
||||
origin: 'example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
}
|
||||
controller._handleProviderRequest(metadata)
|
||||
assert.deepEqual(controller._getMergedState(), {
|
||||
approvedOrigins: {},
|
||||
providerRequests: [metadata],
|
||||
})
|
||||
})
|
||||
|
||||
it('should add a 2nd pending provider request when unlocked', () => {
|
||||
const controller = new ProviderApprovalController({
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
const metadata = [{
|
||||
hostname: 'https://example1.com',
|
||||
origin: 'example1.com',
|
||||
siteTitle: 'Example 1',
|
||||
siteImage: 'https://example1.com/logo.svg',
|
||||
}, {
|
||||
hostname: 'https://example2.com',
|
||||
origin: 'example2.com',
|
||||
siteTitle: 'Example 2',
|
||||
siteImage: 'https://example2.com/logo.svg',
|
||||
}]
|
||||
|
||||
controller._handleProviderRequest(metadata[0])
|
||||
controller._handleProviderRequest(metadata[1])
|
||||
assert.deepEqual(controller._getMergedState(), {
|
||||
approvedOrigins: {},
|
||||
providerRequests: metadata,
|
||||
})
|
||||
})
|
||||
|
||||
it('should add a 2nd pending provider request when locked', () => {
|
||||
const controller = new ProviderApprovalController({
|
||||
keyringController: mockLockedKeyringController,
|
||||
})
|
||||
|
||||
const metadata = [{
|
||||
hostname: 'https://example1.com',
|
||||
origin: 'example1.com',
|
||||
siteTitle: 'Example 1',
|
||||
siteImage: 'https://example1.com/logo.svg',
|
||||
}, {
|
||||
hostname: 'https://example2.com',
|
||||
origin: 'example2.com',
|
||||
siteTitle: 'Example 2',
|
||||
siteImage: 'https://example2.com/logo.svg',
|
||||
}]
|
||||
|
||||
controller._handleProviderRequest(metadata[0])
|
||||
controller._handleProviderRequest(metadata[1])
|
||||
assert.deepEqual(controller._getMergedState(), {
|
||||
approvedOrigins: {},
|
||||
providerRequests: metadata,
|
||||
})
|
||||
})
|
||||
|
||||
it('should call openPopup when unlocked and when given', () => {
|
||||
const openPopup = sinon.spy()
|
||||
const controller = new ProviderApprovalController({
|
||||
openPopup,
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
const metadata = {
|
||||
hostname: 'https://example.com',
|
||||
origin: 'example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
}
|
||||
controller._handleProviderRequest(metadata)
|
||||
assert.ok(openPopup.calledOnce)
|
||||
})
|
||||
|
||||
it('should call openPopup when locked and when given', () => {
|
||||
const openPopup = sinon.spy()
|
||||
const controller = new ProviderApprovalController({
|
||||
openPopup,
|
||||
keyringController: mockLockedKeyringController,
|
||||
})
|
||||
|
||||
const metadata = {
|
||||
hostname: 'https://example.com',
|
||||
origin: 'example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
}
|
||||
controller._handleProviderRequest(metadata)
|
||||
assert.ok(openPopup.calledOnce)
|
||||
})
|
||||
|
||||
it('should NOT call openPopup when unlocked and when the domain has already been approved', () => {
|
||||
const openPopup = sinon.spy()
|
||||
const controller = new ProviderApprovalController({
|
||||
openPopup,
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
controller.store.updateState({
|
||||
approvedOrigins: {
|
||||
'example.com': {
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
},
|
||||
},
|
||||
})
|
||||
const metadata = {
|
||||
hostname: 'https://example.com',
|
||||
origin: 'example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
}
|
||||
controller._handleProviderRequest(metadata)
|
||||
assert.ok(openPopup.notCalled)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#approveProviderRequestByOrigin', () => {
|
||||
it('should mark the origin as approved and remove the provider request', () => {
|
||||
const controller = new ProviderApprovalController({
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
const metadata = {
|
||||
hostname: 'https://example.com',
|
||||
origin: 'example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
}
|
||||
controller._handleProviderRequest(metadata)
|
||||
controller.approveProviderRequestByOrigin('example.com')
|
||||
assert.deepEqual(controller._getMergedState(), {
|
||||
providerRequests: [],
|
||||
approvedOrigins: {
|
||||
'example.com': {
|
||||
hostname: 'https://example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should mark the origin as approved and multiple requests for the same domain', () => {
|
||||
const controller = new ProviderApprovalController({
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
const metadata = {
|
||||
hostname: 'https://example.com',
|
||||
origin: 'example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
}
|
||||
controller._handleProviderRequest(metadata)
|
||||
controller._handleProviderRequest(metadata)
|
||||
controller.approveProviderRequestByOrigin('example.com')
|
||||
assert.deepEqual(controller._getMergedState(), {
|
||||
providerRequests: [],
|
||||
approvedOrigins: {
|
||||
'example.com': {
|
||||
hostname: 'https://example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should mark the origin as approved without a provider request', () => {
|
||||
const controller = new ProviderApprovalController({
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
controller.approveProviderRequestByOrigin('example.com')
|
||||
assert.deepEqual(controller._getMergedState(), {
|
||||
providerRequests: [],
|
||||
approvedOrigins: {
|
||||
'example.com': {
|
||||
hostname: null,
|
||||
siteTitle: null,
|
||||
siteImage: null,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#rejectProviderRequestByOrigin', () => {
|
||||
it('should remove the origin from approved', () => {
|
||||
const controller = new ProviderApprovalController({
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
const metadata = {
|
||||
hostname: 'https://example.com',
|
||||
origin: 'example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
}
|
||||
controller._handleProviderRequest(metadata)
|
||||
controller.approveProviderRequestByOrigin('example.com')
|
||||
controller.rejectProviderRequestByOrigin('example.com')
|
||||
assert.deepEqual(controller._getMergedState(), {
|
||||
providerRequests: [],
|
||||
approvedOrigins: {},
|
||||
})
|
||||
})
|
||||
|
||||
it('should reject the origin even without a pending request', () => {
|
||||
const controller = new ProviderApprovalController({
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
controller.rejectProviderRequestByOrigin('example.com')
|
||||
assert.deepEqual(controller._getMergedState(), {
|
||||
providerRequests: [],
|
||||
approvedOrigins: {},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#clearApprovedOrigins', () => {
|
||||
it('should clear the approved origins', () => {
|
||||
const controller = new ProviderApprovalController({
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
const metadata = {
|
||||
hostname: 'https://example.com',
|
||||
origin: 'example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
}
|
||||
controller._handleProviderRequest(metadata)
|
||||
controller.approveProviderRequestByOrigin('example.com')
|
||||
controller.clearApprovedOrigins()
|
||||
assert.deepEqual(controller._getMergedState(), {
|
||||
providerRequests: [],
|
||||
approvedOrigins: {},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#shouldExposeAccounts', () => {
|
||||
it('should return true for an approved origin', () => {
|
||||
const controller = new ProviderApprovalController({
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
const metadata = {
|
||||
hostname: 'https://example.com',
|
||||
origin: 'example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
}
|
||||
controller._handleProviderRequest(metadata)
|
||||
controller.approveProviderRequestByOrigin('example.com')
|
||||
assert.ok(controller.shouldExposeAccounts('example.com'))
|
||||
})
|
||||
|
||||
it('should return false for an origin not yet approved', () => {
|
||||
const controller = new ProviderApprovalController({
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
const metadata = {
|
||||
hostname: 'https://example.com',
|
||||
origin: 'example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
}
|
||||
controller._handleProviderRequest(metadata)
|
||||
controller.approveProviderRequestByOrigin('example.com')
|
||||
assert.ok(!controller.shouldExposeAccounts('bad.website'))
|
||||
})
|
||||
})
|
||||
})
|
@ -48,6 +48,7 @@ describe('Transaction Controller', function () {
|
||||
ethTx.sign(fromAccount.key)
|
||||
resolve()
|
||||
}),
|
||||
getPermittedAccounts: () => {},
|
||||
})
|
||||
txController.nonceTracker.getNonceLock = () => Promise.resolve({ nextNonce: 0, releaseLock: noop })
|
||||
})
|
||||
@ -176,13 +177,15 @@ describe('Transaction Controller', function () {
|
||||
describe('#addUnapprovedTransaction', function () {
|
||||
const selectedAddress = '0x1678a085c290ebd122dc42cba69373b5953b831d'
|
||||
|
||||
let getSelectedAddress
|
||||
let getSelectedAddress, getPermittedAccounts
|
||||
beforeEach(function () {
|
||||
getSelectedAddress = sinon.stub(txController, 'getSelectedAddress').returns(selectedAddress)
|
||||
getPermittedAccounts = sinon.stub(txController, 'getPermittedAccounts').returns([selectedAddress])
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
getSelectedAddress.restore()
|
||||
getPermittedAccounts.restore()
|
||||
})
|
||||
|
||||
it('should add an unapproved transaction and return a valid txMeta', function (done) {
|
||||
|
@ -19,9 +19,11 @@ export default class AccountDetails extends Component {
|
||||
static propTypes = {
|
||||
hideSidebar: PropTypes.func,
|
||||
showAccountDetailModal: PropTypes.func,
|
||||
showConnectedSites: PropTypes.func.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
checksummedAddress: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
history: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
state = {
|
||||
@ -48,6 +50,7 @@ export default class AccountDetails extends Component {
|
||||
const {
|
||||
hideSidebar,
|
||||
showAccountDetailModal,
|
||||
showConnectedSites,
|
||||
label,
|
||||
checksummedAddress,
|
||||
name,
|
||||
@ -65,14 +68,19 @@ export default class AccountDetails extends Component {
|
||||
<div className="account-details__keyring-label allcaps">
|
||||
{label}
|
||||
</div>
|
||||
<div className="flex-column flex-center account-details__name-container" onClick={showAccountDetailModal}>
|
||||
<Identicon diameter={54} address={checksummedAddress} />
|
||||
<div className="flex-column flex-center account-details__name-container">
|
||||
<Identicon diameter={54} address={checksummedAddress} onClick={showAccountDetailModal} />
|
||||
<span className="account-details__account-name">
|
||||
{name}
|
||||
</span>
|
||||
<button className="btn-secondary account-details__details-button">
|
||||
{t('details')}
|
||||
</button>
|
||||
<div className="account-details__details-buttons">
|
||||
<button className="btn-secondary account-details__details-button" onClick={showAccountDetailModal} >
|
||||
{t('details')}
|
||||
</button>
|
||||
<button className="btn-secondary account-details__details-button" onClick={showConnectedSites}>
|
||||
{t('connectedSites')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Tooltip
|
||||
|
@ -1,4 +1,7 @@
|
||||
import { connect } from 'react-redux'
|
||||
import { compose } from 'recompose'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import PropTypes from 'prop-types'
|
||||
import { hideSidebar, showModal } from '../../../store/actions'
|
||||
import AccountDetails from './account-details.component'
|
||||
|
||||
@ -11,4 +14,16 @@ function mapDispatchToProps (dispatch) {
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(AccountDetails)
|
||||
const AccountDetailsContainer = compose(
|
||||
withRouter,
|
||||
connect(null, mapDispatchToProps)
|
||||
)(AccountDetails)
|
||||
|
||||
AccountDetailsContainer.propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
checksummedAddress: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
showConnectedSites: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default AccountDetailsContainer
|
||||
|
@ -31,6 +31,12 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__details-buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 175px;
|
||||
}
|
||||
|
||||
&__details-button {
|
||||
font-size: 10px;
|
||||
border-radius: 17px;
|
||||
|
@ -6,6 +6,7 @@ import { ENVIRONMENT_TYPE_POPUP } from '../../../../../app/scripts/lib/enums'
|
||||
import { getEnvironmentType } from '../../../../../app/scripts/lib/util'
|
||||
import Tooltip from '../../ui/tooltip'
|
||||
import Identicon from '../../ui/identicon'
|
||||
import IconWithFallBack from '../../ui/icon-with-fallback'
|
||||
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
|
||||
import { PRIMARY } from '../../../helpers/constants/common'
|
||||
import {
|
||||
@ -35,6 +36,8 @@ export default class AccountMenu extends PureComponent {
|
||||
showAccountDetail: PropTypes.func,
|
||||
showRemoveAccountConfirmationModal: PropTypes.func,
|
||||
toggleAccountMenu: PropTypes.func,
|
||||
addressConnectedDomainMap: PropTypes.object,
|
||||
originOfCurrentTab: PropTypes.string,
|
||||
}
|
||||
|
||||
state = {
|
||||
@ -57,6 +60,8 @@ export default class AccountMenu extends PureComponent {
|
||||
selectedAddress,
|
||||
keyrings,
|
||||
showAccountDetail,
|
||||
addressConnectedDomainMap,
|
||||
originOfCurrentTab,
|
||||
} = this.props
|
||||
|
||||
const accountOrder = keyrings.reduce((list, keyring) => list.concat(keyring.accounts), [])
|
||||
@ -71,6 +76,8 @@ export default class AccountMenu extends PureComponent {
|
||||
const keyring = keyrings.find(kr => {
|
||||
return kr.accounts.includes(simpleAddress) || kr.accounts.includes(identity.address)
|
||||
})
|
||||
const addressDomains = addressConnectedDomainMap[identity.address] || {}
|
||||
const iconAndNameForOpenDomain = addressDomains[originOfCurrentTab]
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -104,6 +111,14 @@ export default class AccountMenu extends PureComponent {
|
||||
type={PRIMARY}
|
||||
/>
|
||||
</div>
|
||||
{ iconAndNameForOpenDomain
|
||||
? (
|
||||
<div className="account-menu__icon-list">
|
||||
<IconWithFallBack icon={iconAndNameForOpenDomain.icon} name={iconAndNameForOpenDomain.name} />
|
||||
</div>
|
||||
)
|
||||
: null
|
||||
}
|
||||
{ this.renderKeyringType(keyring) }
|
||||
{ this.renderRemoveAccount(keyring, identity) }
|
||||
</div>
|
||||
|
@ -11,7 +11,12 @@ import {
|
||||
showInfoPage,
|
||||
showModal,
|
||||
} from '../../../store/actions'
|
||||
import { getMetaMaskAccounts } from '../../../selectors/selectors'
|
||||
import {
|
||||
getAddressConnectedDomainMap,
|
||||
getMetaMaskAccounts,
|
||||
getOriginOfCurrentTab,
|
||||
} from '../../../selectors/selectors'
|
||||
|
||||
import AccountMenu from './account-menu.component'
|
||||
|
||||
function mapStateToProps (state) {
|
||||
@ -23,6 +28,8 @@ function mapStateToProps (state) {
|
||||
keyrings,
|
||||
identities,
|
||||
accounts: getMetaMaskAccounts(state),
|
||||
addressConnectedDomainMap: getAddressConnectedDomainMap(state),
|
||||
originOfCurrentTab: getOriginOfCurrentTab(state),
|
||||
}
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user