Merge remote-tracking branch 'origin/develop' into sync-master

* origin/develop: (45 commits)
  eth-block-tracker@5.0.1 (#10737)
  Allow 11 characters in symbol for RPC (#10670)
  security - update SES lockdown (#10663)
  build - refactor build system for easier configuration (#10718)
  ci - cache deps before patch-package (#10735)
  Additional swaps network support (#10721)
  Update @metamask/controllers to v6.2.1 (#10701)
  Fix 10562 - Hide the suggested token pane when not on Mainnet or test network (#10702)
  Fix mismatchedChain typo in custom network approval screen (#10723)
  Fix 10706 - Prevent autocomplete from add token input (#10700)
  fix: remove unused `metamask.rpcUrl` from redux state + fix tests to reflect that (#10714)
  rule out empty string for symbol (#10712)
  Removing hard references to 12 word seed phrases in copy (#10704)
  Add MetaMask to list of BIP44 HD path examples (#10703)
  resolve issue with missing template error (#10692)
  Delete setupFetchDebugging.js (#10636)
  Excluding sourcemaps comment in production builds (#10695)
  deps - remove "remotedev-server" (#10687)
  Adding default properties to NetworkForm (#10682)
  make migration more safe (#10689)
  ...
feature/default_network_editable
Mark Stacey 4 years ago
commit 68d3756528
  1. 6
      .eslintrc.js
  2. 1
      .storybook/test-data.js
  3. 9
      app/_locales/am/messages.json
  4. 9
      app/_locales/ar/messages.json
  5. 9
      app/_locales/bg/messages.json
  6. 9
      app/_locales/bn/messages.json
  7. 9
      app/_locales/ca/messages.json
  8. 6
      app/_locales/cs/messages.json
  9. 9
      app/_locales/da/messages.json
  10. 9
      app/_locales/de/messages.json
  11. 9
      app/_locales/el/messages.json
  12. 12
      app/_locales/en/messages.json
  13. 9
      app/_locales/es/messages.json
  14. 9
      app/_locales/es_419/messages.json
  15. 9
      app/_locales/et/messages.json
  16. 9
      app/_locales/fa/messages.json
  17. 9
      app/_locales/fi/messages.json
  18. 9
      app/_locales/fil/messages.json
  19. 9
      app/_locales/fr/messages.json
  20. 9
      app/_locales/he/messages.json
  21. 9
      app/_locales/hi/messages.json
  22. 6
      app/_locales/hn/messages.json
  23. 9
      app/_locales/hr/messages.json
  24. 6
      app/_locales/ht/messages.json
  25. 9
      app/_locales/hu/messages.json
  26. 9
      app/_locales/id/messages.json
  27. 9
      app/_locales/it/messages.json
  28. 9
      app/_locales/ja/messages.json
  29. 9
      app/_locales/kn/messages.json
  30. 9
      app/_locales/ko/messages.json
  31. 9
      app/_locales/lt/messages.json
  32. 9
      app/_locales/lv/messages.json
  33. 9
      app/_locales/ms/messages.json
  34. 6
      app/_locales/nl/messages.json
  35. 6
      app/_locales/no/messages.json
  36. 9
      app/_locales/pl/messages.json
  37. 6
      app/_locales/pt/messages.json
  38. 9
      app/_locales/pt_BR/messages.json
  39. 9
      app/_locales/ro/messages.json
  40. 9
      app/_locales/ru/messages.json
  41. 9
      app/_locales/sk/messages.json
  42. 9
      app/_locales/sl/messages.json
  43. 9
      app/_locales/sr/messages.json
  44. 9
      app/_locales/sv/messages.json
  45. 9
      app/_locales/sw/messages.json
  46. 6
      app/_locales/ta/messages.json
  47. 6
      app/_locales/th/messages.json
  48. 9
      app/_locales/tl/messages.json
  49. 6
      app/_locales/tr/messages.json
  50. 9
      app/_locales/uk/messages.json
  51. 9
      app/_locales/vi/messages.json
  52. 9
      app/_locales/zh_CN/messages.json
  53. 9
      app/_locales/zh_TW/messages.json
  54. BIN
      app/images/bnb.png
  55. 2
      app/scripts/account-import-strategies/account-import-strategies.test.js
  56. 22
      app/scripts/background.js
  57. 4
      app/scripts/controllers/cached-balances.test.js
  58. 8
      app/scripts/controllers/detect-tokens.test.js
  59. 2
      app/scripts/controllers/ens/index.test.js
  60. 293
      app/scripts/controllers/incoming-transactions.js
  61. 902
      app/scripts/controllers/incoming-transactions.test.js
  62. 12
      app/scripts/controllers/metametrics.test.js
  63. 4
      app/scripts/controllers/network/createInfuraClient.js
  64. 4
      app/scripts/controllers/network/createJsonRpcClient.js
  65. 4
      app/scripts/controllers/network/network-controller.test.js
  66. 4
      app/scripts/controllers/network/pending-middleware.test.js
  67. 18
      app/scripts/controllers/permissions/permissions-controller.test.js
  68. 18
      app/scripts/controllers/permissions/permissions-log-controller.test.js
  69. 15
      app/scripts/controllers/permissions/permissions-middleware.test.js
  70. 6
      app/scripts/controllers/permissions/permissionsLog.js
  71. 2
      app/scripts/controllers/permissions/restricted-methods.test.js
  72. 11
      app/scripts/controllers/preferences.js
  73. 25
      app/scripts/controllers/preferences.test.js
  74. 73
      app/scripts/controllers/swaps.js
  75. 25
      app/scripts/controllers/swaps.test.js
  76. 2
      app/scripts/controllers/token-rates-controller.test.js
  77. 63
      app/scripts/controllers/transactions/index.js
  78. 59
      app/scripts/controllers/transactions/index.test.js
  79. 4
      app/scripts/controllers/transactions/lib/tx-state-history-helpers.test.js
  80. 2
      app/scripts/controllers/transactions/lib/util.test.js
  81. 4
      app/scripts/controllers/transactions/pending-tx-tracker.test.js
  82. 4
      app/scripts/controllers/transactions/tx-gas-utils.test.js
  83. 2
      app/scripts/controllers/transactions/tx-state-manager.js
  84. 8
      app/scripts/controllers/transactions/tx-state-manager.test.js
  85. 2
      app/scripts/lib/ComposableObservableStore.test.js
  86. 36
      app/scripts/lib/buy-eth-url.js
  87. 16
      app/scripts/lib/buy-eth-url.test.js
  88. 2
      app/scripts/lib/cleanErrorStack.test.js
  89. 33
      app/scripts/lib/createMetaRPCHandler.js
  90. 61
      app/scripts/lib/createMetaRPCHandler.test.js
  91. 2
      app/scripts/lib/decrypt-message-manager.js
  92. 2
      app/scripts/lib/encryption-public-key-manager.js
  93. 2
      app/scripts/lib/message-manager.js
  94. 2
      app/scripts/lib/message-manager.test.js
  95. 81
      app/scripts/lib/metaRPCClientFactory.js
  96. 88
      app/scripts/lib/metaRPCClientFactory.test.js
  97. 8
      app/scripts/lib/migrator/index.test.js
  98. 2
      app/scripts/lib/nodeify.test.js
  99. 2
      app/scripts/lib/personal-message-manager.js
  100. 2
      app/scripts/lib/personal-message-manager.test.js
  101. Some files were not shown because too many files have changed in this diff Show More

@ -180,7 +180,11 @@ module.exports = {
},
},
{
files: ['development/**/*.js', 'test/e2e/benchmark.js', 'test/helper.js'],
files: [
'development/**/*.js',
'test/e2e/benchmark.js',
'test/helpers/setup-helper.js',
],
rules: {
'node/no-process-exit': 'off',
'node/shebang': 'off',

@ -5,7 +5,6 @@ const state = {
isInitialized: true,
isUnlocked: true,
featureFlags: { sendHexData: true },
rpcUrl: 'https://rawtestrpc.metamask.io/',
identities: {
'0xfdea65c8e26263f6d9a1b5de9555d2931a33b825': {
address: '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825',

@ -505,9 +505,6 @@
"importWallet": {
"message": "ቋት አስመጣ"
},
"importYourExisting": {
"message": "ባለ 12 ቃል የዘር ሐረግን በመጠቀም ነባር ቋትዎን ያስመጡ"
},
"imported": {
"message": "ከውጭ የመጣ",
"description": "status showing that an account has been fully loaded into the keyring"
@ -892,9 +889,6 @@
"secretBackupPhraseWarning": {
"message": "ማስጠንቀቂያ፡ የመጠባበቂያ ምዕራፍዎን በጭራሽ አይግለጹ። ይህን ሐረገ የያዘ ማንኛውም ሰው የእርስዎን Ether እስከወዲያኛው ሊወስደው ይችላል።"
},
"secretPhrase": {
"message": "ካዝናዎን ወደነበረበት ለመመለስ ሚስጥራዊ ባለ አስራ ሁለት ቃል ሐረግዎን ያስገቡ።"
},
"securityAndPrivacy": {
"message": "ደህንነት እና ግላዊነት"
},
@ -934,9 +928,6 @@
"sendAmount": {
"message": "መጠኑን ላክ"
},
"sendETH": {
"message": "ETH ላክ"
},
"sendTokens": {
"message": "ተለዋጭ ስሞችን ላክ"
},

@ -501,9 +501,6 @@
"importWallet": {
"message": "استيراد المحفظة"
},
"importYourExisting": {
"message": "قم باستيراد محفظتك الحالية باستخدام جملة بذرية مكونة من 12 كلمة"
},
"imported": {
"message": "المستوردة",
"description": "status showing that an account has been fully loaded into the keyring"
@ -888,9 +885,6 @@
"secretBackupPhraseWarning": {
"message": "تحذير: لا تكشف مطلقاً عن عبارة الدعم الخاصة بك. يمكن لأي شخص بهذه العبارة أن يستحوذ على الأثير الخاص بك إلى الأبد."
},
"secretPhrase": {
"message": "أدخل جملتك السرية المكونة من اثني عشر كلمة هنا لاستعادة خزنتك."
},
"securityAndPrivacy": {
"message": "الأمن والخصوصية"
},
@ -930,9 +924,6 @@
"sendAmount": {
"message": "إرسال المبلغ"
},
"sendETH": {
"message": "إرسال عملة إيثيريوم"
},
"sendTokens": {
"message": "إرسال عملات رمزية"
},

@ -501,9 +501,6 @@
"importWallet": {
"message": "Импортиране на портфейла"
},
"importYourExisting": {
"message": "Импортирайте съществуващия си портфейл с помощта на фраза зародиш с 12 думи"
},
"imported": {
"message": "Импортирани",
"description": "status showing that an account has been fully loaded into the keyring"
@ -891,9 +888,6 @@
"secretBackupPhraseWarning": {
"message": "ВНИМАНИЕ: Никога не разкривайте резервната си фраза. Всеки с тази фраза може да вземе вашия етер завинаги."
},
"secretPhrase": {
"message": "Въведете своята тайна фраза от дванадесет думи тук, за да възстановите портфейла си."
},
"securityAndPrivacy": {
"message": "Сигурност и поверителност"
},
@ -933,9 +927,6 @@
"sendAmount": {
"message": "Изпратете сумата"
},
"sendETH": {
"message": "Изпрати ETH"
},
"sendTokens": {
"message": "Изпращане на жетони"
},

@ -505,9 +505,6 @@
"importWallet": {
"message": "ওয়ট আমদি করন"
},
"importYourExisting": {
"message": "একটি 12 শবর সড ফজ বযবহর কর আপনর বিযমন ওয়ট আমদি করন"
},
"imported": {
"message": "আমদিত",
"description": "status showing that an account has been fully loaded into the keyring"
@ -895,9 +892,6 @@
"secretBackupPhraseWarning": {
"message": "সতরকত: কখনও আপনর বকআপ ফজ পরকশ করবন ন। এই ফজ পউ আপনর ইথর চিরকর জনয নিিরবন।"
},
"secretPhrase": {
"message": "আপনর ভলট রির করত এখ আপনর গপন ব শবর ফজ লিন।"
},
"securityAndPrivacy": {
"message": "নিপত এবপনয়ত"
},
@ -937,9 +931,6 @@
"sendAmount": {
"message": "প অরথরি"
},
"sendETH": {
"message": "ETH পন"
},
"sendTokens": {
"message": "টনগিন"
},

@ -492,9 +492,6 @@
"importWallet": {
"message": "Importar Moneder"
},
"importYourExisting": {
"message": "Importa el teu moneder utilitzant la frase de recuperació de 12 paraules"
},
"imported": {
"message": "Importats",
"description": "status showing that an account has been fully loaded into the keyring"
@ -873,9 +870,6 @@
"secretBackupPhraseWarning": {
"message": "ATENCIÓ: No divulguis mai la teva frase de recuperació. Qualsevol amb aquesta frase pot utilitzar el teu Ether per sempre."
},
"secretPhrase": {
"message": "Introdueix aquí la teva frase secreta de dotze paraules per a recuperar la teva caixa forta."
},
"securityAndPrivacy": {
"message": "Seguretat i privacitat"
},
@ -915,9 +909,6 @@
"sendAmount": {
"message": "Enviar Quantitat"
},
"sendETH": {
"message": "Envia ETH"
},
"sendTokens": {
"message": "Enviar Fitxes"
},

@ -344,9 +344,6 @@
"searchTokens": {
"message": "Hledat tokeny"
},
"secretPhrase": {
"message": "Zadejte svých 12 slov tajné fráze k obnovení trezoru."
},
"seedPhraseReq": {
"message": "klíčové fráze mají 12 slov"
},
@ -356,9 +353,6 @@
"send": {
"message": "Odeslat"
},
"sendETH": {
"message": "Odeslat ETH"
},
"sendTokens": {
"message": "Odeslat tokeny"
},

@ -498,9 +498,6 @@
"importWallet": {
"message": "Importér pung"
},
"importYourExisting": {
"message": "Importér din eksisterende pung med en 12-ords seedfrase"
},
"imported": {
"message": "Importeret",
"description": "status showing that an account has been fully loaded into the keyring"
@ -876,9 +873,6 @@
"secretBackupPhraseWarning": {
"message": "ADVARSEL: Afslør aldrig din backup-frase. Enhver med denne frase kan tage din Ether for altid."
},
"secretPhrase": {
"message": "Indtast din hemmelige tolv ord lange sætning for at gendanne din vault."
},
"securityAndPrivacy": {
"message": "Sikkerhed & Privatliv"
},
@ -915,9 +909,6 @@
"sendAmount": {
"message": "Send Beløb"
},
"sendETH": {
"message": "Vælg ETH"
},
"sendTokens": {
"message": "Send tokens"
},

@ -493,9 +493,6 @@
"importWallet": {
"message": "Wallet importieren"
},
"importYourExisting": {
"message": "Importieren Sie Ihre bestehende Wallet mit einem 12-Wort-Seed-Schlüssel"
},
"imported": {
"message": "Importiert",
"description": "status showing that an account has been fully loaded into the keyring"
@ -864,9 +861,6 @@
"secretBackupPhraseWarning": {
"message": "WARNUNG: Legen Sie niemals Ihre Sicherungsphrase offen. Mit dieser Phrase kann sich jeder Ihr Ether für immer aneignen."
},
"secretPhrase": {
"message": "Gib die 12 Wörter deiner geheimem Wörterfolge ein um deinen Vault wiederherzustellen."
},
"securityAndPrivacy": {
"message": "Sicherheit & Datenschutz"
},
@ -906,9 +900,6 @@
"sendAmount": {
"message": "Betrag senden"
},
"sendETH": {
"message": "ETH senden"
},
"sendTokens": {
"message": "Token senden"
},

@ -502,9 +502,6 @@
"importWallet": {
"message": "Εισαγωγή Πορτοφολιού"
},
"importYourExisting": {
"message": "Εισαγάγετε το υπάρχον πορτοφόλι σας χρησιμοποιώντας μια φράση φύτρου 12 λέξεων"
},
"imported": {
"message": "Έγινε εισαγωγή",
"description": "status showing that an account has been fully loaded into the keyring"
@ -892,9 +889,6 @@
"secretBackupPhraseWarning": {
"message": "ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Ποτέ μην αποκαλύπτετε την εφεδρική φράση. Όποιος έχει αυτή τη φράση μπορεί να πάρει τα Ether σας για πάντα."
},
"secretPhrase": {
"message": "Εισαγάγετε εδώ τη μυστική φράση δώδεκα λέξεων για να επαναφέρετε το χρηματοκιβώτιό σας."
},
"securityAndPrivacy": {
"message": "Ασφάλεια και Απόρρητο"
},
@ -934,9 +928,6 @@
"sendAmount": {
"message": "Αποστολή Ποσού"
},
"sendETH": {
"message": "Στείλτε ETH"
},
"sendTokens": {
"message": "Στείλτε Tokens"
},

@ -873,7 +873,7 @@
"message": "Import wallet"
},
"importYourExisting": {
"message": "Import your existing wallet using a 12 word seed phrase"
"message": "Import your existing wallet using a seed phrase"
},
"imported": {
"message": "Imported",
@ -1064,7 +1064,7 @@
"message": "MetaMask would like to gather usage data to better understand how our users interact with the extension. This data will be used to continually improve the usability and user experience of our product and the Ethereum ecosystem."
},
"mismatchedChain": {
"message": "This network details for this Chain ID do not match our records. We recommend that you $1 before proceeding.",
"message": "The network details for this chain ID do not match our records. We recommend that you $1 before proceeding.",
"description": "$1 is a clickable link with text defined by the 'mismatchedChainLinkText' key"
},
"mismatchedChainLinkText": {
@ -1455,7 +1455,7 @@
"message": "WARNING: Never disclose your backup phrase. Anyone with this phrase can take your Ether forever."
},
"secretPhrase": {
"message": "Enter your secret twelve word phrase here to restore your vault."
"message": "Enter your secret phrase here to restore your vault."
},
"securityAndPrivacy": {
"message": "Security & Privacy"
@ -1511,9 +1511,6 @@
"sendAmount": {
"message": "Send Amount"
},
"sendETH": {
"message": "Send ETH"
},
"sendSpecifiedTokens": {
"message": "Send $1",
"description": "Symbol of the specified token"
@ -2221,6 +2218,9 @@
"walletSeed": {
"message": "Seed phrase"
},
"walletSeedRestore": {
"message": "Wallet Seed"
},
"web3ShimUsageNotification": {
"message": "We noticed that the current website tried to use the removed window.web3 API. If the site appears to be broken, please click $1 for more information.",
"description": "$1 is a clickable link."

@ -800,9 +800,6 @@
"importWallet": {
"message": "Importar Monedero"
},
"importYourExisting": {
"message": "Importa tu monedero existente usando la frase semilla de 12 palabras"
},
"imported": {
"message": "Importado",
"description": "status showing that an account has been fully loaded into the keyring"
@ -1359,9 +1356,6 @@
"secretBackupPhraseWarning": {
"message": "ADVERTENCIA: Nunca revele su frase de respaldo. Cualquiera con esta frase puede tomar su Ether para siempre."
},
"secretPhrase": {
"message": "Ingresa tu frase de doce (12) palabras para restaurar tu bóveda."
},
"securityAndPrivacy": {
"message": "Seguridad y Privacidad"
},
@ -1413,9 +1407,6 @@
"sendAmount": {
"message": "Enviar cantidad"
},
"sendETH": {
"message": "Enviar ETH"
},
"sendSpecifiedTokens": {
"message": "Enviar $1",
"description": "Symbol of the specified token"

@ -800,9 +800,6 @@
"importWallet": {
"message": "Importar billetera"
},
"importYourExisting": {
"message": "Importa tu billetera existente con una frase semilla de 12 palabras"
},
"imported": {
"message": "Importado",
"description": "status showing that an account has been fully loaded into the keyring"
@ -1359,9 +1356,6 @@
"secretBackupPhraseWarning": {
"message": "ADVERTENCIA: Nunca reveles tu frase de respaldo. Cualquier persona que tenga acceso a esta frase puede llevarse tus Ether permanentemente."
},
"secretPhrase": {
"message": "Ingresa tu frase secreta de doce palabras para restaurar tu almacén."
},
"securityAndPrivacy": {
"message": "Seguridad y privacidad"
},
@ -1413,9 +1407,6 @@
"sendAmount": {
"message": "Enviar monto"
},
"sendETH": {
"message": "Enviar ETH"
},
"sendSpecifiedTokens": {
"message": "Enviar $1",
"description": "Symbol of the specified token"

@ -501,9 +501,6 @@
"importWallet": {
"message": "Importige rahakott"
},
"importYourExisting": {
"message": "Importige 12-sõnalise seemnefraasi abil olemasolev rahakott"
},
"imported": {
"message": "Imporditud",
"description": "status showing that an account has been fully loaded into the keyring"
@ -885,9 +882,6 @@
"secretBackupPhraseWarning": {
"message": "HOIATUS! Ärge avaldage kunagi oma varundusfraasi. Selle fraasiga on võimalik teie eeter igaveseks ära võtta."
},
"secretPhrase": {
"message": "Sisestage hoidla taastamiseks oma salajane 12-sõnaline fraas."
},
"securityAndPrivacy": {
"message": "Turvalisus ja privaatsus"
},
@ -927,9 +921,6 @@
"sendAmount": {
"message": "Saatke kogus"
},
"sendETH": {
"message": "Saada ETH"
},
"sendTokens": {
"message": "Saada lube"
},

@ -505,9 +505,6 @@
"importWallet": {
"message": "وارد سازی کیف"
},
"importYourExisting": {
"message": "کیف موجود تان را با استفاده از عبارت رمزیاب 12 کلمه یی وارد کنید"
},
"imported": {
"message": "وارد شده",
"description": "status showing that an account has been fully loaded into the keyring"
@ -895,9 +892,6 @@
"secretBackupPhraseWarning": {
"message": "هشدار: هرگز عبارت پشتیبان تان را به کسی فاش نسازید. هرکسیکه این عبارت را داشته باشد ایتر شما را برای همیشه خواهد گرفت."
},
"secretPhrase": {
"message": "جهت بازیابی خزانه تان عبارت دوازده کلمه یی تان را اینجا وارد کنید."
},
"securityAndPrivacy": {
"message": "امنیت و حریم خصوصی"
},
@ -937,9 +931,6 @@
"sendAmount": {
"message": "ارسال مبلغ"
},
"sendETH": {
"message": "ارسال ETH"
},
"sendTokens": {
"message": "رمزیاب ها را ارسال کنید"
},

@ -505,9 +505,6 @@
"importWallet": {
"message": "Tuo kukkaro"
},
"importYourExisting": {
"message": "Tuo nykyinen lompakkosi käyttäen 12 sanan \n siemenlausetta"
},
"imported": {
"message": "Tuotu",
"description": "status showing that an account has been fully loaded into the keyring"
@ -892,9 +889,6 @@
"secretBackupPhraseWarning": {
"message": "VAROITUS: älä koskaan kerro varmuuskopiolausettasi kenellekään. Kuka tahansa tämän lauseen omaava voi napata Etherisi pysyvästi."
},
"secretPhrase": {
"message": "Palauta holvisi syöttämällä tähän salainen kahdentoista sanan tekstisi."
},
"securityAndPrivacy": {
"message": "Turva & yksityisyys"
},
@ -934,9 +928,6 @@
"sendAmount": {
"message": "Lähetä summa"
},
"sendETH": {
"message": "Lähetä ETH:iä"
},
"sendTokens": {
"message": "Lähetä tietueita"
},

@ -462,9 +462,6 @@
"importWallet": {
"message": "Mag-import ng Wallet"
},
"importYourExisting": {
"message": "I-import ang kasalukuyan mong wallet gamit ang isang seed phrase na may 12 salita"
},
"imported": {
"message": "Na-import",
"description": "status showing that an account has been fully loaded into the keyring"
@ -807,9 +804,6 @@
"secretBackupPhraseWarning": {
"message": "BABALA: Huwag ibunyag ang iyong backup phrase. Mananakaw ng kahit sinong may ganitong parirala ang iyong Ether at hindi na ito maibabalik."
},
"secretPhrase": {
"message": "Ilagay ang iyong lihim na pariralang may labindalawang salita para ma-restore ang iyong vault."
},
"securityAndPrivacy": {
"message": "Seguridad at Privacy"
},
@ -849,9 +843,6 @@
"sendAmount": {
"message": "Magpadala ng Halaga"
},
"sendETH": {
"message": "Magpadala ng ETH"
},
"sendTokens": {
"message": "Magpadala ng Mga Token"
},

@ -496,9 +496,6 @@
"importWallet": {
"message": "Importer le portefeuille"
},
"importYourExisting": {
"message": "Importez votre portefeuille existant à l'aide d'une phrase mnémotechnique de 12 mots"
},
"imported": {
"message": "Importé",
"description": "status showing that an account has been fully loaded into the keyring"
@ -877,9 +874,6 @@
"secretBackupPhraseWarning": {
"message": "AVERTISSEMENT : ne révélez jamais votre phrase de sauvegarde. N'importe qui avec cette phrase peut voler votre Ether pour toujours."
},
"secretPhrase": {
"message": "Entrez vos 12 mots secrets de votre phrase Seed pour restaurer votre coffre."
},
"securityAndPrivacy": {
"message": "Sécurité et confidentialité"
},
@ -919,9 +913,6 @@
"sendAmount": {
"message": "Envoyer le montant"
},
"sendETH": {
"message": "Envoyer des ETH"
},
"sendTokens": {
"message": "Envoyer des jetons"
},

@ -505,9 +505,6 @@
"importWallet": {
"message": "ייבא ארנק"
},
"importYourExisting": {
"message": "יבא/י את הארנק הקיים שלך באמצעות seed phrase בן 12 מילים"
},
"imported": {
"message": "מיובאות",
"description": "status showing that an account has been fully loaded into the keyring"
@ -889,9 +886,6 @@
"secretBackupPhraseWarning": {
"message": "אזהרה: לעולם אין לחשוף את צירוף הגיבוי שלך. כל מי שברשותו צירוף זה יכול לקחת את האת'ר שלך לצמיתות."
},
"secretPhrase": {
"message": "הזנ/י את הצירוף הסודי שלך של שתים-עשרה המילים כאן כדי לשחזר את הכספת שלך."
},
"securityAndPrivacy": {
"message": "אבטחה ופרטיות"
},
@ -931,9 +925,6 @@
"sendAmount": {
"message": "שלח סכום"
},
"sendETH": {
"message": "שלח/י ETH"
},
"sendTokens": {
"message": "שלח טוקנים"
},

@ -791,9 +791,6 @@
"importWallet": {
"message": "वट आयत कर"
},
"importYourExisting": {
"message": "12 शबद कडफ उपयग करक अपनट क आयत कर"
},
"imported": {
"message": "आयित",
"description": "status showing that an account has been fully loaded into the keyring"
@ -1350,9 +1347,6 @@
"secretBackupPhraseWarning": {
"message": "चवन: कभ अपनकअप वश क न कर। इस वश कथ कई भ आपक Ether क हमिए ल सकत।"
},
"secretPhrase": {
"message": "अपनिनरित करनिए अपनत बरह शबद वश क यह दरज कर।"
},
"securityAndPrivacy": {
"message": "सरक और गपनयत"
},
@ -1404,9 +1398,6 @@
"sendAmount": {
"message": "रि"
},
"sendETH": {
"message": "ETH भ"
},
"sendSpecifiedTokens": {
"message": "$1 भ",
"description": "Symbol of the specified token"

@ -324,9 +324,6 @@
"search": {
"message": "खज कर"
},
"secretPhrase": {
"message": "अपनत बरह शबद वश यह अपनिनरित करनिए दरज कर।"
},
"seedPhraseReq": {
"message": "बज वश 12 शबद ल"
},
@ -336,9 +333,6 @@
"send": {
"message": "भ"
},
"sendETH": {
"message": "भ ETH"
},
"sendTokens": {
"message": "भकन"
},

@ -501,9 +501,6 @@
"importWallet": {
"message": "Uvezi novčanik"
},
"importYourExisting": {
"message": "Uvezite svoj postojeći novčanik početnom rečenicom od 12 riječi"
},
"imported": {
"message": "Uvezeno",
"description": "status showing that an account has been fully loaded into the keyring"
@ -888,9 +885,6 @@
"secretBackupPhraseWarning": {
"message": "UPOZORENJE: nikada ne otkrivajte svoju alternativnu rečenicu. Bilo tko ovom rečenicom može zauvijek preuzeti vaš Ether."
},
"secretPhrase": {
"message": "Ovdje upišite svoju tajnu rečenicu od 12 riječi kako biste obnovili svoj sef."
},
"securityAndPrivacy": {
"message": "Sigurnost i privatnost"
},
@ -930,9 +924,6 @@
"sendAmount": {
"message": "Odaberi iznos"
},
"sendETH": {
"message": "Pošalji ETH"
},
"sendTokens": {
"message": "Pošalji tokene"
},

@ -561,9 +561,6 @@
"searchTokens": {
"message": "Rechèch Tokens"
},
"secretPhrase": {
"message": "Antre fraz sekrè douz mo ou a pou w restore kòf ou a."
},
"seedPhraseReq": {
"message": "Seed fraz yo se 12 long mo"
},
@ -585,9 +582,6 @@
"send": {
"message": "Voye"
},
"sendETH": {
"message": "Voye ETH"
},
"sendTokens": {
"message": "Voye Tokens"
},

@ -501,9 +501,6 @@
"importWallet": {
"message": "Pénztárca importálása"
},
"importYourExisting": {
"message": "Importálja meglévő pénztárcáját a 12 szóból álló seed mondat segítségével"
},
"imported": {
"message": "Importált",
"description": "status showing that an account has been fully loaded into the keyring"
@ -888,9 +885,6 @@
"secretBackupPhraseWarning": {
"message": "FIGYELEM: Senkise se adja meg a biztonsági szakaszát. Ennek tulajdonosa örökre elviheti Ether-jeit."
},
"secretPhrase": {
"message": "Tárolód helyreállításához írd be titkos tizenkét szavas szókapcsolatodat ide."
},
"securityAndPrivacy": {
"message": "Biztonság és adatvédelem"
},
@ -930,9 +924,6 @@
"sendAmount": {
"message": "Összeg küldése"
},
"sendETH": {
"message": "ETH küldése"
},
"sendTokens": {
"message": "Token küldése"
},

@ -791,9 +791,6 @@
"importWallet": {
"message": "Impor dompet"
},
"importYourExisting": {
"message": "Impor dompet Anda yang ada menggunakan frasa pemulihan 12 kata"
},
"imported": {
"message": "Diimpor",
"description": "status showing that an account has been fully loaded into the keyring"
@ -1350,9 +1347,6 @@
"secretBackupPhraseWarning": {
"message": "PERINGATAN: Jangan pernah ungkapkan frasa cadangan Anda. Siapa pun yang memiliki frasa ini dapat mengambil Ether Anda selamanya."
},
"secretPhrase": {
"message": "Masukkan frasa kata dua belas rahasia Anda di sini untuk memulihkan vault Anda."
},
"securityAndPrivacy": {
"message": "Keamanan & Privasi"
},
@ -1404,9 +1398,6 @@
"sendAmount": {
"message": "Kirim Jumlah"
},
"sendETH": {
"message": "Kirim ETH"
},
"sendSpecifiedTokens": {
"message": "Kirim $1",
"description": "Symbol of the specified token"

@ -803,9 +803,6 @@
"importWallet": {
"message": "Importa Portafoglio"
},
"importYourExisting": {
"message": "Importa il tuo portafoglio esistente usando la tua frase seed a 12 parole"
},
"imported": {
"message": "Importato",
"description": "status showing that an account has been fully loaded into the keyring"
@ -1365,9 +1362,6 @@
"secretBackupPhraseWarning": {
"message": "ATTENZIONE: Non dire mai a nessuno questa frase di backup. Chiunque con questa frase può rubare i tuoi Ether per sempre."
},
"secretPhrase": {
"message": "Inserisci la tua frase segreta di dodici parole per ripristinare la cassaforte."
},
"securityAndPrivacy": {
"message": "Sicurezza & Privacy"
},
@ -1419,9 +1413,6 @@
"sendAmount": {
"message": "Invia Importo"
},
"sendETH": {
"message": "Invia ETH"
},
"sendSpecifiedTokens": {
"message": "Invia $1",
"description": "Symbol of the specified token"

@ -800,9 +800,6 @@
"importWallet": {
"message": "ウォレットのインポート"
},
"importYourExisting": {
"message": "12単語のシードフレーズを使用して既存のウォレットをインポートします"
},
"imported": {
"message": "インポート済",
"description": "status showing that an account has been fully loaded into the keyring"
@ -1359,9 +1356,6 @@
"secretBackupPhraseWarning": {
"message": "警告:シードフレーズは絶対に公開しないでください。シードフレーズを使うと、誰でもアカウントからETHを盗み出せます。"
},
"secretPhrase": {
"message": "アカウント情報を復元するには、12単語で構成されたシードフレーズを入力してください。"
},
"securityAndPrivacy": {
"message": "セキュリティとプライバシー"
},
@ -1413,9 +1407,6 @@
"sendAmount": {
"message": "送金額"
},
"sendETH": {
"message": "ETHの送金"
},
"sendSpecifiedTokens": {
"message": "$1 を送る",
"description": "Symbol of the specified token"

@ -505,9 +505,6 @@
"importWallet": {
"message": "ವ ಅನ ಆಮದಿ"
},
"importYourExisting": {
"message": "12 ಪದದ ಸ ಅನ ಬಳಸಿಿಮ ಅಸಿವದಲಿವ ವ ಅನ ಆಮದಿ"
},
"imported": {
"message": "ಆಮದಡಲಿ",
"description": "status showing that an account has been fully loaded into the keyring"
@ -895,9 +892,6 @@
"secretBackupPhraseWarning": {
"message": "ಎಚಚರಿ: ನಿಮ ಬಕಪ ಅನಿ ಬಹಿರಗಪಡಿಸಬಿ. ಈ ಫ ಅನಿವ ಯದರಿಮ ಎಥರ ಅನವತವಿಳಬಹ."
},
"secretPhrase": {
"message": "ನಿಮ ವ ಅನ ಮರಿಸಲಿಮ ರಹಸಯ ಹನರಡ ಪದದ ಫ ಅನ ಇಲಿ ನಮಿಿ."
},
"securityAndPrivacy": {
"message": "ಭದರತ ಮತಯತ"
},
@ -937,9 +931,6 @@
"sendAmount": {
"message": "ಮತವನ ಕಳಿಿ"
},
"sendETH": {
"message": "ETH ಕಳಿಿ"
},
"sendTokens": {
"message": "ಟಕನಗಳನ ಕಳಿಿ"
},

@ -791,9 +791,6 @@
"importWallet": {
"message": "지갑 가져오기"
},
"importYourExisting": {
"message": "12단어 시드 구문을 사용하여 지갑 가져오기"
},
"imported": {
"message": "가져옴",
"description": "status showing that an account has been fully loaded into the keyring"
@ -1347,9 +1344,6 @@
"secretBackupPhraseWarning": {
"message": "경고: 백업 구문은 절대로 공개하지 마세요. 이 구문이 있는 사람은 귀하의 Ether를 영원히 소유할 수 있습니다."
},
"secretPhrase": {
"message": "금고를 복구하려면 비밀 12단어 구문을 여기에 입력하세요."
},
"securityAndPrivacy": {
"message": "보안 및 개인정보 보호"
},
@ -1401,9 +1395,6 @@
"sendAmount": {
"message": "금액 보내기"
},
"sendETH": {
"message": "ETH 보내기"
},
"sendSpecifiedTokens": {
"message": "$1 보내기",
"description": "Symbol of the specified token"

@ -505,9 +505,6 @@
"importWallet": {
"message": "Importuoti slaptažodinę"
},
"importYourExisting": {
"message": "Importuoti turimą piniginę naudojant 12 žodžių atkūrimo frazę"
},
"imported": {
"message": "Importuota",
"description": "status showing that an account has been fully loaded into the keyring"
@ -895,9 +892,6 @@
"secretBackupPhraseWarning": {
"message": "ĮSPĖJIMAS. Niekada neatskleiskite savo atsarginės frazės. Bet kas, žinantis šią frazę, gali visiems laikams pasiimti jūsų eterius."
},
"secretPhrase": {
"message": "Savo saugyklai atkurti įveskite slaptą dvylikos žodžių frazę."
},
"securityAndPrivacy": {
"message": "Sauga ir privatumas"
},
@ -937,9 +931,6 @@
"sendAmount": {
"message": "Siųsti sumą"
},
"sendETH": {
"message": "Siųsti ETH"
},
"sendTokens": {
"message": "Siųsti žetonus"
},

@ -501,9 +501,6 @@
"importWallet": {
"message": "Importēt maku"
},
"importYourExisting": {
"message": "Importējiet esošo maku, izmantojot 12 vārdu atkopšanas frāzi"
},
"imported": {
"message": "Importēts",
"description": "status showing that an account has been fully loaded into the keyring"
@ -891,9 +888,6 @@
"secretBackupPhraseWarning": {
"message": "BRĪDINĀJUMS! Nekādā gadījumā neizpaudiet savu rezerves frāzi. Ikviens, kam pieejama šī frāze, var uz visiem laikiem pārņemt jūsu Ether."
},
"secretPhrase": {
"message": "Ievadiet šeit slepeno divpadsmit vārdu frāzi, lai atjaunotu savu seifu."
},
"securityAndPrivacy": {
"message": "Drošība un konfidencialitāte"
},
@ -933,9 +927,6 @@
"sendAmount": {
"message": "Nosūtītā summa"
},
"sendETH": {
"message": "Sūtīt ETH"
},
"sendTokens": {
"message": "Nosūtīt marķierus"
},

@ -491,9 +491,6 @@
"importWallet": {
"message": "Import Dompet"
},
"importYourExisting": {
"message": "Import dompet sedia ada anda menggunakan frasa benih 12 perkataan"
},
"imported": {
"message": "Diimport",
"description": "status showing that an account has been fully loaded into the keyring"
@ -872,9 +869,6 @@
"secretBackupPhraseWarning": {
"message": "AMARAN: Jangan sesekali dedahkan frasa sandaran anda. Sesiapa yang memperoleh frasa ini boleh mengambil Ether anda selama-lamanya."
},
"secretPhrase": {
"message": "Masukkan ungkapan rahsia dua belas perkataan di sini untuk memulihkan kekubah anda."
},
"securityAndPrivacy": {
"message": "Keselamatan & Privasi"
},
@ -914,9 +908,6 @@
"sendAmount": {
"message": "Hantar Amaun"
},
"sendETH": {
"message": "Hantar ETH"
},
"sendTokens": {
"message": "Hantar Token"
},

@ -311,9 +311,6 @@
"search": {
"message": "Zoeken"
},
"secretPhrase": {
"message": "Voer hier je geheime twaalfwoordfrase in om je kluis te herstellen."
},
"seedPhraseReq": {
"message": "Back-up woorden zijn 12 woorden lang"
},
@ -323,9 +320,6 @@
"send": {
"message": "Sturen"
},
"sendETH": {
"message": "Verzend ETH"
},
"sendTokens": {
"message": "Stuur tokens"
},

@ -492,9 +492,6 @@
"importWallet": {
"message": "Importér lommebok "
},
"importYourExisting": {
"message": "Importer din eksisterende lommebok ved å bruk en tolvords seed-frase."
},
"imported": {
"message": "Importert",
"description": "status showing that an account has been fully loaded into the keyring"
@ -879,9 +876,6 @@
"secretBackupPhraseWarning": {
"message": "ADVARSEL: Du må aldri avsløre gjenopprettingsfrasen din. Alle som har denne frasen kan ta fra deg Etheren din for alltid."
},
"secretPhrase": {
"message": "Skriv inn den tolv ord lange frasen her for å gjenopprette hvelvet ditt. "
},
"securityAndPrivacy": {
"message": "Sikkerhet og personvern"
},

@ -505,9 +505,6 @@
"importWallet": {
"message": "Importuj portfel"
},
"importYourExisting": {
"message": "Zaimportuj istniejący portfel, wprowadzając 12 słów frazy seed"
},
"imported": {
"message": "Zaimportowane",
"description": "status showing that an account has been fully loaded into the keyring"
@ -889,9 +886,6 @@
"secretBackupPhraseWarning": {
"message": "OSTRZEŻENIE: Nigdy nie ujawniaj swojej frazy zapasowej. Każdy, kto pozna tę frazę, może bezpowrotnie odebrać Ci kryptowalutę Ether."
},
"secretPhrase": {
"message": "Żeby otworzyć schowek, wpisz tutaj swoją frazę dwunastu słów."
},
"securityAndPrivacy": {
"message": "Bezpieczeństwo i prywatność"
},
@ -931,9 +925,6 @@
"sendAmount": {
"message": "Wyślij kwotę"
},
"sendETH": {
"message": "Wyślij ETH"
},
"sendTokens": {
"message": "Wyślij tokeny"
},

@ -321,9 +321,6 @@
"search": {
"message": "Procurar"
},
"secretPhrase": {
"message": "Introduza a sua frase secreta de 12 palavras para recuperar o seu ."
},
"seedPhraseReq": {
"message": "seed phrases are 12 words long"
},
@ -333,9 +330,6 @@
"send": {
"message": "Enviar"
},
"sendETH": {
"message": "Enviar ETH"
},
"sendTokens": {
"message": "Enviar Tokens"
},

@ -499,9 +499,6 @@
"importWallet": {
"message": "Importar Carteira"
},
"importYourExisting": {
"message": "Importe sua carteira existente usando uma frase-semente de 12 palavras"
},
"imported": {
"message": "Importado",
"description": "status showing that an account has been fully loaded into the keyring"
@ -883,9 +880,6 @@
"secretBackupPhraseWarning": {
"message": "ATENÇÃO: Nunca revele sua frase de backup a ninguém. Qualquer pessoa com essa frase pode obter seu Ether para sempre."
},
"secretPhrase": {
"message": "Digite sua frase secreta de doze palavras aqui para restaurar seu cofre."
},
"securityAndPrivacy": {
"message": "Segurança & Privacidade"
},
@ -925,9 +919,6 @@
"sendAmount": {
"message": "Enviar Quantia"
},
"sendETH": {
"message": "Enviar ETH"
},
"sendTokens": {
"message": "Enviar Tokens"
},

@ -495,9 +495,6 @@
"importWallet": {
"message": "Importați portofel"
},
"importYourExisting": {
"message": "Importați portofelul existent folosind o frază seed de 12 cuvinte"
},
"imported": {
"message": "Importate",
"description": "status showing that an account has been fully loaded into the keyring"
@ -882,9 +879,6 @@
"secretBackupPhraseWarning": {
"message": "ATENȚIE: Nu dezvăluiți niciodată expresia dvs. de rezervă. Oricine are această expresie vă poate lua Ether-ul pentru totdeauna."
},
"secretPhrase": {
"message": "Introduceți aici expresia dvs. secretă din 12 cuvinte pentru a restabili seiful."
},
"securityAndPrivacy": {
"message": "Securitate și confidențialitate"
},
@ -924,9 +918,6 @@
"sendAmount": {
"message": "Suma trimisă"
},
"sendETH": {
"message": "Trimitere ETH"
},
"sendTokens": {
"message": "Trimiteți indicative"
},

@ -791,9 +791,6 @@
"importWallet": {
"message": "Импортировать кошелек"
},
"importYourExisting": {
"message": "Импортируйте существующий кошелек, используя начальную фразу из 12 слов"
},
"imported": {
"message": "Импортированный",
"description": "status showing that an account has been fully loaded into the keyring"
@ -1350,9 +1347,6 @@
"secretBackupPhraseWarning": {
"message": "ПРЕДУПРЕЖДЕНИЕ: Никогда не разглашайте резервную фразу. Любой, у кого есть эта фраза, может забрать ваш Ether навсегда."
},
"secretPhrase": {
"message": "Введите здесь свою секретную фразу из двенадцати слов, чтобы восстановить свой сейф."
},
"securityAndPrivacy": {
"message": "Безопасность и конфиденциальность"
},
@ -1404,9 +1398,6 @@
"sendAmount": {
"message": "Отправить сумму"
},
"sendETH": {
"message": "Отправить ETH"
},
"sendSpecifiedTokens": {
"message": "Отправить $1",
"description": "Symbol of the specified token"

@ -492,9 +492,6 @@
"importWallet": {
"message": "Importovať Peňaženku"
},
"importYourExisting": {
"message": "Importujte svoju existujúcu peňaženku pomocou 12-slovnej seed frázy"
},
"imported": {
"message": "Importováno",
"description": "status showing that an account has been fully loaded into the keyring"
@ -858,9 +855,6 @@
"secretBackupPhraseWarning": {
"message": "UPOZORNENIE: Nikdy nezverejňujte svoju backup frázu. Každý, kto má túto frázu, môže navždy vziať váš Ether."
},
"secretPhrase": {
"message": "Zadejte svých 12 slov tajné fráze k obnovení trezoru."
},
"securityAndPrivacy": {
"message": "Bezpečnosť a súkromie"
},
@ -900,9 +894,6 @@
"sendAmount": {
"message": "Poslať sumu"
},
"sendETH": {
"message": "Odeslat ETH"
},
"sendTokens": {
"message": "Odeslat tokeny"
},

@ -496,9 +496,6 @@
"importWallet": {
"message": "Uvozi denarnico"
},
"importYourExisting": {
"message": "Uvozite svojo obstoječo denarnico s pomočjo 12-besednega gesla seed phrase"
},
"imported": {
"message": "Uvoženo",
"description": "status showing that an account has been fully loaded into the keyring"
@ -877,9 +874,6 @@
"secretBackupPhraseWarning": {
"message": "OPOZORILO: Nikoli nikomur ne razkrijte varnostne kopije. Kdorkoli lahko tem geslom vedno prevzame vaš Ether."
},
"secretPhrase": {
"message": "Vnesite vaših dvanajst besed za obnovitev vaših računov."
},
"securityAndPrivacy": {
"message": "Varnost in zasebnost"
},
@ -919,9 +913,6 @@
"sendAmount": {
"message": "Pošlji znesek"
},
"sendETH": {
"message": "Pošlji ETH"
},
"sendTokens": {
"message": "Pošlji žetone"
},

@ -502,9 +502,6 @@
"importWallet": {
"message": "Uvezite novčanik"
},
"importYourExisting": {
"message": "Uvezite vaš postojeći novčanik koristeći seed frazu sa 12 reči"
},
"imported": {
"message": "Увезени",
"description": "status showing that an account has been fully loaded into the keyring"
@ -886,9 +883,6 @@
"secretBackupPhraseWarning": {
"message": "UPOZORENJE: Nikada ne otkrivajte svoju rezervnu frazu. Svako sa ovom frazom može zauvek da Vam uzme Vaš Ether."
},
"secretPhrase": {
"message": "Unesite ovde svoj tajni izraz od dvanaest reči kako biste povratili svoj trezor."
},
"securityAndPrivacy": {
"message": "Bezbednost i privatnost"
},
@ -928,9 +922,6 @@
"sendAmount": {
"message": "Pošaljite iznos"
},
"sendETH": {
"message": "Pošalji ETH"
},
"sendTokens": {
"message": "Pošalji tokene"
},

@ -495,9 +495,6 @@
"importWallet": {
"message": "Importera plånbok"
},
"importYourExisting": {
"message": "Importera din existerande plånbok med hjälp av en 12 ord lång seedfras"
},
"imported": {
"message": "Importerade",
"description": "status showing that an account has been fully loaded into the keyring"
@ -879,9 +876,6 @@
"secretBackupPhraseWarning": {
"message": "VARNING: avslöja aldrig din backup-fras. Någon som känner till denna fras kan ta dina Ether för alltid."
},
"secretPhrase": {
"message": "Ange din tolv ord långa hemliga fras här för att återställa ditt valv."
},
"securityAndPrivacy": {
"message": "Säkerhet och integritet"
},
@ -921,9 +915,6 @@
"sendAmount": {
"message": "Skicka belopp"
},
"sendETH": {
"message": "Skicka ETH"
},
"sendTokens": {
"message": "Skicka tokens"
},

@ -492,9 +492,6 @@
"importWallet": {
"message": "Hamisha Waleti"
},
"importYourExisting": {
"message": "Hamisha waleti iliyopo kwa kutumia kirai kianzio cha maneno 12"
},
"imported": {
"message": "Zilizoingizwa",
"description": "status showing that an account has been fully loaded into the keyring"
@ -873,9 +870,6 @@
"secretBackupPhraseWarning": {
"message": "ONYO: Kamwe usiweke wazi kirai chako cha hifadhi mbadala. Mtu yeyote mwenye kirai hiki anaweza kuchukua Ether yako daima."
},
"secretPhrase": {
"message": "Ingiza hapa kirai chako cha siri cha maneno kumi na mawili ili urejeshe vault yako."
},
"securityAndPrivacy": {
"message": "Ulinzi na Faragha"
},
@ -915,9 +909,6 @@
"sendAmount": {
"message": "Tuma Kiasi"
},
"sendETH": {
"message": "Tuma ETH"
},
"sendTokens": {
"message": "Tuma Vianzio"
},

@ -426,9 +426,6 @@
"searchTokens": {
"message": "தடலகன"
},
"secretPhrase": {
"message": "உஙகளடகதபதறக இங உஙகள ரகசிய பனிரணடர உளிடவ."
},
"seedPhraseReq": {
"message": "விியஙகள 12 வகளடவ"
},
@ -438,9 +435,6 @@
"send": {
"message": "அன"
},
"sendETH": {
"message": "ETH ஐ அன"
},
"sendTokens": {
"message": "டகனகள அனபவ"
},

@ -420,9 +420,6 @@
"search": {
"message": "คนหา"
},
"secretPhrase": {
"message": "ปอนกลมคำสบสองคำเพอกนตเซฟของคณ"
},
"seedPhraseReq": {
"message": "กลมคำชดมความยาว 12 คำ"
},
@ -441,9 +438,6 @@
"sendAmount": {
"message": "สงจำนวนเงนน"
},
"sendETH": {
"message": "สงอเธอร"
},
"sendTokens": {
"message": "สงโทเคน"
},

@ -791,9 +791,6 @@
"importWallet": {
"message": "I-import ang wallet"
},
"importYourExisting": {
"message": "I-import ang iyong kasalukuyang wallet gamit ang 12 salita na seed phrase"
},
"imported": {
"message": "Na-import",
"description": "status showing that an account has been fully loaded into the keyring"
@ -1347,9 +1344,6 @@
"secretBackupPhraseWarning": {
"message": "BABALA: Huwag kailanman ipaalam ang iyong phrase sa pag-back up. Ang sinumang may phrase na ito ay maaaring angkinin ang iyong Ether."
},
"secretPhrase": {
"message": "Ilagay ang iyong labindalawang lihim na phrase dito para ma-restore ang iyong vault."
},
"securityAndPrivacy": {
"message": "Seguridad at Pagkapribado"
},
@ -1401,9 +1395,6 @@
"sendAmount": {
"message": "Halaga ng Ipapadala"
},
"sendETH": {
"message": "Magpadala ng ETH"
},
"sendSpecifiedTokens": {
"message": "Magpadala ng $1",
"description": "Symbol of the specified token"

@ -366,9 +366,6 @@
"searchTokens": {
"message": "Jeton ara"
},
"secretPhrase": {
"message": "Kasanızı geri getirmek için gizli 12 kelimelik ifadenizi giriniz."
},
"seedPhraseReq": {
"message": "Kaynak ifadeleri 12 kelimedir."
},
@ -378,9 +375,6 @@
"send": {
"message": "Gönder"
},
"sendETH": {
"message": "ETH Gönder"
},
"sendTokens": {
"message": "Jeton Gönder"
},

@ -505,9 +505,6 @@
"importWallet": {
"message": "Імпортувати гаманець"
},
"importYourExisting": {
"message": "Імпортуйте ваш гаманець, що існує, використовуючи початкову фразу з 12 слів"
},
"imported": {
"message": "Імпортовано",
"description": "status showing that an account has been fully loaded into the keyring"
@ -895,9 +892,6 @@
"secretBackupPhraseWarning": {
"message": "ЗАСТЕРЕЖЕННЯ: Ніколи не розголошуйте вашу резервну фразу. Будь-хто з цією фразою зможе забрати ваш Ether назавжди."
},
"secretPhrase": {
"message": "Введіть секретну фразу з дванадцяти слів, щоб відновити своє сховище."
},
"securityAndPrivacy": {
"message": "Безпека й конфіденційність"
},
@ -937,9 +931,6 @@
"sendAmount": {
"message": "Надіслати суму"
},
"sendETH": {
"message": "Надіслати ETH"
},
"sendTokens": {
"message": "Надіслати токени"
},

@ -791,9 +791,6 @@
"importWallet": {
"message": "Nhập ví"
},
"importYourExisting": {
"message": "Nhập ví hiện có của bạn bằng cụm mật khẩu gốc gồm 12 từ"
},
"imported": {
"message": "Đã nhập",
"description": "status showing that an account has been fully loaded into the keyring"
@ -1350,9 +1347,6 @@
"secretBackupPhraseWarning": {
"message": "CẢNH BÁO: Tuyệt đối không để lộ cụm mật khẩu sao lưu của bạn. Bất kỳ ai có cụm mật khẩu này cũng có thể lấy Ether của bạn vĩnh viễn."
},
"secretPhrase": {
"message": "Nhập cụm mật khẩu bí mật gồm 12 từ vào đây để khôi phục két của bạn."
},
"securityAndPrivacy": {
"message": "Bảo mật và quyền riêng tư"
},
@ -1404,9 +1398,6 @@
"sendAmount": {
"message": "Gửi khoản tiền"
},
"sendETH": {
"message": "Gửi ETH"
},
"sendSpecifiedTokens": {
"message": "Gửi $1",
"description": "Symbol of the specified token"

@ -800,9 +800,6 @@
"importWallet": {
"message": "导入钱包"
},
"importYourExisting": {
"message": "使用 12 个单词的账户助记词导入您现有的钱包账户。"
},
"imported": {
"message": "已导入",
"description": "status showing that an account has been fully loaded into the keyring"
@ -1359,9 +1356,6 @@
"secretBackupPhraseWarning": {
"message": "警告:切勿向他人透露您的账户助记词。任何人一旦持有该账户助记词,即可控制您的 Ether。"
},
"secretPhrase": {
"message": "输入 12 个单词组成的账户助记词恢复您的账户。"
},
"securityAndPrivacy": {
"message": "安全与隐私"
},
@ -1413,9 +1407,6 @@
"sendAmount": {
"message": "发送数额"
},
"sendETH": {
"message": "发送 ETH"
},
"sendSpecifiedTokens": {
"message": "发送 $1",
"description": "Symbol of the specified token"

@ -514,9 +514,6 @@
"importWallet": {
"message": "匯入錢包"
},
"importYourExisting": {
"message": "使用 12 字的助記詞來匯入你現有的錢包"
},
"imported": {
"message": "已匯入私鑰",
"description": "status showing that an account has been fully loaded into the keyring"
@ -889,9 +886,6 @@
"secretBackupPhraseWarning": {
"message": "警告: 絕對不要洩漏您的助憶詞。任何人只要得知助憶詞代表他可以竊取您所有的以太幣和代幣。"
},
"secretPhrase": {
"message": "輸入您的12個助憶詞以回復金庫"
},
"securityAndPrivacy": {
"message": "安全&隱私"
},
@ -925,9 +919,6 @@
"sendAmount": {
"message": "發送數量"
},
"sendETH": {
"message": "發送以太幣"
},
"sendTokens": {
"message": "發送代幣"
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

@ -1,6 +1,6 @@
import assert from 'assert';
import ethUtil from 'ethereumjs-util';
import accountImporter from '../../../app/scripts/account-import-strategies';
import accountImporter from '.';
describe('Account Import Strategies', function () {
const privkey =

@ -1,12 +1,6 @@
/**
* @file The entry point for the web extension singleton process.
*/
// these need to run before anything else
/* eslint-disable import/first,import/order */
import setupFetchDebugging from './lib/setupFetchDebugging';
/* eslint-enable import/order */
setupFetchDebugging();
// polyfills
import 'abortcontroller-polyfill/dist/polyfill-patch-fetch';
@ -70,21 +64,7 @@ if (inTest || process.env.METAMASK_DEBUG) {
initialize().catch(log.error);
/**
* An object representing a transaction, in whatever state it is in.
* @typedef TransactionMeta
*
* @property {number} id - An internally unique tx identifier.
* @property {number} time - Time the tx was first suggested, in unix epoch time (ms).
* @property {string} status - The current transaction status (unapproved, signed, submitted, dropped, failed, rejected), as defined in `tx-state-manager.js`.
* @property {string} metamaskNetworkId - The transaction's network ID, used for EIP-155 compliance.
* @property {boolean} loadingDefaults - TODO: Document
* @property {Object} txParams - The tx params as passed to the network provider.
* @property {Object[]} history - A history of mutations to this TransactionMeta object.
* @property {string} origin - A string representing the interface that suggested the transaction.
* @property {Object} nonceDetails - A metadata object containing information used to derive the suggested nonce, useful for debugging nonce issues.
* @property {string} rawTx - A hex string of the final signed transaction, ready to submit to the network.
* @property {string} hash - A hex string of the transaction hash, used to identify the transaction on the network.
* @property {number} submittedTime - The time the transaction was submitted to the network, in Unix epoch time (ms).
* @typedef {import('../../shared/constants/transaction').TransactionMeta} TransactionMeta
*/
/**

@ -1,7 +1,7 @@
import assert from 'assert';
import sinon from 'sinon';
import CachedBalancesController from '../../../../app/scripts/controllers/cached-balances';
import { KOVAN_CHAIN_ID } from '../../../../shared/constants/network';
import { KOVAN_CHAIN_ID } from '../../../shared/constants/network';
import CachedBalancesController from './cached-balances';
describe('CachedBalancesController', function () {
describe('updateCachedBalances', function () {

@ -4,10 +4,10 @@ import { ObservableStore } from '@metamask/obs-store';
import contracts from '@metamask/contract-metadata';
import BigNumber from 'bignumber.js';
import DetectTokensController from '../../../../app/scripts/controllers/detect-tokens';
import NetworkController from '../../../../app/scripts/controllers/network/network';
import PreferencesController from '../../../../app/scripts/controllers/preferences';
import { MAINNET, ROPSTEN } from '../../../../shared/constants/network';
import { MAINNET, ROPSTEN } from '../../../shared/constants/network';
import DetectTokensController from './detect-tokens';
import NetworkController from './network';
import PreferencesController from './preferences';
describe('DetectTokensController', function () {
const sandbox = sinon.createSandbox();

@ -1,6 +1,6 @@
import assert from 'assert';
import sinon from 'sinon';
import EnsController from '../../../../app/scripts/controllers/ens';
import EnsController from '.';
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
const ZERO_X_ERROR_ADDRESS = '0x';

@ -1,32 +1,48 @@
import { ObservableStore } from '@metamask/obs-store';
import log from 'loglevel';
import BN from 'bn.js';
import createId from '../lib/random-id';
import createId from '../../../shared/modules/random-id';
import { bnToHex } from '../lib/util';
import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout';
import {
TRANSACTION_CATEGORIES,
TRANSACTION_TYPES,
TRANSACTION_STATUSES,
} from '../../../shared/constants/transaction';
import {
CHAIN_ID_TO_NETWORK_ID_MAP,
CHAIN_ID_TO_TYPE_MAP,
GOERLI,
GOERLI_CHAIN_ID,
KOVAN,
KOVAN_CHAIN_ID,
MAINNET,
MAINNET_CHAIN_ID,
RINKEBY,
RINKEBY_CHAIN_ID,
ROPSTEN,
ROPSTEN_CHAIN_ID,
} from '../../../shared/constants/network';
import { NETWORK_EVENTS } from './network';
const fetchWithTimeout = getFetchWithTimeout(30000);
/**
* @typedef {import('../../../shared/constants/transaction').TransactionMeta} TransactionMeta
*/
/**
* A transaction object in the format returned by the Etherscan API.
*
* Note that this is not an exhaustive type definiton; only the properties we use are defined
*
* @typedef {Object} EtherscanTransaction
* @property {string} blockNumber - The number of the block this transaction was found in, in decimal
* @property {string} from - The hex-prefixed address of the sender
* @property {string} gas - The gas limit, in decimal WEI
* @property {string} gasPrice - The gas price, in decimal WEI
* @property {string} hash - The hex-prefixed transaction hash
* @property {string} isError - Whether the transaction was confirmed or failed (0 for confirmed, 1 for failed)
* @property {string} nonce - The transaction nonce, in decimal
* @property {string} timeStamp - The timestamp for the transaction, in seconds
* @property {string} to - The hex-prefixed address of the recipient
* @property {string} value - The amount of ETH sent in this transaction, in decimal WEI
*/
/**
* This controller is responsible for retrieving incoming transactions. Etherscan is polled once every block to check
* for new incoming transactions for the current selected account on the current network
@ -44,35 +60,37 @@ const etherscanSupportedNetworks = [
export default class IncomingTransactionsController {
constructor(opts = {}) {
const { blockTracker, networkController, preferencesController } = opts;
const {
blockTracker,
onNetworkDidChange,
getCurrentChainId,
preferencesController,
} = opts;
this.blockTracker = blockTracker;
this.networkController = networkController;
this.getCurrentChainId = getCurrentChainId;
this.preferencesController = preferencesController;
this._onLatestBlock = async (newBlockNumberHex) => {
const selectedAddress = this.preferencesController.getSelectedAddress();
const newBlockNumberDec = parseInt(newBlockNumberHex, 16);
await this._update({
address: selectedAddress,
newBlockNumberDec,
});
await this._update(selectedAddress, newBlockNumberDec);
};
const initState = {
incomingTransactions: {},
incomingTxLastFetchedBlocksByNetwork: {
[GOERLI]: null,
[KOVAN]: null,
[MAINNET]: null,
[RINKEBY]: null,
[ROPSTEN]: null,
incomingTxLastFetchedBlockByChainId: {
[GOERLI_CHAIN_ID]: null,
[KOVAN_CHAIN_ID]: null,
[MAINNET_CHAIN_ID]: null,
[RINKEBY_CHAIN_ID]: null,
[ROPSTEN_CHAIN_ID]: null,
},
...opts.initState,
};
this.store = new ObservableStore(initState);
this.preferencesController.store.subscribe(
pairwise((prevState, currState) => {
previousValueComparator((prevState, currState) => {
const {
featureFlags: {
showIncomingTransactions: prevShowIncomingTransactions,
@ -94,29 +112,24 @@ export default class IncomingTransactionsController {
}
this.start();
}),
}, this.preferencesController.store.getState()),
);
this.preferencesController.store.subscribe(
pairwise(async (prevState, currState) => {
previousValueComparator(async (prevState, currState) => {
const { selectedAddress: prevSelectedAddress } = prevState;
const { selectedAddress: currSelectedAddress } = currState;
if (currSelectedAddress === prevSelectedAddress) {
return;
}
await this._update({
address: currSelectedAddress,
});
}),
await this._update(currSelectedAddress);
}, this.preferencesController.store.getState()),
);
this.networkController.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, async () => {
onNetworkDidChange(async () => {
const address = this.preferencesController.getSelectedAddress();
await this._update({
address,
});
await this._update(address);
});
}
@ -136,85 +149,79 @@ export default class IncomingTransactionsController {
this.blockTracker.removeListener('latest', this._onLatestBlock);
}
async _update({ address, newBlockNumberDec } = {}) {
const chainId = this.networkController.getCurrentChainId();
if (!etherscanSupportedNetworks.includes(chainId)) {
/**
* Determines the correct block number to begin looking for new transactions
* from, fetches the transactions and then saves them and the next block
* number to begin fetching from in state. Block numbers and transactions are
* stored per chainId.
* @private
* @param {string} address - address to lookup transactions for
* @param {number} [newBlockNumberDec] - block number to begin fetching from
* @returns {void}
*/
async _update(address, newBlockNumberDec) {
const chainId = this.getCurrentChainId();
if (!etherscanSupportedNetworks.includes(chainId) || !address) {
return;
}
try {
const dataForUpdate = await this._getDataForUpdate({
const currentState = this.store.getState();
const currentBlock = parseInt(this.blockTracker.getCurrentBlock(), 16);
const mostRecentlyFetchedBlock =
currentState.incomingTxLastFetchedBlockByChainId[chainId];
const blockToFetchFrom =
mostRecentlyFetchedBlock ?? newBlockNumberDec ?? currentBlock;
const newIncomingTxs = await this._getNewIncomingTransactions(
address,
blockToFetchFrom,
chainId,
newBlockNumberDec,
);
let newMostRecentlyFetchedBlock = blockToFetchFrom;
newIncomingTxs.forEach((tx) => {
if (
tx.blockNumber &&
parseInt(newMostRecentlyFetchedBlock, 10) <
parseInt(tx.blockNumber, 10)
) {
newMostRecentlyFetchedBlock = parseInt(tx.blockNumber, 10);
}
});
this.store.updateState({
incomingTxLastFetchedBlockByChainId: {
...currentState.incomingTxLastFetchedBlockByChainId,
[chainId]: newMostRecentlyFetchedBlock + 1,
},
incomingTransactions: newIncomingTxs.reduce(
(transactions, tx) => {
transactions[tx.hash] = tx;
return transactions;
},
{
...currentState.incomingTransactions,
},
),
});
this._updateStateWithNewTxData(dataForUpdate);
} catch (err) {
log.error(err);
}
}
async _getDataForUpdate({ address, chainId, newBlockNumberDec } = {}) {
const {
incomingTransactions: currentIncomingTxs,
incomingTxLastFetchedBlocksByNetwork: currentBlocksByNetwork,
} = this.store.getState();
const lastFetchBlockByCurrentNetwork =
currentBlocksByNetwork[CHAIN_ID_TO_TYPE_MAP[chainId]];
let blockToFetchFrom = lastFetchBlockByCurrentNetwork || newBlockNumberDec;
if (blockToFetchFrom === undefined) {
blockToFetchFrom = parseInt(this.blockTracker.getCurrentBlock(), 16);
}
const { latestIncomingTxBlockNumber, txs: newTxs } = await this._fetchAll(
address,
blockToFetchFrom,
chainId,
);
return {
latestIncomingTxBlockNumber,
newTxs,
currentIncomingTxs,
currentBlocksByNetwork,
fetchedBlockNumber: blockToFetchFrom,
chainId,
};
}
_updateStateWithNewTxData({
latestIncomingTxBlockNumber,
newTxs,
currentIncomingTxs,
currentBlocksByNetwork,
fetchedBlockNumber,
chainId,
}) {
const newLatestBlockHashByNetwork = latestIncomingTxBlockNumber
? parseInt(latestIncomingTxBlockNumber, 10) + 1
: fetchedBlockNumber + 1;
const newIncomingTransactions = {
...currentIncomingTxs,
};
newTxs.forEach((tx) => {
newIncomingTransactions[tx.hash] = tx;
});
this.store.updateState({
incomingTxLastFetchedBlocksByNetwork: {
...currentBlocksByNetwork,
[CHAIN_ID_TO_TYPE_MAP[chainId]]: newLatestBlockHashByNetwork,
},
incomingTransactions: newIncomingTransactions,
});
}
async _fetchAll(address, fromBlock, chainId) {
const fetchedTxResponse = await this._fetchTxs(address, fromBlock, chainId);
return this._processTxFetchResponse(fetchedTxResponse);
}
async _fetchTxs(address, fromBlock, chainId) {
/**
* fetches transactions for the given address and chain, via etherscan, then
* processes the data into the necessary shape for usage in this controller.
*
* @private
* @param {string} [address] - Address to fetch transactions for
* @param {number} [fromBlock] - Block to look for transactions at
* @param {string} [chainId] - The chainId for the current network
* @returns {TransactionMeta[]}
*/
async _getNewIncomingTransactions(address, fromBlock, chainId) {
const etherscanSubdomain =
chainId === MAINNET_CHAIN_ID
? 'api'
@ -227,16 +234,8 @@ export default class IncomingTransactionsController {
url += `&startBlock=${parseInt(fromBlock, 10)}`;
}
const response = await fetchWithTimeout(url);
const parsedResponse = await response.json();
return {
...parsedResponse,
address,
chainId,
};
}
_processTxFetchResponse({ status, result = [], address, chainId }) {
const { status, result } = await response.json();
let newIncomingTxs = [];
if (status === '1' && Array.isArray(result) && result.length > 0) {
const remoteTxList = {};
const remoteTxs = [];
@ -247,70 +246,70 @@ export default class IncomingTransactionsController {
}
});
const incomingTxs = remoteTxs.filter(
newIncomingTxs = remoteTxs.filter(
(tx) => tx.txParams?.to?.toLowerCase() === address.toLowerCase(),
);
incomingTxs.sort((a, b) => (a.time < b.time ? -1 : 1));
let latestIncomingTxBlockNumber = null;
incomingTxs.forEach((tx) => {
if (
tx.blockNumber &&
(!latestIncomingTxBlockNumber ||
parseInt(latestIncomingTxBlockNumber, 10) <
parseInt(tx.blockNumber, 10))
) {
latestIncomingTxBlockNumber = tx.blockNumber;
}
});
return {
latestIncomingTxBlockNumber,
txs: incomingTxs,
};
newIncomingTxs.sort((a, b) => (a.time < b.time ? -1 : 1));
}
return {
latestIncomingTxBlockNumber: null,
txs: [],
};
return newIncomingTxs;
}
_normalizeTxFromEtherscan(txMeta, chainId) {
const time = parseInt(txMeta.timeStamp, 10) * 1000;
/**
* Transmutes a EtherscanTransaction into a TransactionMeta
* @param {EtherscanTransaction} etherscanTransaction - the transaction to normalize
* @param {string} chainId - The chainId of the current network
* @returns {TransactionMeta}
*/
_normalizeTxFromEtherscan(etherscanTransaction, chainId) {
const time = parseInt(etherscanTransaction.timeStamp, 10) * 1000;
const status =
txMeta.isError === '0'
etherscanTransaction.isError === '0'
? TRANSACTION_STATUSES.CONFIRMED
: TRANSACTION_STATUSES.FAILED;
return {
blockNumber: txMeta.blockNumber,
blockNumber: etherscanTransaction.blockNumber,
id: createId(),
chainId,
metamaskNetworkId: CHAIN_ID_TO_NETWORK_ID_MAP[chainId],
status,
time,
txParams: {
from: txMeta.from,
gas: bnToHex(new BN(txMeta.gas)),
gasPrice: bnToHex(new BN(txMeta.gasPrice)),
nonce: bnToHex(new BN(txMeta.nonce)),
to: txMeta.to,
value: bnToHex(new BN(txMeta.value)),
from: etherscanTransaction.from,
gas: bnToHex(new BN(etherscanTransaction.gas)),
gasPrice: bnToHex(new BN(etherscanTransaction.gasPrice)),
nonce: bnToHex(new BN(etherscanTransaction.nonce)),
to: etherscanTransaction.to,
value: bnToHex(new BN(etherscanTransaction.value)),
},
hash: txMeta.hash,
transactionCategory: TRANSACTION_CATEGORIES.INCOMING,
hash: etherscanTransaction.hash,
type: TRANSACTION_TYPES.INCOMING,
};
}
}
function pairwise(fn) {
/**
* Returns a function with arity 1 that caches the argument that the function
* is called with and invokes the comparator with both the cached, previous,
* value and the current value. If specified, the initialValue will be passed
* in as the previous value on the first invocation of the returned method.
* @template A
* @params {A=} type of compared value
* @param {(prevValue: A, nextValue: A) => void} comparator - method to compare
* previous and next values.
* @param {A} [initialValue] - initial value to supply to prevValue
* on first call of the method.
* @returns {void}
*/
function previousValueComparator(comparator, initialValue) {
let first = true;
let cache;
return (value) => {
try {
if (first) {
first = false;
return fn(value, value);
return comparator(initialValue ?? value, value);
}
return fn(cache, value);
return comparator(cache, value);
} finally {
cache = value;
}

@ -1,14 +1,14 @@
import { strict as assert } from 'assert';
import sinon from 'sinon';
import MetaMetricsController from '../../../../app/scripts/controllers/metametrics';
import { ENVIRONMENT_TYPE_BACKGROUND } from '../../../../shared/constants/app';
import { createSegmentMock } from '../../../../app/scripts/lib/segment';
import { ENVIRONMENT_TYPE_BACKGROUND } from '../../../shared/constants/app';
import { createSegmentMock } from '../lib/segment';
import {
METAMETRICS_ANONYMOUS_ID,
METAMETRICS_BACKGROUND_PAGE_OBJECT,
} from '../../../../shared/constants/metametrics';
import waitUntilCalled from '../../../lib/wait-until-called';
import { NETWORK_EVENTS } from '../../../../app/scripts/controllers/network';
} from '../../../shared/constants/metametrics';
import waitUntilCalled from '../../../test/lib/wait-until-called';
import MetaMetricsController from './metametrics';
import { NETWORK_EVENTS } from './network';
const segment = createSegmentMock(2, 10000);
const segmentLegacy = createSegmentMock(2, 10000);

@ -6,7 +6,7 @@ import createInflightMiddleware from 'eth-json-rpc-middleware/inflight-cache';
import createBlockTrackerInspectorMiddleware from 'eth-json-rpc-middleware/block-tracker-inspector';
import providerFromMiddleware from 'eth-json-rpc-middleware/providerFromMiddleware';
import createInfuraMiddleware from 'eth-json-rpc-infura';
import BlockTracker from 'eth-block-tracker';
import { PollingBlockTracker } from 'eth-block-tracker';
import { NETWORK_TYPE_TO_ID_MAP } from '../../../../shared/constants/network';
@ -18,7 +18,7 @@ export default function createInfuraClient({ network, projectId }) {
source: 'metamask',
});
const infuraProvider = providerFromMiddleware(infuraMiddleware);
const blockTracker = new BlockTracker({ provider: infuraProvider });
const blockTracker = new PollingBlockTracker({ provider: infuraProvider });
const networkMiddleware = mergeMiddleware([
createNetworkAndChainIdMiddleware({ network }),

@ -5,7 +5,7 @@ import createBlockCacheMiddleware from 'eth-json-rpc-middleware/block-cache';
import createInflightMiddleware from 'eth-json-rpc-middleware/inflight-cache';
import createBlockTrackerInspectorMiddleware from 'eth-json-rpc-middleware/block-tracker-inspector';
import providerFromMiddleware from 'eth-json-rpc-middleware/providerFromMiddleware';
import BlockTracker from 'eth-block-tracker';
import { PollingBlockTracker } from 'eth-block-tracker';
const inTest = process.env.IN_TEST === 'true';
const blockTrackerOpts = inTest ? { pollingInterval: 1000 } : {};
@ -16,7 +16,7 @@ const getTestMiddlewares = () => {
export default function createJsonRpcClient({ rpcUrl, chainId }) {
const fetchMiddleware = createFetchMiddleware({ rpcUrl });
const blockProvider = providerFromMiddleware(fetchMiddleware);
const blockTracker = new BlockTracker({
const blockTracker = new PollingBlockTracker({
...blockTrackerOpts,
provider: blockProvider,
});

@ -1,7 +1,7 @@
import { strict as assert } from 'assert';
import sinon from 'sinon';
import NetworkController from '../../../../../app/scripts/controllers/network';
import { getNetworkDisplayName } from '../../../../../app/scripts/controllers/network/util';
import { getNetworkDisplayName } from './util';
import NetworkController from './network';
describe('NetworkController', function () {
describe('controller', function () {

@ -1,9 +1,9 @@
import assert from 'assert';
import { txMetaStub } from '../../../../test/stub/tx-meta-stub';
import {
createPendingNonceMiddleware,
createPendingTxMiddleware,
} from '../../../../../app/scripts/controllers/network/middleware/pending';
import { txMetaStub } from './stubs';
} from './middleware/pending';
describe('PendingNonceMiddleware', function () {
describe('#createPendingNonceMiddleware', function () {

@ -2,22 +2,20 @@ import { strict as assert } from 'assert';
import { find } from 'lodash';
import sinon from 'sinon';
import {
METADATA_STORE_KEY,
METADATA_CACHE_MAX_SIZE,
} from '../../../../../app/scripts/controllers/permissions/enums';
import { PermissionsController } from '../../../../../app/scripts/controllers/permissions';
import { getRequestUserApprovalHelper, grantPermissions } from './helpers';
import {
constants,
getters,
getNotifyDomain,
getNotifyAllDomains,
getPermControllerOpts,
} from './mocks';
} from '../../../../test/mocks/permission-controller';
import {
getRequestUserApprovalHelper,
grantPermissions,
} from '../../../../test/helpers/permission-controller-helpers';
import { METADATA_STORE_KEY, METADATA_CACHE_MAX_SIZE } from './enums';
import { PermissionsController } from '.';
const { ERRORS, NOTIFICATIONS, PERMS } = getters;

@ -3,16 +3,14 @@ import { ObservableStore } from '@metamask/obs-store';
import nanoid from 'nanoid';
import { useFakeTimers } from 'sinon';
import PermissionsLogController from '../../../../../app/scripts/controllers/permissions/permissionsLog';
import {
LOG_LIMIT,
LOG_METHOD_TYPES,
} from '../../../../../app/scripts/controllers/permissions/enums';
import { validateActivityEntry } from './helpers';
import { constants, getters, noop } from './mocks';
constants,
getters,
noop,
} from '../../../../test/mocks/permission-controller';
import { validateActivityEntry } from '../../../../test/helpers/permission-controller-helpers';
import PermissionsLogController from './permissionsLog';
import { LOG_LIMIT, LOG_METHOD_TYPES } from './enums';
const { PERMS, RPC_REQUESTS } = getters;
@ -50,7 +48,7 @@ const initMiddleware = (permLog) => {
const initClock = () => {
// useFakeTimers, is in fact, not a react-hook
// eslint-disable-next-line
clock = useFakeTimers(1)
clock = useFakeTimers(1);
};
const tearDownClock = () => {

@ -1,18 +1,19 @@
import { strict as assert } from 'assert';
import sinon from 'sinon';
import { METADATA_STORE_KEY } from '../../../../../app/scripts/controllers/permissions/enums';
import { PermissionsController } from '../../../../../app/scripts/controllers/permissions';
import { getUserApprovalPromise, grantPermissions } from './helpers';
import {
constants,
getters,
getPermControllerOpts,
getPermissionsMiddleware,
} from './mocks';
} from '../../../../test/mocks/permission-controller';
import {
getUserApprovalPromise,
grantPermissions,
} from '../../../../test/helpers/permission-controller-helpers';
import { METADATA_STORE_KEY } from './enums';
import { PermissionsController } from '.';
const { CAVEATS, ERRORS, PERMS, RPC_REQUESTS } = getters;

@ -1,4 +1,4 @@
import { cloneDeep } from 'lodash';
import stringify from 'fast-safe-stringify';
import { CAVEAT_NAMES } from '../../../../shared/constants/permissions';
import {
HISTORY_STORE_KEY,
@ -151,7 +151,7 @@ export default class PermissionsLogController {
? LOG_METHOD_TYPES.internal
: LOG_METHOD_TYPES.restricted,
origin: request.origin,
request: cloneDeep(request),
request: stringify(request, null, 2),
requestTime: Date.now(),
response: null,
responseTime: null,
@ -174,7 +174,7 @@ export default class PermissionsLogController {
return;
}
entry.response = cloneDeep(response);
entry.response = stringify(response, null, 2);
entry.responseTime = time;
entry.success = !response.error;
}

@ -1,7 +1,7 @@
import { strict as assert } from 'assert';
import pify from 'pify';
import getRestrictedMethods from '../../../../../app/scripts/controllers/permissions/restrictedMethods';
import getRestrictedMethods from './restrictedMethods';
describe('restricted methods', function () {
describe('eth_accounts', function () {

@ -378,7 +378,7 @@ export default class PreferencesController {
*/
async addToken(rawAddress, symbol, decimals, image) {
const address = normalizeAddress(rawAddress);
const newEntry = { address, symbol, decimals };
const newEntry = { address, symbol, decimals: Number(decimals) };
const { tokens, hiddenTokens } = this.store.getState();
const assetImages = this.getAssetImages();
const updatedHiddenTokens = hiddenTokens.filter(
@ -793,9 +793,14 @@ export default class PreferencesController {
if (typeof symbol !== 'string') {
throw ethErrors.rpc.invalidParams(`Invalid symbol: not a string.`);
}
if (!(symbol.length < 7)) {
if (!(symbol.length > 0)) {
throw ethErrors.rpc.invalidParams(
`Invalid symbol "${symbol}": longer than 6 characters.`,
`Invalid symbol "${symbol}": shorter than a character.`,
);
}
if (!(symbol.length < 12)) {
throw ethErrors.rpc.invalidParams(
`Invalid symbol "${symbol}": longer than 11 characters.`,
);
}
const numDecimals = parseInt(decimals, 10);

@ -1,10 +1,10 @@
import assert from 'assert';
import sinon from 'sinon';
import PreferencesController from '../../../../app/scripts/controllers/preferences';
import {
MAINNET_CHAIN_ID,
RINKEBY_CHAIN_ID,
} from '../../../../shared/constants/network';
} from '../../../shared/constants/network';
import PreferencesController from './preferences';
describe('preferences controller', function () {
let preferencesController;
@ -539,6 +539,14 @@ describe('preferences controller', function () {
decimals: 0,
}),
);
assert.doesNotThrow(() =>
validate({
address: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07',
symbol: 'ABCDEFGHIJK',
decimals: 0,
}),
);
assert.throws(
() => validate({ symbol: 'ABC', decimals: 0 }),
'missing address should fail',
@ -563,10 +571,19 @@ describe('preferences controller', function () {
() =>
validate({
address: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07',
symbol: 'ABCDEFGHI',
symbol: 'ABCDEFGHIJKLM',
decimals: 0,
}),
'long symbol should fail',
);
assert.throws(
() =>
validate({
address: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07',
symbol: '',
decimals: 0,
}),
'invalid symbol should fail',
'empty symbol should fail',
);
assert.throws(
() =>

@ -8,12 +8,13 @@ import { calcTokenAmount } from '../../../ui/app/helpers/utils/token-util';
import { calcGasTotal } from '../../../ui/app/pages/send/send.utils';
import { conversionUtil } from '../../../ui/app/helpers/utils/conversion-util';
import {
ETH_SWAPS_TOKEN_OBJECT,
DEFAULT_ERC20_APPROVE_GAS,
QUOTES_EXPIRED_ERROR,
QUOTES_NOT_AVAILABLE_ERROR,
SWAPS_FETCH_ORDER_CONFLICT,
} from '../../../ui/app/helpers/constants/swaps';
} from '../../../shared/constants/swaps';
import { isSwapsDefaultTokenAddress } from '../../../shared/modules/swaps.utils';
import {
fetchTradesInfo as defaultFetchTradesInfo,
fetchSwapsFeatureLiveness as defaultFetchSwapsFeatureLiveness,
@ -85,6 +86,7 @@ export default class SwapsController {
fetchTradesInfo = defaultFetchTradesInfo,
fetchSwapsFeatureLiveness = defaultFetchSwapsFeatureLiveness,
fetchSwapsQuoteRefreshTime = defaultFetchSwapsQuoteRefreshTime,
getCurrentChainId,
}) {
this.store = new ObservableStore({
swapsState: { ...initialState.swapsState },
@ -93,6 +95,7 @@ export default class SwapsController {
this._fetchTradesInfo = fetchTradesInfo;
this._fetchSwapsFeatureLiveness = fetchSwapsFeatureLiveness;
this._fetchSwapsQuoteRefreshTime = fetchSwapsQuoteRefreshTime;
this._getCurrentChainId = getCurrentChainId;
this.getBufferedGasLimit = getBufferedGasLimit;
this.tokenRatesStore = tokenRatesStore;
@ -116,10 +119,11 @@ export default class SwapsController {
// Sets the refresh rate for quote updates from the MetaSwap API
async _setSwapsQuoteRefreshTime() {
const chainId = this._getCurrentChainId();
// Default to fallback time unless API returns valid response
let swapsQuoteRefreshTime = FALLBACK_QUOTE_REFRESH_TIME;
try {
swapsQuoteRefreshTime = await this._fetchSwapsQuoteRefreshTime();
swapsQuoteRefreshTime = await this._fetchSwapsQuoteRefreshTime(chainId);
} catch (e) {
console.error('Request for swaps quote refresh time failed: ', e);
}
@ -158,6 +162,8 @@ export default class SwapsController {
fetchParamsMetaData = {},
isPolledRequest,
) {
const { chainId } = fetchParamsMetaData;
if (!fetchParams) {
return null;
}
@ -177,7 +183,7 @@ export default class SwapsController {
this.indexOfNewestCallInFlight = indexOfCurrentCall;
let [newQuotes] = await Promise.all([
this._fetchTradesInfo(fetchParams),
this._fetchTradesInfo(fetchParams, fetchParamsMetaData),
this._setSwapsQuoteRefreshTime(),
]);
@ -191,7 +197,7 @@ export default class SwapsController {
let approvalRequired = false;
if (
fetchParams.sourceToken !== ETH_SWAPS_TOKEN_OBJECT.address &&
!isSwapsDefaultTokenAddress(fetchParams.sourceToken, chainId) &&
Object.values(newQuotes).length
) {
const allowance = await this._getERC20Allowance(
@ -490,6 +496,7 @@ export default class SwapsController {
const {
swapsState: { customGasPrice },
} = this.store.getState();
const chainId = this._getCurrentChainId();
const numQuotes = Object.keys(quotes).length;
if (!numQuotes) {
@ -533,8 +540,8 @@ export default class SwapsController {
// trade.value is a sum of different values depending on the transaction.
// It always includes any external fees charged by the quote source. In
// addition, if the source asset is ETH, trade.value includes the amount
// of swapped ETH.
// addition, if the source asset is the selected chain's default token, trade.value
// includes the amount of that token.
const totalWeiCost = new BigNumber(gasTotalInWeiHex, 16).plus(
trade.value,
16,
@ -549,21 +556,21 @@ export default class SwapsController {
});
// The total fee is aggregator/exchange fees plus gas fees.
// If the swap is from ETH, subtract the sourceAmount from the total cost.
// Otherwise, the total fee is simply trade.value plus gas fees.
const ethFee =
sourceToken === ETH_SWAPS_TOKEN_OBJECT.address
? conversionUtil(
totalWeiCost.minus(sourceAmount, 10), // sourceAmount is in wei
{
fromCurrency: 'ETH',
fromDenomination: 'WEI',
toDenomination: 'ETH',
fromNumericBase: 'BN',
numberOfDecimals: 6,
},
)
: totalEthCost;
// If the swap is from the selected chain's default token, subtract
// the sourceAmount from the total cost. Otherwise, the total fee
// is simply trade.value plus gas fees.
const ethFee = isSwapsDefaultTokenAddress(sourceToken, chainId)
? conversionUtil(
totalWeiCost.minus(sourceAmount, 10), // sourceAmount is in wei
{
fromCurrency: 'ETH',
fromDenomination: 'WEI',
toDenomination: 'ETH',
fromNumericBase: 'BN',
numberOfDecimals: 6,
},
)
: totalEthCost;
const decimalAdjustedDestinationAmount = calcTokenAmount(
destinationAmount,
@ -588,10 +595,12 @@ export default class SwapsController {
10,
);
const conversionRateForCalculations =
destinationToken === ETH_SWAPS_TOKEN_OBJECT.address
? 1
: tokenConversionRate;
const conversionRateForCalculations = isSwapsDefaultTokenAddress(
destinationToken,
chainId,
)
? 1
: tokenConversionRate;
const overallValueOfQuoteForSorting =
conversionRateForCalculations === undefined
@ -618,8 +627,10 @@ export default class SwapsController {
});
const isBest =
newQuotes[topAggId].destinationToken === ETH_SWAPS_TOKEN_OBJECT.address ||
Boolean(tokenConversionRates[newQuotes[topAggId]?.destinationToken]);
isSwapsDefaultTokenAddress(
newQuotes[topAggId].destinationToken,
chainId,
) || Boolean(tokenConversionRates[newQuotes[topAggId]?.destinationToken]);
let savings = null;
@ -726,13 +737,17 @@ export default class SwapsController {
async _fetchAndSetSwapsLiveness() {
const { swapsState } = this.store.getState();
const { swapsFeatureIsLive: oldSwapsFeatureIsLive } = swapsState;
const chainId = this._getCurrentChainId();
let swapsFeatureIsLive = false;
let successfullyFetched = false;
let numAttempts = 0;
const fetchAndIncrementNumAttempts = async () => {
try {
swapsFeatureIsLive = Boolean(await this._fetchSwapsFeatureLiveness());
swapsFeatureIsLive = Boolean(
await this._fetchSwapsFeatureLiveness(chainId),
);
successfullyFetched = true;
} catch (err) {
log.error(err);

@ -8,13 +8,12 @@ import { ObservableStore } from '@metamask/obs-store';
import {
ROPSTEN_NETWORK_ID,
MAINNET_NETWORK_ID,
} from '../../../../shared/constants/network';
import { ETH_SWAPS_TOKEN_OBJECT } from '../../../../ui/app/helpers/constants/swaps';
import { createTestProviderTools } from '../../../stub/provider';
import SwapsController, {
utils,
} from '../../../../app/scripts/controllers/swaps';
import { NETWORK_EVENTS } from '../../../../app/scripts/controllers/network';
MAINNET_CHAIN_ID,
} from '../../../shared/constants/network';
import { ETH_SWAPS_TOKEN_OBJECT } from '../../../shared/constants/swaps';
import { createTestProviderTools } from '../../../test/stub/provider';
import SwapsController, { utils } from './swaps';
import { NETWORK_EVENTS } from './network';
const MOCK_FETCH_PARAMS = {
slippage: 3,
@ -77,6 +76,7 @@ const MOCK_FETCH_METADATA = {
symbol: 'FOO',
decimals: 18,
},
chainId: MAINNET_CHAIN_ID,
};
const MOCK_TOKEN_RATES_STORE = new ObservableStore({
@ -133,6 +133,8 @@ const sandbox = sinon.createSandbox();
const fetchTradesInfoStub = sandbox.stub();
const fetchSwapsFeatureLivenessStub = sandbox.stub();
const fetchSwapsQuoteRefreshTimeStub = sandbox.stub();
const getCurrentChainIdStub = sandbox.stub();
getCurrentChainIdStub.returns(MAINNET_CHAIN_ID);
describe('SwapsController', function () {
let provider;
@ -147,6 +149,7 @@ describe('SwapsController', function () {
fetchTradesInfo: fetchTradesInfoStub,
fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub,
fetchSwapsQuoteRefreshTime: fetchSwapsQuoteRefreshTimeStub,
getCurrentChainId: getCurrentChainIdStub,
});
};
@ -196,6 +199,7 @@ describe('SwapsController', function () {
tokenRatesStore: MOCK_TOKEN_RATES_STORE,
fetchTradesInfo: fetchTradesInfoStub,
fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub,
getCurrentChainId: getCurrentChainIdStub,
});
const currentEthersInstance = swapsController.ethersProvider;
const onNetworkDidChange = networkController.on.getCall(0).args[1];
@ -220,6 +224,7 @@ describe('SwapsController', function () {
tokenRatesStore: MOCK_TOKEN_RATES_STORE,
fetchTradesInfo: fetchTradesInfoStub,
fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub,
getCurrentChainId: getCurrentChainIdStub,
});
const currentEthersInstance = swapsController.ethersProvider;
const onNetworkDidChange = networkController.on.getCall(0).args[1];
@ -244,6 +249,7 @@ describe('SwapsController', function () {
tokenRatesStore: MOCK_TOKEN_RATES_STORE,
fetchTradesInfo: fetchTradesInfoStub,
fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub,
getCurrentChainId: getCurrentChainIdStub,
});
const currentEthersInstance = swapsController.ethersProvider;
const onNetworkDidChange = networkController.on.getCall(0).args[1];
@ -688,7 +694,10 @@ describe('SwapsController', function () {
});
assert.strictEqual(
fetchTradesInfoStub.calledOnceWithExactly(MOCK_FETCH_PARAMS),
fetchTradesInfoStub.calledOnceWithExactly(
MOCK_FETCH_PARAMS,
MOCK_FETCH_METADATA,
),
true,
);
});

@ -1,7 +1,7 @@
import assert from 'assert';
import sinon from 'sinon';
import { ObservableStore } from '@metamask/obs-store';
import TokenRatesController from '../../../../app/scripts/controllers/token-rates';
import TokenRatesController from './token-rates';
describe('TokenRatesController', function () {
let nativeCurrency;

@ -19,7 +19,6 @@ import {
import { TRANSACTION_NO_CONTRACT_ERROR_KEY } from '../../../../ui/app/helpers/constants/error-keys';
import { getSwapsTokensReceivedFromTxMeta } from '../../../../ui/app/pages/swaps/swaps.util';
import {
TRANSACTION_CATEGORIES,
TRANSACTION_STATUSES,
TRANSACTION_TYPES,
} from '../../../../shared/constants/transaction';
@ -235,11 +234,10 @@ export default class TransactionController extends EventEmitter {
`generateTxMeta` adds the default txMeta properties to the passed object.
These include the tx's `id`. As we use the id for determining order of
txes in the tx-state-manager, it is necessary to call the asynchronous
method `this._determineTransactionCategory` after `generateTxMeta`.
method `this._determineTransactionType` after `generateTxMeta`.
*/
let txMeta = this.txStateManager.generateTxMeta({
txParams: normalizedTxParams,
type: TRANSACTION_TYPES.STANDARD,
});
if (origin === 'metamask') {
@ -265,11 +263,10 @@ export default class TransactionController extends EventEmitter {
txMeta.origin = origin;
const {
transactionCategory,
getCodeResponse,
} = await this._determineTransactionCategory(txParams);
txMeta.transactionCategory = transactionCategory;
const { type, getCodeResponse } = await this._determineTransactionType(
txParams,
);
txMeta.type = type;
// ensure value
txMeta.txParams.value = txMeta.txParams.value
@ -347,7 +344,7 @@ export default class TransactionController extends EventEmitter {
return {};
} else if (
txMeta.txParams.to &&
txMeta.transactionCategory === TRANSACTION_CATEGORIES.SENT_ETHER
txMeta.type === TRANSACTION_TYPES.SENT_ETHER
) {
// if there's data in the params, but there's no contract code, it's not a valid transaction
if (txMeta.txParams.data) {
@ -388,7 +385,7 @@ export default class TransactionController extends EventEmitter {
* @param {string} [customGasPrice] - the hex value to use for the cancel transaction
* @returns {txMeta}
*/
async createCancelTransaction(originalTxId, customGasPrice) {
async createCancelTransaction(originalTxId, customGasPrice, customGasLimit) {
const originalTxMeta = this.txStateManager.getTx(originalTxId);
const { txParams } = originalTxMeta;
const { gasPrice: lastGasPrice, from, nonce } = txParams;
@ -401,7 +398,7 @@ export default class TransactionController extends EventEmitter {
from,
to: from,
nonce,
gas: '0x5208',
gas: customGasLimit || '0x5208',
value: '0x0',
gasPrice: newGasPrice,
},
@ -581,7 +578,7 @@ export default class TransactionController extends EventEmitter {
async publishTransaction(txId, rawTx) {
const txMeta = this.txStateManager.getTx(txId);
txMeta.rawTx = rawTx;
if (txMeta.transactionCategory === TRANSACTION_CATEGORIES.SWAP) {
if (txMeta.type === TRANSACTION_TYPES.SWAP) {
const preTxBalance = await this.query.getBalance(txMeta.txParams.from);
txMeta.preTxBalance = preTxBalance.toString(16);
}
@ -637,7 +634,7 @@ export default class TransactionController extends EventEmitter {
'transactions#confirmTransaction - add txReceipt',
);
if (txMeta.transactionCategory === TRANSACTION_CATEGORIES.SWAP) {
if (txMeta.type === TRANSACTION_TYPES.SWAP) {
const postTxBalance = await this.query.getBalance(txMeta.txParams.from);
const latestTxMeta = this.txStateManager.getTx(txId);
@ -812,10 +809,27 @@ export default class TransactionController extends EventEmitter {
}
/**
Returns a "type" for a transaction out of the following list: simpleSend, tokenTransfer, tokenApprove,
contractDeployment, contractMethodCall
*/
async _determineTransactionCategory(txParams) {
* @typedef { 'transfer' | 'approve' | 'transferfrom' | 'contractInteraction'| 'sentEther' } InferrableTransactionTypes
*/
/**
* @typedef {Object} InferTransactionTypeResult
* @property {InferrableTransactionTypes} type - The type of transaction
* @property {string} getCodeResponse - The contract code, in hex format if
* it exists. '0x0' or '0x' are also indicators of non-existent contract
* code
*/
/**
* Determines the type of the transaction by analyzing the txParams.
* This method will return one of the types defined in shared/constants/transactions
* It will never return TRANSACTION_TYPE_CANCEL or TRANSACTION_TYPE_RETRY as these
* represent specific events that we control from the extension and are added manually
* at transaction creation.
* @param {Object} txParams - Parameters for the transaction
* @returns {InferTransactionTypeResult}
*/
async _determineTransactionType(txParams) {
const { data, to } = txParams;
let name;
try {
@ -825,16 +839,16 @@ export default class TransactionController extends EventEmitter {
}
const tokenMethodName = [
TRANSACTION_CATEGORIES.TOKEN_METHOD_APPROVE,
TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER,
TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER_FROM,
TRANSACTION_TYPES.TOKEN_METHOD_APPROVE,
TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER,
TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM,
].find((methodName) => methodName === name && name.toLowerCase());
let result;
if (data && tokenMethodName) {
result = tokenMethodName;
} else if (data && !to) {
result = TRANSACTION_CATEGORIES.DEPLOY_CONTRACT;
result = TRANSACTION_TYPES.DEPLOY_CONTRACT;
}
let code;
@ -849,11 +863,11 @@ export default class TransactionController extends EventEmitter {
const codeIsEmpty = !code || code === '0x' || code === '0x0';
result = codeIsEmpty
? TRANSACTION_CATEGORIES.SENT_ETHER
: TRANSACTION_CATEGORIES.CONTRACT_INTERACTION;
? TRANSACTION_TYPES.SENT_ETHER
: TRANSACTION_TYPES.CONTRACT_INTERACTION;
}
return { transactionCategory: result, getCodeResponse: code };
return { type: result, getCodeResponse: code };
}
/**
@ -944,6 +958,7 @@ export default class TransactionController extends EventEmitter {
txMeta.txParams.from,
txMeta.destinationTokenDecimals,
approvalTxMeta,
txMeta.chainId,
);
const quoteVsExecutionRatio = `${new BigNumber(tokensReceived, 10)

@ -4,18 +4,17 @@ import ethUtil from 'ethereumjs-util';
import EthTx from 'ethereumjs-tx';
import { ObservableStore } from '@metamask/obs-store';
import sinon from 'sinon';
import TransactionController from '../../../../../app/scripts/controllers/transactions';
import {
createTestProviderTools,
getTestAccounts,
} from '../../../../stub/provider';
} from '../../../../test/stub/provider';
import {
TRANSACTION_CATEGORIES,
TRANSACTION_STATUSES,
TRANSACTION_TYPES,
} from '../../../../../shared/constants/transaction';
import { METAMASK_CONTROLLER_EVENTS } from '../../../../../app/scripts/metamask-controller';
} from '../../../../shared/constants/transaction';
import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller';
import TransactionController from '.';
const noop = () => true;
const currentNetworkId = '42';
@ -776,76 +775,76 @@ describe('Transaction Controller', function () {
});
});
describe('#_determineTransactionCategory', function () {
it('should return a simple send transactionCategory when to is truthy but data is falsy', async function () {
const result = await txController._determineTransactionCategory({
describe('#_determineTransactionType', function () {
it('should return a simple send type when to is truthy but data is falsy', async function () {
const result = await txController._determineTransactionType({
to: '0xabc',
data: '',
});
assert.deepEqual(result, {
transactionCategory: TRANSACTION_CATEGORIES.SENT_ETHER,
type: TRANSACTION_TYPES.SENT_ETHER,
getCodeResponse: null,
});
});
it('should return a token transfer transactionCategory when data is for the respective method call', async function () {
const result = await txController._determineTransactionCategory({
it('should return a token transfer type when data is for the respective method call', async function () {
const result = await txController._determineTransactionType({
to: '0xabc',
data:
'0xa9059cbb0000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C970000000000000000000000000000000000000000000000000000000000000000a',
});
assert.deepEqual(result, {
transactionCategory: TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER,
type: TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER,
getCodeResponse: undefined,
});
});
it('should return a token approve transactionCategory when data is for the respective method call', async function () {
const result = await txController._determineTransactionCategory({
it('should return a token approve type when data is for the respective method call', async function () {
const result = await txController._determineTransactionType({
to: '0xabc',
data:
'0x095ea7b30000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C9700000000000000000000000000000000000000000000000000000000000000005',
});
assert.deepEqual(result, {
transactionCategory: TRANSACTION_CATEGORIES.TOKEN_METHOD_APPROVE,
type: TRANSACTION_TYPES.TOKEN_METHOD_APPROVE,
getCodeResponse: undefined,
});
});
it('should return a contract deployment transactionCategory when to is falsy and there is data', async function () {
const result = await txController._determineTransactionCategory({
it('should return a contract deployment type when to is falsy and there is data', async function () {
const result = await txController._determineTransactionType({
to: '',
data: '0xabd',
});
assert.deepEqual(result, {
transactionCategory: TRANSACTION_CATEGORIES.DEPLOY_CONTRACT,
type: TRANSACTION_TYPES.DEPLOY_CONTRACT,
getCodeResponse: undefined,
});
});
it('should return a simple send transactionCategory with a 0x getCodeResponse when there is data and but the to address is not a contract address', async function () {
const result = await txController._determineTransactionCategory({
it('should return a simple send type with a 0x getCodeResponse when there is data and but the to address is not a contract address', async function () {
const result = await txController._determineTransactionType({
to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9',
data: '0xabd',
});
assert.deepEqual(result, {
transactionCategory: TRANSACTION_CATEGORIES.SENT_ETHER,
type: TRANSACTION_TYPES.SENT_ETHER,
getCodeResponse: '0x',
});
});
it('should return a simple send transactionCategory with a null getCodeResponse when to is truthy and there is data and but getCode returns an error', async function () {
const result = await txController._determineTransactionCategory({
it('should return a simple send type with a null getCodeResponse when to is truthy and there is data and but getCode returns an error', async function () {
const result = await txController._determineTransactionType({
to: '0xabc',
data: '0xabd',
});
assert.deepEqual(result, {
transactionCategory: TRANSACTION_CATEGORIES.SENT_ETHER,
type: TRANSACTION_TYPES.SENT_ETHER,
getCodeResponse: null,
});
});
it('should return a contract interaction transactionCategory with the correct getCodeResponse when to is truthy and there is data and it is not a token transaction', async function () {
it('should return a contract interaction type with the correct getCodeResponse when to is truthy and there is data and it is not a token transaction', async function () {
const _providerResultStub = {
// 1 gwei
eth_gasPrice: '0x0de0b6b3a7640000',
@ -875,17 +874,17 @@ describe('Transaction Controller', function () {
}),
getParticipateInMetrics: () => false,
});
const result = await _txController._determineTransactionCategory({
const result = await _txController._determineTransactionType({
to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9',
data: 'abd',
});
assert.deepEqual(result, {
transactionCategory: TRANSACTION_CATEGORIES.CONTRACT_INTERACTION,
type: TRANSACTION_TYPES.CONTRACT_INTERACTION,
getCodeResponse: '0x0a',
});
});
it('should return a contract interaction transactionCategory with the correct getCodeResponse when to is a contract address and data is falsy', async function () {
it('should return a contract interaction type with the correct getCodeResponse when to is a contract address and data is falsy', async function () {
const _providerResultStub = {
// 1 gwei
eth_gasPrice: '0x0de0b6b3a7640000',
@ -915,12 +914,12 @@ describe('Transaction Controller', function () {
}),
getParticipateInMetrics: () => false,
});
const result = await _txController._determineTransactionCategory({
const result = await _txController._determineTransactionType({
to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9',
data: '',
});
assert.deepEqual(result, {
transactionCategory: TRANSACTION_CATEGORIES.CONTRACT_INTERACTION,
type: TRANSACTION_TYPES.CONTRACT_INTERACTION,
getCodeResponse: '0x0a',
});
});

@ -1,11 +1,11 @@
import { strict as assert } from 'assert';
import testData from '../../../../../test/data/mock-tx-history.json';
import {
snapshotFromTxMeta,
migrateFromSnapshotsToDiffs,
replayHistory,
generateHistoryEntry,
} from '../../../../../app/scripts/controllers/transactions/lib/tx-state-history-helpers';
import testData from '../../../../data/mock-tx-history.json';
} from './tx-state-history-helpers';
describe('Transaction state history helper', function () {
describe('#snapshotFromTxMeta', function () {

@ -1,5 +1,5 @@
import { strict as assert } from 'assert';
import * as txUtils from '../../../../../app/scripts/controllers/transactions/lib/util';
import * as txUtils from './util';
describe('txUtils', function () {
describe('#validateTxParams', function () {

@ -1,8 +1,8 @@
import { strict as assert } from 'assert';
import sinon from 'sinon';
import BN from 'bn.js';
import PendingTransactionTracker from '../../../../../app/scripts/controllers/transactions/pending-tx-tracker';
import { TRANSACTION_STATUSES } from '../../../../../shared/constants/transaction';
import { TRANSACTION_STATUSES } from '../../../../shared/constants/transaction';
import PendingTransactionTracker from './pending-tx-tracker';
describe('PendingTransactionTracker', function () {
describe('#resubmitPendingTxs', function () {

@ -1,7 +1,7 @@
import { strict as assert } from 'assert';
import Transaction from 'ethereumjs-tx';
import { hexToBn, bnToHex } from '../../../../../app/scripts/lib/util';
import TxUtils from '../../../../../app/scripts/controllers/transactions/tx-gas-utils';
import { hexToBn, bnToHex } from '../../lib/util';
import TxUtils from './tx-gas-utils';
describe('txUtils', function () {
let txUtils;

@ -1,7 +1,7 @@
import EventEmitter from 'safe-event-emitter';
import { ObservableStore } from '@metamask/obs-store';
import log from 'loglevel';
import createId from '../../lib/random-id';
import createId from '../../../../shared/modules/random-id';
import { TRANSACTION_STATUSES } from '../../../../shared/constants/transaction';
import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller';
import { transactionMatchesNetwork } from '../../../../shared/modules/transaction.utils';

@ -1,12 +1,12 @@
import { strict as assert } from 'assert';
import sinon from 'sinon';
import TxStateManager from '../../../../../app/scripts/controllers/transactions/tx-state-manager';
import { snapshotFromTxMeta } from '../../../../../app/scripts/controllers/transactions/lib/tx-state-history-helpers';
import { TRANSACTION_STATUSES } from '../../../../../shared/constants/transaction';
import { TRANSACTION_STATUSES } from '../../../../shared/constants/transaction';
import {
KOVAN_CHAIN_ID,
KOVAN_NETWORK_ID,
} from '../../../../../shared/constants/network';
} from '../../../../shared/constants/network';
import TxStateManager from './tx-state-manager';
import { snapshotFromTxMeta } from './lib/tx-state-history-helpers';
const noop = () => true;

@ -1,6 +1,6 @@
import assert from 'assert';
import { ObservableStore } from '@metamask/obs-store';
import ComposableObservableStore from '../../../app/scripts/lib/ComposableObservableStore';
import ComposableObservableStore from './ComposableObservableStore';
describe('ComposableObservableStore', function () {
it('should register initial state', function () {

@ -1,18 +1,26 @@
import {
GOERLI_CHAIN_ID,
KOVAN_CHAIN_ID,
MAINNET_CHAIN_ID,
RINKEBY_CHAIN_ID,
ROPSTEN_CHAIN_ID,
} from '../../../shared/constants/network';
/**
* Gives the caller a url at which the user can acquire eth, depending on the network they are in
*
* @param {Object} opts - Options required to determine the correct url
* @param {string} opts.network - The network for which to return a url
* @param {string} opts.address - The address the bought ETH should be sent to. Only relevant if network === '1'.
* @returns {string|undefined} The url at which the user can access ETH, while in the given network. If the passed
* network does not match any of the specified cases, or if no network is given, returns undefined.
* @param {string} opts.chainId - The chainId for which to return a url
* @param {string} opts.address - The address the bought ETH should be sent to. Only relevant if chainId === '0x1'.
* @returns {string|undefined} The url at which the user can access ETH, while in the given chain. If the passed
* chainId does not match any of the specified cases, or if no chainId is given, returns undefined.
*
*/
export default function getBuyEthUrl({ network, address, service }) {
export default function getBuyEthUrl({ chainId, address, service }) {
// default service by network if not specified
if (!service) {
// eslint-disable-next-line no-param-reassign
service = getDefaultServiceForNetwork(network);
service = getDefaultServiceForChain(chainId);
}
switch (service) {
@ -33,21 +41,21 @@ export default function getBuyEthUrl({ network, address, service }) {
}
}
function getDefaultServiceForNetwork(network) {
switch (network) {
case '1':
function getDefaultServiceForChain(chainId) {
switch (chainId) {
case MAINNET_CHAIN_ID:
return 'wyre';
case '3':
case ROPSTEN_CHAIN_ID:
return 'metamask-faucet';
case '4':
case RINKEBY_CHAIN_ID:
return 'rinkeby-faucet';
case '42':
case KOVAN_CHAIN_ID:
return 'kovan-faucet';
case '5':
case GOERLI_CHAIN_ID:
return 'goerli-faucet';
default:
throw new Error(
`No default cryptocurrency exchange or faucet for networkId: "${network}"`,
`No default cryptocurrency exchange or faucet for chainId: "${chainId}"`,
);
}
}

@ -1,20 +1,26 @@
import assert from 'assert';
import getBuyEthUrl from '../../../app/scripts/lib/buy-eth-url';
import {
KOVAN_CHAIN_ID,
MAINNET_CHAIN_ID,
RINKEBY_CHAIN_ID,
ROPSTEN_CHAIN_ID,
} from '../../../shared/constants/network';
import getBuyEthUrl from './buy-eth-url';
describe('buy-eth-url', function () {
const mainnet = {
network: '1',
chainId: MAINNET_CHAIN_ID,
amount: 5,
address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
};
const ropsten = {
network: '3',
chainId: ROPSTEN_CHAIN_ID,
};
const rinkeby = {
network: '4',
chainId: RINKEBY_CHAIN_ID,
};
const kovan = {
network: '42',
chainId: KOVAN_CHAIN_ID,
};
it('returns wyre url with address for network 1', function () {

@ -1,5 +1,5 @@
import assert from 'assert';
import cleanErrorStack from '../../../app/scripts/lib/cleanErrorStack';
import cleanErrorStack from './cleanErrorStack';
describe('Clean Error Stack', function () {
const testMessage = 'Test Message';

@ -0,0 +1,33 @@
import { ethErrors, serializeError } from 'eth-rpc-errors';
const createMetaRPCHandler = (api, outStream) => {
return (data) => {
if (!api[data.method]) {
outStream.write({
jsonrpc: '2.0',
error: ethErrors.rpc.methodNotFound({
message: `${data.method} not found`,
}),
id: data.id,
});
return;
}
api[data.method](...data.params, (err, result) => {
if (err) {
outStream.write({
jsonrpc: '2.0',
error: serializeError(err, { shouldIncludeStack: true }),
id: data.id,
});
} else {
outStream.write({
jsonrpc: '2.0',
result,
id: data.id,
});
}
});
};
};
export default createMetaRPCHandler;

@ -0,0 +1,61 @@
import assert from 'assert';
import { obj as createThoughStream } from 'through2';
import createMetaRPCHandler from './createMetaRPCHandler';
describe('createMetaRPCHandler', function () {
it('can call the api when handler receives a JSON-RPC request', function (done) {
const api = {
foo: (param1) => {
assert.strictEqual(param1, 'bar');
done();
},
};
const streamTest = createThoughStream();
const handler = createMetaRPCHandler(api, streamTest);
handler({
id: 1,
method: 'foo',
params: ['bar'],
});
});
it('can write the response to the outstream when api callback is called', function (done) {
const api = {
foo: (param1, cb) => {
assert.strictEqual(param1, 'bar');
cb(null, 'foobarbaz');
},
};
const streamTest = createThoughStream();
const handler = createMetaRPCHandler(api, streamTest);
handler({
id: 1,
method: 'foo',
params: ['bar'],
});
streamTest.on('data', (data) => {
assert.strictEqual(data.result, 'foobarbaz');
streamTest.end();
done();
});
});
it('can write the error to the outstream when api callback is called with an error', function (done) {
const api = {
foo: (param1, cb) => {
assert.strictEqual(param1, 'bar');
cb(new Error('foo-error'));
},
};
const streamTest = createThoughStream();
const handler = createMetaRPCHandler(api, streamTest);
handler({
id: 1,
method: 'foo',
params: ['bar'],
});
streamTest.on('data', (data) => {
assert.strictEqual(data.error.message, 'foo-error');
streamTest.end();
done();
});
});
});

@ -5,8 +5,8 @@ import { ethErrors } from 'eth-rpc-errors';
import log from 'loglevel';
import { MESSAGE_TYPE } from '../../../shared/constants/app';
import { METAMASK_CONTROLLER_EVENTS } from '../metamask-controller';
import createId from '../../../shared/modules/random-id';
import { addHexPrefix } from './util';
import createId from './random-id';
const hexRe = /^[0-9A-Fa-f]+$/gu;

@ -4,7 +4,7 @@ import { ethErrors } from 'eth-rpc-errors';
import log from 'loglevel';
import { MESSAGE_TYPE } from '../../../shared/constants/app';
import { METAMASK_CONTROLLER_EVENTS } from '../metamask-controller';
import createId from './random-id';
import createId from '../../../shared/modules/random-id';
/**
* Represents, and contains data about, an 'eth_getEncryptionPublicKey' type request. These are created when

@ -4,7 +4,7 @@ import ethUtil from 'ethereumjs-util';
import { ethErrors } from 'eth-rpc-errors';
import { MESSAGE_TYPE } from '../../../shared/constants/app';
import { METAMASK_CONTROLLER_EVENTS } from '../metamask-controller';
import createId from './random-id';
import createId from '../../../shared/modules/random-id';
/**
* Represents, and contains data about, an 'eth_sign' type signature request. These are created when a signature for

@ -1,6 +1,6 @@
import assert from 'assert';
import MessageManager from '../../../app/scripts/lib/message-manager';
import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction';
import MessageManager from './message-manager';
describe('Message Manager', function () {
let messageManager;

@ -0,0 +1,81 @@
import { EthereumRpcError } from 'eth-rpc-errors';
import SafeEventEmitter from 'safe-event-emitter';
import createRandomId from '../../../shared/modules/random-id';
class MetaRPCClient {
constructor(connectionStream) {
this.connectionStream = connectionStream;
this.notificationChannel = new SafeEventEmitter();
this.requests = new Map();
this.connectionStream.on('data', this.handleResponse.bind(this));
this.connectionStream.on('end', this.close.bind(this));
}
onNotification(handler) {
this.notificationChannel.addListener('notification', (data) => {
handler(data);
});
}
close() {
this.notificationChannel.removeAllListeners();
}
handleResponse(data) {
const { id, result, error, method, params } = data;
const cb = this.requests.get(id);
if (method && params && id) {
// dont handle server-side to client-side requests
return;
}
if (method && params && !id) {
// handle servier-side to client-side notification
this.notificationChannel.emit('notification', data);
return;
}
if (!cb) {
// not found in request list
return;
}
if (error) {
const e = new EthereumRpcError(error.code, error.message, error.data);
// preserve the stack from serializeError
e.stack = error.stack;
this.requests.delete(id);
cb(e);
return;
}
this.requests.delete(id);
cb(null, result);
}
}
const metaRPCClientFactory = (connectionStream) => {
const metaRPCClient = new MetaRPCClient(connectionStream);
return new Proxy(metaRPCClient, {
get: (object, property) => {
if (object[property]) {
return object[property];
}
return (...p) => {
const cb = p[p.length - 1];
const params = p.slice(0, -1);
const id = createRandomId();
object.requests.set(id, cb);
object.connectionStream.write({
jsonrpc: '2.0',
method: property,
params,
id,
});
};
},
});
};
export default metaRPCClientFactory;

@ -0,0 +1,88 @@
import assert from 'assert';
import { obj as createThoughStream } from 'through2';
import metaRPCClientFactory from './metaRPCClientFactory';
describe('metaRPCClientFactory', function () {
it('should be able to make an rpc request with the method', function (done) {
const streamTest = createThoughStream((chunk) => {
assert.strictEqual(chunk.method, 'foo');
done();
});
const metaRPCClient = metaRPCClientFactory(streamTest);
metaRPCClient.foo();
});
it('should be able to make an rpc request/response with the method and params and node-style callback', function (done) {
const streamTest = createThoughStream();
const metaRPCClient = metaRPCClientFactory(streamTest);
// make a "foo" method call
metaRPCClient.foo('bar', (_, result) => {
assert.strictEqual(result, 'foobarbaz');
done();
});
// fake a response
metaRPCClient.requests.forEach((_, key) => {
streamTest.write({
jsonrpc: '2.0',
id: key,
result: 'foobarbaz',
});
});
});
it('should be able to make an rpc request/error with the method and params and node-style callback', function (done) {
const streamTest = createThoughStream();
const metaRPCClient = metaRPCClientFactory(streamTest);
// make a "foo" method call
metaRPCClient.foo('bar', (err) => {
assert.strictEqual(err.message, 'foo-message');
assert.strictEqual(err.code, 1);
done();
});
metaRPCClient.requests.forEach((_, key) => {
streamTest.write({
jsonrpc: '2.0',
id: key,
error: {
code: 1,
message: 'foo-message',
},
});
});
});
it('should be able to make an rpc request/response with the method and params and node-style callback with multiple instances of metaRPCClientFactory and the same connectionStream', function (done) {
const streamTest = createThoughStream();
const metaRPCClient = metaRPCClientFactory(streamTest);
const metaRPCClient2 = metaRPCClientFactory(streamTest);
// make a "foo" method call, followed by "baz" call on metaRPCClient2
metaRPCClient.foo('bar', (_, result) => {
assert.strictEqual(result, 'foobarbaz');
metaRPCClient2.baz('bar', (err) => {
assert.strictEqual(err, null);
done();
});
});
// fake a response
metaRPCClient.requests.forEach((_, key) => {
streamTest.write({
jsonrpc: '2.0',
id: key,
result: 'foobarbaz',
});
});
// fake client2's response
metaRPCClient2.requests.forEach((_, key) => {
streamTest.write({
jsonrpc: '2.0',
id: key,
result: 'foobarbaz',
});
});
});
});

@ -1,9 +1,9 @@
import fs from 'fs';
import assert from 'assert';
import { cloneDeep } from 'lodash';
import Migrator from '../../../app/scripts/lib/migrator';
import liveMigrations from '../../../app/scripts/migrations';
import data from '../../../app/scripts/first-time-state';
import liveMigrations from '../../migrations';
import data from '../../first-time-state';
import Migrator from '.';
const stubMigrations = [
{
@ -67,7 +67,7 @@ describe('migrations', function () {
});
it('should have tests for all migrations', function () {
const fileNames = fs.readdirSync('./test/unit/migrations/');
const fileNames = fs.readdirSync('./app/scripts/migrations/');
const testNumbers = fileNames
.reduce((acc, filename) => {
const name = filename.split('.test.')[0];

@ -1,5 +1,5 @@
import assert from 'assert';
import nodeify from '../../../app/scripts/lib/nodeify';
import nodeify from './nodeify';
describe('nodeify', function () {
const obj = {

@ -5,8 +5,8 @@ import { ethErrors } from 'eth-rpc-errors';
import log from 'loglevel';
import { MESSAGE_TYPE } from '../../../shared/constants/app';
import { METAMASK_CONTROLLER_EVENTS } from '../metamask-controller';
import createId from '../../../shared/modules/random-id';
import { addHexPrefix } from './util';
import createId from './random-id';
const hexRe = /^[0-9A-Fa-f]+$/gu;

@ -1,6 +1,6 @@
import assert from 'assert';
import PersonalMessageManager from '../../../app/scripts/lib/personal-message-manager';
import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction';
import PersonalMessageManager from './personal-message-manager';
describe('Personal Message Manager', function () {
let messageManager;

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save