1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Merge pull request #10257 from MetaMask/Version-v9.0.4

Version v9.0.4 RC
This commit is contained in:
Mark Stacey 2021-01-27 20:05:17 -03:30 committed by GitHub
commit 61374caa4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
145 changed files with 2996 additions and 935 deletions

View File

@ -7,3 +7,4 @@ app/vendor/**
.nyc_output/**
.vscode/**
test/e2e/send-eth-with-private-key-test/**
*.scss

View File

@ -7,7 +7,7 @@ module.exports = {
addons: [
'@storybook/addon-knobs',
'@storybook/addon-actions',
'@storybook/addon-backgrounds'
'@storybook/addon-backgrounds',
],
webpackFinal: async (config) => {
config.module.strictExportPresence = true
@ -27,18 +27,29 @@ module.exports = {
loader: 'sass-loader',
options: {
sourceMap: true,
implementation: require('sass'),
sassOptions: {
includePaths: ['ui/app/css/'],
},
},
},
],
})
config.plugins.push(new CopyWebpackPlugin({
patterns: [
{
from: path.join('node_modules', '@fortawesome', 'fontawesome-free', 'webfonts'),
to: path.join('fonts', 'fontawesome'),
},
],
}))
config.plugins.push(
new CopyWebpackPlugin({
patterns: [
{
from: path.join(
'node_modules',
'@fortawesome',
'fontawesome-free',
'webfonts',
),
to: path.join('fonts', 'fontawesome'),
},
],
}),
)
return config
},
}

View File

@ -2,6 +2,19 @@
## Current Develop Branch
## 9.0.4 Fri Jan 22 2021
- [#10285](https://github.com/MetaMask/metamask-extension/pull/10285): Update @metamask/contract-metadata from v1.21.0 to 1.22.0
- [#10174](https://github.com/MetaMask/metamask-extension/pull/10174): Move fox to bottom of 'About' page
- [#10198](https://github.com/MetaMask/metamask-extension/pull/10198): Fix hardware account selection
- [#10101](https://github.com/MetaMask/metamask-extension/pull/10101): Add a timeout to all network requests
- [#10212](https://github.com/MetaMask/metamask-extension/pull/10212): Fix displayed balance of tokens with 0 decimals in swaps flow
- [#10162](https://github.com/MetaMask/metamask-extension/pull/10162): Prevent accidentally submitting a swap twice
- [#10224](https://github.com/MetaMask/metamask-extension/pull/10224): Improve chain ID validation
- [#10195](https://github.com/MetaMask/metamask-extension/pull/10195): Increase minimum Firefox version to v68
- [#10192](https://github.com/MetaMask/metamask-extension/pull/10192): Update TrezorConnect to v8
- [#10166](https://github.com/MetaMask/metamask-extension/pull/10166): Fix back button on swaps loading page
- [#9947](https://github.com/MetaMask/metamask-extension/pull/9947): Do not publish swaps transaction if the estimateGas call made when adding the transaction fails.
## 9.0.3 Fri Jan 22 2021
- [#10243](https://github.com/MetaMask/metamask-extension/pull/10243): Fix site metadata handling
- [#10252](https://github.com/MetaMask/metamask-extension/pull/10252): Fix decrypt message confirmation UI crash

View File

@ -1,5 +1,8 @@
# MetaMask Browser Extension
Hey! We are hiring JavaScript Engineers! [Apply here](https://boards.greenhouse.io/consensys/jobs/2572388)!
---
You can find the latest version of MetaMask on [our official website](https://metamask.io/). For help using MetaMask, visit our [User Support Site](https://metamask.zendesk.com/hc/en-us).
For [general questions](https://metamask.zendesk.com/hc/en-us/community/topics/360000682532-General), [feature requests](https://metamask.zendesk.com/hc/en-us/community/topics/360000682552-Feature-Requests-Ideas), or [developer questions](https://metamask.zendesk.com/hc/en-us/community/topics/360001751291-Developer-Questions), visit our [Community Forum](https://metamask.zendesk.com/hc/en-us/community/topics).

View File

@ -845,6 +845,9 @@
"invalidBlockExplorerURL": {
"message": "Invalid Block Explorer URL"
},
"invalidChainIdTooBig": {
"message": "Invalid chain ID. The chain ID is too big."
},
"invalidCustomNetworkAlertContent1": {
"message": "The chain ID for custom network '$1' has to be re-entered.",
"description": "$1 is the name/identifier of the network."

File diff suppressed because it is too large Load Diff

View File

@ -19,11 +19,11 @@
},
"appDescription": {
"message": "इथीरियम ब्राउज़र एक्सटेंशन",
"description": "आवेदन का विवरण"
"description": "The description of the application"
},
"appName": {
"message": "मेटामास्क/MetaMask",
"description": "एप्लिकेशन का नाम"
"description": "The name of the application"
},
"approve": {
"message": "मंजूर"
@ -123,11 +123,11 @@
},
"fiat": {
"message": "FIAT एक्सचेंज टाइप",
"description": "एक्सचेंज FIAT टाइप"
"description": "Exchange type"
},
"fileImportFail": {
"message": "फ़ाइल आयात काम नहीं कर रहा है? यहां क्लिक करें!",
"description": "यूजर को अपने खाते को जे.एस.ौ.एन फ़ाइल से आयात करने में मदद करता है"
"description": "Helps user import their account from a JSON file"
},
"from": {
"message": "की तरफ से - संदेश"
@ -146,11 +146,11 @@
},
"getEtherFromFaucet": {
"message": "$1 के लिए एक नल से ईथर प्राप्त करें",
"description": "ईथर नल के लिए नेटवर्क नाम प्रदर्शित करता है"
"description": "Displays network name for Ether faucet"
},
"here": {
"message": "यहां",
"description": "अधिक जानकारी के लिए यहां क्लिक करें- (परेशानी के साथ जाता है टोकनबैलेंस) (troubleTokenBalances)"
"description": "as in -click here- for more information (goes with troubleTokenBalances)"
},
"hide": {
"message": "छुपाएं"
@ -160,7 +160,7 @@
},
"import": {
"message": "आयात",
"description": "एक चयनित फ़ाइल से एक खाता आयात करने के लिए बटन "
"description": "Button to import an account from a selected file"
},
"importAccount": {
"message": "खाता आयात"
@ -170,7 +170,7 @@
},
"imported": {
"message": "आयातित",
"description": "यह स्थिति दिखाती है कि कोई खाता पूरी तरह से कीरिंग में लोड हो चुका है"
"description": "status showing that an account has been fully loaded into the keyring"
},
"infoHelp": {
"message": "जानकारी और सहायता"
@ -195,7 +195,7 @@
},
"jsonFile": {
"message": "JSON फ़ाइल",
"description": "एक खाता आयात करने के लिए प्रारूप"
"description": "format for importing an account"
},
"kovan": {
"message": "कोवान टेस्ट नेटवर्क"
@ -235,7 +235,7 @@
},
"needImportFile": {
"message": "आयात करने के लिए आपको एक फ़ाइल का चयन करना होगा।",
"description": "प्रयोक्ता महत्वपूर्ण खाता है और उसे जारी रखने के लिए एक फ़ाइल जोड़ने की आवश्यकता है"
"description": "User is important an account and needs to add a file to continue"
},
"negativeETH": {
"message": "ईटीएच की नकारात्मक मात्रा नहीं भेज सकते हैं।."
@ -248,7 +248,7 @@
},
"newAccountNumberName": {
"message": "नया खाता $1",
"description": "खाते का खाता बनाने पर अगले खाते का डिफ़ॉल्ट नाम"
"description": "Default name of next account to be created on create account screen"
},
"newContract": {
"message": "नया अनुबंध"
@ -267,7 +267,7 @@
},
"pastePrivateKey": {
"message": "यहां अपनी निजी कुंजी स्ट्रिंग चिपकाएं:",
"description": "किसी निजी कुंजी से किसी खाते को आयात करने के लिए"
"description": "For importing an account from a private key"
},
"personalAddressDetected": {
"message": "व्यक्तिगत पता मिला। टोकन अनुबंध का पता इनपुट।"
@ -277,7 +277,7 @@
},
"privateKey": {
"message": "निजी कुंजी",
"description": "खाता आयात करने के लिए उपयोग करने के लिए इस प्रकार की फ़ाइल का चयन करें"
"description": "select this type of file to use to import an account"
},
"privateKeyWarning": {
"message": "चेतावनी: कभी भी इस कुंजी का खुलासा न करें। आपकी निजी कुंजी वाले कोई भी आपके खाते में रखी किसी भी संपत्ति को चुरा सकता है।"
@ -392,7 +392,7 @@
},
"troubleTokenBalances": {
"message": "मुसीबत... आपके टोकन शेष राशि को लोड करने में हमें परेशानी हुई थी। आप उन्हें देख सकते हैं",
"description": "टोकन शेष देखने के लिए एक लिंक ... (यहां)"
"description": "Followed by a link (here) to view token balances"
},
"typePassword": {
"message": "अपना पासवर्ड टाइप करें"

View File

@ -267,7 +267,7 @@
},
"connectTo": {
"message": "Connettiti a $1",
"description": "$1 is the name/origin of a site/dapp that the user can connect to metamask"
"description": "$1 is the name/origin of a web3 site/application that the user can connect to metamask"
},
"connectToAll": {
"message": "Connettiti a tutti i tuoi $1",
@ -426,7 +426,7 @@
},
"decryptMessageNotice": {
"message": "$1 vorrebbe leggere questo messaggio per completare l'azione",
"description": "$1 is website or dapp name"
"description": "$1 is the web3 site name"
},
"decryptMetamask": {
"message": "Decifra messaggio"
@ -511,7 +511,7 @@
},
"encryptionPublicKeyNotice": {
"message": "$1 vorrebbe la tua chiave di cifratura pubblica. Consentendo, questo sito sarà in grado di comporre messaggi criptati per te.",
"description": "$1 is website or dapp name"
"description": "$1 is the web3 site name"
},
"encryptionPublicKeyRequest": {
"message": "Richiedi chiave pubblica di cifratura"

View File

@ -13,7 +13,7 @@
},
"appDescription": {
"message": "Ethereum Browser Extension",
"description": "Ang deskripsyon ng application"
"description": "The description of the application"
},
"appName": {
"message": "MetaMask",
@ -96,11 +96,11 @@
},
"fiat": {
"message": "FIAT",
"description": "Type ng exchange"
"description": "Exchange type"
},
"fileImportFail": {
"message": "Hindi gumagana ang file import? I-click ito!",
"description": "Tinutulungan ang user na i-import ang kanilang account mula sa JSON file"
"description": "Helps user import their account from a JSON file"
},
"from": {
"message": "Mula sa"
@ -113,11 +113,11 @@
},
"getEtherFromFaucet": {
"message": "Kumuha ng Ether mula sa faucet para sa $1",
"description": "Ipinapakita ang pangalan ng network para sa Ether faucet"
"description": "Displays network name for Ether faucet"
},
"here": {
"message": "i-click ito",
"description": "tulad ng -i-click dito- para sa mas maraming impormasyon (kasama ng troubleTokenBalances)"
"description": "as in -click here- for more information (goes with troubleTokenBalances)"
},
"hide": {
"message": "Itago"
@ -127,14 +127,14 @@
},
"import": {
"message": "I-import",
"description": "Button para i-import ang account mula sa napiling file"
"description": "Button to import an account from a selected file"
},
"importAccount": {
"message": "I-import ang Account"
},
"imported": {
"message": "Na-import na",
"description": "status na nagpapakita na ang account ay lubos na na-load sa keyring"
"description": "status showing that an account has been fully loaded into the keyring"
},
"infoHelp": {
"message": "Impormasyon at Tulong"
@ -159,7 +159,7 @@
},
"needImportFile": {
"message": "Dapat kang pumili ng file para i-import.",
"description": "Ang user ay nag-iimport ng account at kailangan magdagdag ng file upang tumuloy"
"description": "User is important an account and needs to add a file to continue"
},
"newAccount": {
"message": "Bagong Account"
@ -181,7 +181,7 @@
},
"pastePrivateKey": {
"message": "I-paste dito ang iyong private key string:",
"description": "Para sa pag-import ng account mula sa private key"
"description": "For importing an account from a private key"
},
"privateKeyWarning": {
"message": "Babala: Huwag sabihin sa kahit na sino ang key na ito. Maaring makuha at manakaw ng sinumang nakakaalam ng iyong private key ang mga assets sa iyong account."
@ -233,7 +233,7 @@
},
"troubleTokenBalances": {
"message": "Nagkaroon kami ng problema sa paglo-load ng iyong mga balanseng token. Tingnan ito dito ",
"description": "Susundan ng link (dito) para tingnan ang token balances"
"description": "Followed by a link (here) to view token balances"
},
"typePassword": {
"message": "I-type ang iyong Password"

View File

@ -134,7 +134,8 @@
"message": "Блок-эксплорер"
},
"blockExplorerView": {
"message": "Посмотреть аккаунт на $1"
"message": "Посмотреть аккаунт на $1",
"description": "$1 replaced by URL for custom block explorer"
},
"blockiesIdenticon": {
"message": "Использовать Blockies Identicon"
@ -292,7 +293,7 @@
},
"decryptMessageNotice": {
"message": "Для $1 необходимо прочитать это сообщение, чтобы завершить Ваше действие",
"description": "$1 is website or dapp name"
"description": "$1 is the web3 site name"
},
"decryptMetamask": {
"message": "Расшифровать сообщение"
@ -353,7 +354,7 @@
},
"encryptionPublicKeyNotice": {
"message": "$1 запрашивает ваш открытый ключ шифрования. По согласованию, этот сайт сможет создавать для Вас зашифрованные сообщения.",
"description": "$1 is website or dapp name"
"description": "$1 is the web3 site name"
},
"encryptionPublicKeyRequest": {
"message": "Запрос публичного ключа шифрования"

View File

@ -25,11 +25,11 @@
},
"appDescription": {
"message": "எதெரியும் பிரௌசர் நீட்டிப்பு",
"description": "பயன்பாட்டின் விளக்கம்"
"description": "The description of the application"
},
"appName": {
"message": "மேடமஸ்க் ",
"description": "பயன்பாட்டின் பெயர்"
"description": "The name of the application"
},
"approve": {
"message": "ஒப்புதல்"
@ -168,11 +168,11 @@
},
"fiat": {
"message": "FIAT",
"description": "பரிமாற்ற வகை"
"description": "Exchange type"
},
"fileImportFail": {
"message": "கோப்பு இறக்குமதி வேலை செய்யவில்லையா? இங்கே கிளிக் செய்யவும்!",
"description": "JSON கோப்பில் பயனர் கணக்கை தங்கள் கணக்கை இறக்குமதி செய்ய உதவுகிறது"
"description": "Helps user import their account from a JSON file"
},
"forgetDevice": {
"message": "இந்தச் சாதனத்தை அகற்று"
@ -194,14 +194,14 @@
},
"getEtherFromFaucet": {
"message": "$ 1 க்கு ஒரு குழாய் இருந்து ஈதர் கிடைக்கும்$1",
"description": "ஈத்தர் குழாய் ஐந்து பிணைய பெயர் காட்டுகிறது"
"description": "Displays network name for Ether faucet"
},
"getStarted": {
"message": "தொடங்குக"
},
"here": {
"message": "இங்கே",
"description": "இங்கே-கிளிக் செய்யவும்- மேலும் தகவலுக்கு (troubleTokenBalances செல்கிறது)"
"description": "as in -click here- for more information (goes with troubleTokenBalances)"
},
"hide": {
"message": "மறை"
@ -249,7 +249,7 @@
},
"jsonFile": {
"message": "JSON கோப்பு",
"description": "ஒரு கணக்கை இறக்குமதி செய்ய வடிவமைக்கப்பட்டுள்ளது"
"description": "format for importing an account"
},
"kovan": {
"message": "கோவன் டெஸ்ட் நெட்வொர்க்"
@ -298,7 +298,7 @@
},
"needImportFile": {
"message": "இறக்குமதி செய்ய ஒரு கோப்பை நீங்கள் தேர்ந்தெடுக்க வேண்டும்.",
"description": "பயனர் ஒரு கணக்கு முக்கியம் மற்றும் தொடர ஒரு கோப்பு சேர்க்க வேண்டும்"
"description": "User is important an account and needs to add a file to continue"
},
"negativeETH": {
"message": "ETH எதிர்மறை அளவுகளை அனுப்ப முடியாது."
@ -311,7 +311,7 @@
},
"newAccountNumberName": {
"message": "கணக்கு $ 1",
"description": "கணக்கு கணக்கை உருவாக்குவதற்கு அடுத்த கணக்கின் இயல்புநிலை பெயர் உருவாக்கப்படும்"
"description": "Default name of next account to be created on create account screen"
},
"newContract": {
"message": "புதிய ஒப்பந்தம்"
@ -351,7 +351,7 @@
},
"pastePrivateKey": {
"message": "இங்கே உங்கள் தனிப்பட்ட விசை சரத்தை ஒட்டுக:",
"description": "ஒரு தனிப்பட்ட விசை ஒரு கணக்கை இறக்குமதி செய்ய"
"description": "For importing an account from a private key"
},
"personalAddressDetected": {
"message": "தனிப்பட்ட முகவரி கண்டறியப்பட்டது. டோக்கன் ஒப்பந்த முகவரியை உள்ளிடவும்."
@ -361,7 +361,7 @@
},
"privateKey": {
"message": "தனிப்பட்ட விசை",
"description": "ஒரு கணக்கை இறக்குமதி செய்ய பயன்படுத்த இந்த வகை கோப்பை தேர்ந்தெடுக்கவும்"
"description": "select this type of file to use to import an account"
},
"privateKeyWarning": {
"message": "எச்சரிக்கை: இந்த விசையை எப்போதும் வெளியிட வேண்டாம். உங்கள் தனிப்பட்ட விசைகளைக் கொண்ட எவரும் உங்கள் கணக்கில் உள்ள எந்த சொத்துக்களையும் திருடலாம்."
@ -509,7 +509,7 @@
},
"troubleTokenBalances": {
"message": "உங்கள் டோக்கன் நிலுவைகளை ஏற்றுவதில் சிக்கல் ஏற்பட்டது. நீங்கள் அவர்களை பார்க்க முடியும்.",
"description": "டோக்கன் நிலுவைகளை காண ஒரு இணைப்பு (இங்கே) தொடர்ந்து"
"description": "Followed by a link (here) to view token balances"
},
"tryAgain": {
"message": "மீண்டும் முயல்க"

View File

@ -78,6 +78,6 @@
"notifications"
],
"short_name": "__MSG_appName__",
"version": "9.0.3",
"version": "9.0.4",
"web_accessible_resources": ["inpage.js", "phishing.html"]
}

View File

@ -2,7 +2,7 @@
"applications": {
"gecko": {
"id": "webextension@metamask.io",
"strict_min_version": "56.0"
"strict_min_version": "68.0"
}
}
}

View File

@ -19,6 +19,12 @@ import extension from 'extensionizer'
import { storeAsStream, storeTransformStream } from '@metamask/obs-store'
import PortStream from 'extension-port-stream'
import { captureException } from '@sentry/browser'
import {
ENVIRONMENT_TYPE_POPUP,
ENVIRONMENT_TYPE_NOTIFICATION,
ENVIRONMENT_TYPE_FULLSCREEN,
} from '../../shared/constants/app'
import migrations from './migrations'
import Migrator from './lib/migrator'
import ExtensionPlatform from './platforms/extension'
@ -31,12 +37,6 @@ import rawFirstTimeState from './first-time-state'
import getFirstPreferredLangCode from './lib/get-first-preferred-lang-code'
import getObjStructure from './lib/getObjStructure'
import setupEnsIpfsResolver from './lib/ens-ipfs/setup'
import {
ENVIRONMENT_TYPE_POPUP,
ENVIRONMENT_TYPE_NOTIFICATION,
ENVIRONMENT_TYPE_FULLSCREEN,
} from './lib/enums'
/* eslint-enable import/first */
const { sentry } = global

View File

@ -2,12 +2,11 @@ import Web3 from 'web3'
import contracts from '@metamask/contract-metadata'
import { warn } from 'loglevel'
import SINGLE_CALL_BALANCES_ABI from 'single-call-balance-checker-abi'
import { MAINNET_CHAIN_ID } from './network/enums'
import { MAINNET_CHAIN_ID } from '../../../shared/constants/network'
import { SINGLE_CALL_BALANCES_ADDRESS } from '../constants/contracts'
// By default, poll every 3 minutes
const DEFAULT_INTERVAL = 180 * 1000
const SINGLE_CALL_BALANCES_ADDRESS =
'0xb1f8e55c7f64d203c1400b9d8555d050f94adf39'
/**
* A controller that polls for token exchange

View File

@ -3,7 +3,7 @@ import log from 'loglevel'
import BN from 'bn.js'
import createId from '../lib/random-id'
import { bnToHex } from '../lib/util'
import fetchWithTimeout from '../lib/fetch-with-timeout'
import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout'
import {
TRANSACTION_CATEGORIES,
@ -22,11 +22,9 @@ import {
RINKEBY_CHAIN_ID,
ROPSTEN,
ROPSTEN_CHAIN_ID,
} from './network/enums'
} from '../../../shared/constants/network'
const fetch = fetchWithTimeout({
timeout: 30000,
})
const fetchWithTimeout = getFetchWithTimeout(30000)
/**
* This controller is responsible for retrieving incoming transactions. Etherscan is polled once every block to check
@ -227,7 +225,7 @@ export default class IncomingTransactionsController {
if (fromBlock) {
url += `&startBlock=${parseInt(fromBlock, 10)}`
}
const response = await fetch(url)
const response = await fetchWithTimeout(url)
const parsedResponse = await response.json()
return {

View File

@ -1,7 +1,7 @@
import { merge, omit } from 'lodash'
import { ObservableStore } from '@metamask/obs-store'
import { bufferToHex, sha3 } from 'ethereumjs-util'
import { ENVIRONMENT_TYPE_BACKGROUND } from '../lib/enums'
import { ENVIRONMENT_TYPE_BACKGROUND } from '../../../shared/constants/app'
import {
METAMETRICS_ANONYMOUS_ID,
METAMETRICS_BACKGROUND_PAGE_OBJECT,

View File

@ -8,7 +8,7 @@ import providerFromMiddleware from 'eth-json-rpc-middleware/providerFromMiddlewa
import createInfuraMiddleware from 'eth-json-rpc-infura'
import BlockTracker from 'eth-block-tracker'
import { NETWORK_TYPE_TO_ID_MAP } from './enums'
import { NETWORK_TYPE_TO_ID_MAP } from '../../../../shared/constants/network'
export default function createInfuraClient({ network, projectId }) {
const infuraMiddleware = createInfuraMiddleware({

View File

@ -9,25 +9,29 @@ import {
createEventEmitterProxy,
} from 'swappable-obj-proxy'
import EthQuery from 'eth-query'
import createMetamaskMiddleware from './createMetamaskMiddleware'
import createInfuraClient from './createInfuraClient'
import createJsonRpcClient from './createJsonRpcClient'
import {
RINKEBY,
MAINNET,
INFURA_PROVIDER_TYPES,
NETWORK_TYPE_RPC,
NETWORK_TYPE_TO_ID_MAP,
MAINNET_CHAIN_ID,
RINKEBY_CHAIN_ID,
} from './enums'
} from '../../../../shared/constants/network'
import {
isPrefixedFormattedHexString,
isSafeChainId,
} from '../../../../shared/modules/utils'
import createMetamaskMiddleware from './createMetamaskMiddleware'
import createInfuraClient from './createInfuraClient'
import createJsonRpcClient from './createJsonRpcClient'
const env = process.env.METAMASK_ENV
let defaultProviderConfigOpts
if (process.env.IN_TEST === 'true') {
defaultProviderConfigOpts = {
type: 'rpc',
type: NETWORK_TYPE_RPC,
rpcUrl: 'http://localhost:8545',
chainId: '0x539',
nickname: 'Localhost 8545',
@ -160,8 +164,16 @@ export default class NetworkController extends EventEmitter {
}
setRpcTarget(rpcUrl, chainId, ticker = 'ETH', nickname = '', rpcPrefs) {
assert.ok(
isPrefixedFormattedHexString(chainId),
`Invalid chain ID "${chainId}": invalid hex string.`,
)
assert.ok(
isSafeChainId(parseInt(chainId, 16)),
`Invalid chain ID "${chainId}": numerical value greater than max safe value.`,
)
this.setProviderConfig({
type: 'rpc',
type: NETWORK_TYPE_RPC,
rpcUrl,
chainId,
ticker,
@ -171,14 +183,14 @@ export default class NetworkController extends EventEmitter {
}
async setProviderType(type, rpcUrl = '', ticker = 'ETH', nickname = '') {
assert.notEqual(
assert.notStrictEqual(
type,
'rpc',
`NetworkController - cannot call "setProviderType" with type 'rpc'. use "setRpcTarget"`,
NETWORK_TYPE_RPC,
`NetworkController - cannot call "setProviderType" with type "${NETWORK_TYPE_RPC}". Use "setRpcTarget"`,
)
assert(
assert.ok(
INFURA_PROVIDER_TYPES.includes(type),
`NetworkController - Unknown rpc type "${type}"`,
`Unknown Infura provider type "${type}".`,
)
const { chainId } = NETWORK_TYPE_TO_ID_MAP[type]
this.setProviderConfig({ type, rpcUrl, chainId, ticker, nickname })
@ -209,7 +221,7 @@ export default class NetworkController extends EventEmitter {
getNetworkIdentifier() {
const provider = this.providerStore.getState()
return provider.type === 'rpc' ? provider.rpcUrl : provider.type
return provider.type === NETWORK_TYPE_RPC ? provider.rpcUrl : provider.type
}
//
@ -228,7 +240,7 @@ export default class NetworkController extends EventEmitter {
if (isInfura) {
this._configureInfuraProvider(type, this._infuraProjectId)
// url-based rpc endpoints
} else if (type === 'rpc') {
} else if (type === NETWORK_TYPE_RPC) {
this._configureStandardProvider(rpcUrl, chainId)
} else {
throw new Error(

View File

@ -1,4 +1,4 @@
import { NETWORK_TO_NAME_MAP } from './enums'
import { NETWORK_TO_NAME_MAP } from '../../../../shared/constants/network'
export const getNetworkDisplayName = (key) => NETWORK_TO_NAME_MAP[key]

View File

@ -10,11 +10,6 @@ export const METADATA_STORE_KEY = 'domainMetadata'
export const METADATA_CACHE_MAX_SIZE = 100
export const CAVEAT_NAMES = {
exposedAccounts: 'exposedAccounts',
primaryAccountOnly: 'primaryAccountOnly',
}
export const CAVEAT_TYPES = {
limitResponseLength: 'limitResponseLength',
filterResponse: 'filterResponse',

View File

@ -3,13 +3,10 @@ import { JsonRpcEngine } from 'json-rpc-engine'
import { ObservableStore } from '@metamask/obs-store'
import log from 'loglevel'
import { CapabilitiesController as RpcCap } from 'rpc-cap'
import { ethErrors } from 'eth-json-rpc-errors'
import { ethErrors } from 'eth-rpc-errors'
import { cloneDeep } from 'lodash'
import createPermissionsMethodMiddleware from './permissionsMethodMiddleware'
import PermissionsLogController from './permissionsLog'
// Methods that do not require any permissions to use:
import { CAVEAT_NAMES } from '../../../../shared/constants/permissions'
import {
APPROVAL_TYPE,
SAFE_METHODS, // methods that do not require any permissions to use
@ -18,11 +15,13 @@ import {
METADATA_CACHE_MAX_SIZE,
LOG_STORE_KEY,
HISTORY_STORE_KEY,
CAVEAT_NAMES,
NOTIFICATION_NAMES,
CAVEAT_TYPES,
} from './enums'
import createPermissionsMethodMiddleware from './permissionsMethodMiddleware'
import PermissionsLogController from './permissionsLog'
// instanbul ignore next
const noop = () => undefined

View File

@ -1,6 +1,6 @@
import { cloneDeep } from 'lodash'
import { CAVEAT_NAMES } from '../../../../shared/constants/permissions'
import {
CAVEAT_NAMES,
HISTORY_STORE_KEY,
LOG_IGNORE_METHODS,
LOG_LIMIT,

View File

@ -1,5 +1,5 @@
import { createAsyncMiddleware } from 'json-rpc-engine'
import { ethErrors } from 'eth-json-rpc-errors'
import { ethErrors } from 'eth-rpc-errors'
/**
* Create middleware for handling certain methods and preprocessing permissions requests.

View File

@ -1,13 +1,13 @@
import { strict as assert } from 'assert'
import { ObservableStore } from '@metamask/obs-store'
import { ethErrors } from 'eth-json-rpc-errors'
import { ethErrors } from 'eth-rpc-errors'
import { normalize as normalizeAddress } from 'eth-sig-util'
import { isValidAddress } from 'ethereumjs-util'
import ethers from 'ethers'
import log from 'loglevel'
import { isPrefixedFormattedHexString } from '../lib/util'
import { LISTED_CONTRACT_ADDRESSES } from '../../../shared/constants/tokens'
import { NETWORK_TYPE_TO_ID_MAP } from './network/enums'
import { NETWORK_TYPE_TO_ID_MAP } from '../../../shared/constants/network'
import { isPrefixedFormattedHexString } from '../../../shared/modules/utils'
export default class PreferencesController {
/**

View File

@ -2,6 +2,9 @@ import { ObservableStore } from '@metamask/obs-store'
import log from 'loglevel'
import { normalize as normalizeAddress } from 'eth-sig-util'
import ethUtil from 'ethereumjs-util'
import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout'
const fetchWithTimeout = getFetchWithTimeout(30000)
// By default, poll every 3 minutes
const DEFAULT_INTERVAL = 180 * 1000
@ -34,7 +37,7 @@ export default class TokenRatesController {
const query = `contract_addresses=${pairs}&vs_currencies=${nativeCurrency}`
if (this._tokens.length > 0) {
try {
const response = await window.fetch(
const response = await fetchWithTimeout(
`https://api.coingecko.com/api/v3/simple/token_price/ethereum?${query}`,
)
const prices = await response.json()

View File

@ -3,7 +3,7 @@ import { ObservableStore } from '@metamask/obs-store'
import ethUtil from 'ethereumjs-util'
import Transaction from 'ethereumjs-tx'
import EthQuery from 'ethjs-query'
import { ethErrors } from 'eth-json-rpc-errors'
import { ethErrors } from 'eth-rpc-errors'
import abi from 'human-standard-token-abi'
import { ethers } from 'ethers'
import NonceTracker from 'nonce-tracker'

View File

@ -1,5 +1,5 @@
import { isValidAddress } from 'ethereumjs-util'
import { ethErrors } from 'eth-json-rpc-errors'
import { ethErrors } from 'eth-rpc-errors'
import { addHexPrefix } from '../../../lib/util'
import { TRANSACTION_STATUSES } from '../../../../../shared/constants/transaction'

View File

@ -19,14 +19,14 @@ import {
RINKEBY_CHAIN_ID,
ROPSTEN_CHAIN_ID,
KOVAN_CHAIN_ID,
} from '../controllers/network/enums'
} from '../../../shared/constants/network'
import {
SINGLE_CALL_BALANCES_ADDRESS,
SINGLE_CALL_BALANCES_ADDRESS_RINKEBY,
SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN,
SINGLE_CALL_BALANCES_ADDRESS_KOVAN,
} from '../controllers/network/contract-addresses'
} from '../constants/contracts'
import { bnToHex } from './util'
/**

View File

@ -1,11 +1,11 @@
import EventEmitter from 'events'
import { ObservableStore } from '@metamask/obs-store'
import ethUtil from 'ethereumjs-util'
import { ethErrors } from 'eth-json-rpc-errors'
import { ethErrors } from 'eth-rpc-errors'
import log from 'loglevel'
import { MESSAGE_TYPE } from '../../../shared/constants/app'
import { addHexPrefix } from './util'
import createId from './random-id'
import { MESSAGE_TYPE } from './enums'
const hexRe = /^[0-9A-Fa-f]+$/gu

View File

@ -1,9 +1,9 @@
import EventEmitter from 'events'
import { ObservableStore } from '@metamask/obs-store'
import { ethErrors } from 'eth-json-rpc-errors'
import { ethErrors } from 'eth-rpc-errors'
import log from 'loglevel'
import { MESSAGE_TYPE } from '../../../shared/constants/app'
import createId from './random-id'
import { MESSAGE_TYPE } from './enums'
/**
* Represents, and contains data about, an 'eth_getEncryptionPublicKey' type request. These are created when

View File

@ -1,6 +1,9 @@
import extension from 'extensionizer'
import getFetchWithTimeout from '../../../../shared/modules/fetch-with-timeout'
import resolveEnsToIpfsContentId from './resolver'
const fetchWithTimeout = getFetchWithTimeout(30000)
const supportedTopLevelDomains = ['eth']
export default function setupEnsIpfsResolver({
@ -55,7 +58,9 @@ export default function setupEnsIpfsResolver({
)}.${ipfsGateway}${pathname}${search || ''}${fragment || ''}`
try {
// check if ipfs gateway has result
const response = await window.fetch(resolvedUrl, { method: 'HEAD' })
const response = await fetchWithTimeout(resolvedUrl, {
method: 'HEAD',
})
if (response.status === 200) {
url = resolvedUrl
}

View File

@ -1,9 +1,9 @@
import EventEmitter from 'events'
import { ObservableStore } from '@metamask/obs-store'
import ethUtil from 'ethereumjs-util'
import { ethErrors } from 'eth-json-rpc-errors'
import { ethErrors } from 'eth-rpc-errors'
import { MESSAGE_TYPE } from '../../../shared/constants/app'
import createId from './random-id'
import { MESSAGE_TYPE } from './enums'
/**
* Represents, and contains data about, an 'eth_sign' type signature request. These are created when a signature for

View File

@ -1,4 +1,7 @@
import log from 'loglevel'
import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout'
const fetchWithTimeout = getFetchWithTimeout(30000)
const FIXTURE_SERVER_HOST = 'localhost'
const FIXTURE_SERVER_PORT = 12345
@ -24,7 +27,7 @@ export default class ReadOnlyNetworkStore {
*/
async _init() {
try {
const response = await window.fetch(FIXTURE_SERVER_URL)
const response = await fetchWithTimeout(FIXTURE_SERVER_URL)
if (response.ok) {
this._state = await response.json()
}

View File

@ -1,11 +1,11 @@
import EventEmitter from 'events'
import { ObservableStore } from '@metamask/obs-store'
import ethUtil from 'ethereumjs-util'
import { ethErrors } from 'eth-json-rpc-errors'
import { ethErrors } from 'eth-rpc-errors'
import log from 'loglevel'
import { MESSAGE_TYPE } from '../../../shared/constants/app'
import { addHexPrefix } from './util'
import createId from './random-id'
import { MESSAGE_TYPE } from './enums'
const hexRe = /^[0-9A-Fa-f]+$/gu

View File

@ -1,4 +1,4 @@
import { MESSAGE_TYPE } from '../../enums'
import { MESSAGE_TYPE } from '../../../../../shared/constants/app'
/**
* This RPC method gets background state relevant to the provider.

View File

@ -1,4 +1,4 @@
import { MESSAGE_TYPE } from '../../enums'
import { MESSAGE_TYPE } from '../../../../../shared/constants/app'
/**
* This RPC method is called by the inpage provider whenever it detects the
@ -40,16 +40,18 @@ function logWeb3ShimUsageHandler(
if (getWeb3ShimUsageState(origin) === undefined) {
setWeb3ShimUsageRecorded(origin)
sendMetrics({
event: `Website Accessed window.web3 Shim`,
category: 'inpage_provider',
eventContext: {
sendMetrics(
{
event: `Website Accessed window.web3 Shim`,
category: 'inpage_provider',
referrer: {
url: origin,
},
},
excludeMetaMetricsId: true,
})
{
excludeMetaMetricsId: true,
},
)
}
res.result = true

View File

@ -1,4 +1,4 @@
import { MESSAGE_TYPE } from '../../enums'
import { MESSAGE_TYPE } from '../../../../../shared/constants/app'
const watchAsset = {
methodNames: [MESSAGE_TYPE.WATCH_ASSET, MESSAGE_TYPE.WATCH_ASSET_LEGACY],

View File

@ -1,13 +1,13 @@
import EventEmitter from 'events'
import assert from 'assert'
import { ObservableStore } from '@metamask/obs-store'
import { ethErrors } from 'eth-json-rpc-errors'
import { ethErrors } from 'eth-rpc-errors'
import { typedSignatureHash, TYPED_MESSAGE_SCHEMA } from 'eth-sig-util'
import { isValidAddress } from 'ethereumjs-util'
import log from 'loglevel'
import jsonschema from 'jsonschema'
import { MESSAGE_TYPE } from '../../../shared/constants/app'
import createId from './random-id'
import { MESSAGE_TYPE } from './enums'
/**
* Represents, and contains data about, an 'eth_signTypedData' type signature request. These are created when a

View File

@ -14,7 +14,7 @@ import {
PLATFORM_CHROME,
PLATFORM_EDGE,
PLATFORM_BRAVE,
} from './enums'
} from '../../../shared/constants/app'
/**
* @see {@link getEnvironmentType}
@ -147,21 +147,6 @@ function checkForError() {
return new Error(lastError.message)
}
/**
* Checks whether the given value is a 0x-prefixed, non-zero, non-zero-padded,
* hexadecimal string.
*
* @param {any} value - The value to check.
* @returns {boolean} True if the value is a correctly formatted hex string,
* false otherwise.
*/
function isPrefixedFormattedHexString(value) {
if (typeof value !== 'string') {
return false
}
return /^0x[1-9a-f]+[0-9a-f]*$/iu.test(value)
}
/**
* Prefixes a hex string with '0x' or '-0x' and returns it. Idempotent.
*
@ -202,7 +187,6 @@ export {
hexToBn,
BnMultiplyByFraction,
checkForError,
isPrefixedFormattedHexString,
addHexPrefix,
bnToHex,
}

View File

@ -2402,13 +2402,6 @@ export default class MetamaskController extends EventEmitter {
nickname,
rpcPrefs,
) {
await this.preferencesController.updateRpc({
rpcUrl,
chainId,
ticker,
nickname,
rpcPrefs,
})
this.networkController.setRpcTarget(
rpcUrl,
chainId,
@ -2416,6 +2409,13 @@ export default class MetamaskController extends EventEmitter {
nickname,
rpcPrefs,
)
await this.preferencesController.updateRpc({
rpcUrl,
chainId,
ticker,
nickname,
rpcPrefs,
})
return rpcUrl
}

View File

@ -1,5 +1,5 @@
import { cloneDeep } from 'lodash'
import { NETWORK_TYPE_TO_ID_MAP } from '../controllers/network/enums'
import { NETWORK_TYPE_TO_ID_MAP } from '../../../shared/constants/network'
const version = 51

View File

@ -1,7 +1,7 @@
import extension from 'extensionizer'
import { createExplorerLink as explorerLink } from '@metamask/etherscan-link'
import { getEnvironmentType, checkForError } from '../lib/util'
import { ENVIRONMENT_TYPE_BACKGROUND } from '../lib/enums'
import { ENVIRONMENT_TYPE_BACKGROUND } from '../../../shared/constants/app'
import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction'
export default class ExtensionPlatform {

View File

@ -12,12 +12,12 @@ import EthQuery from 'eth-query'
import StreamProvider from 'web3-stream-provider'
import log from 'loglevel'
import launchMetaMaskUi from '../../ui'
import ExtensionPlatform from './platforms/extension'
import { setupMultiplex } from './lib/stream-utils'
import {
ENVIRONMENT_TYPE_FULLSCREEN,
ENVIRONMENT_TYPE_POPUP,
} from './lib/enums'
} from '../../shared/constants/app'
import ExtensionPlatform from './platforms/extension'
import { setupMultiplex } from './lib/stream-utils'
import { getEnvironmentType } from './lib/util'
start().catch(log.error)

View File

@ -16,6 +16,6 @@ Passing messages from popup to background script
window.addEventListener('message', event => {
if (port && event.source === window && event.data) {
port.postMessage(event.data);
port.postMessage({ data: event.data });
}
});

View File

@ -6,7 +6,7 @@ module.exports = function (api) {
'@babel/preset-env',
{
targets: {
browsers: ['chrome >= 63', 'firefox >= 56.2'],
browsers: ['chrome >= 63', 'firefox >= 68'],
},
},
],

View File

@ -375,12 +375,10 @@ function createScriptTasks({ browserPlatforms, livereload }) {
METAMASK_DEBUG: opts.devMode,
METAMASK_ENVIRONMENT: environment,
METAMASK_VERSION: baseManifest.version,
METAMETRICS_PROJECT_ID: process.env.METAMETRICS_PROJECT_ID,
NODE_ENV: opts.devMode ? 'development' : 'production',
IN_TEST: opts.testing ? 'true' : false,
PUBNUB_SUB_KEY: process.env.PUBNUB_SUB_KEY || '',
PUBNUB_PUB_KEY: process.env.PUBNUB_PUB_KEY || '',
ETH_GAS_STATION_API_KEY: process.env.ETH_GAS_STATION_API_KEY || '',
CONF: opts.devMode ? conf : {},
SENTRY_DSN: process.env.SENTRY_DSN,
INFURA_PROJECT_ID: opts.testing

View File

@ -1,7 +1,7 @@
const pify = require('pify')
const gulp = require('gulp')
const sass = require('gulp-sass')
sass.compiler = require('node-sass')
sass.compiler = require('sass')
const autoprefixer = require('gulp-autoprefixer')
const gulpStylelint = require('gulp-stylelint')
const watch = require('gulp-watch')

View File

@ -0,0 +1,44 @@
const path = require('path')
const fs = require('fs')
const { promisify } = require('util')
const log = require('loglevel')
const readFile = promisify(fs.readFile)
function getLocalePath(code) {
return path.resolve(
__dirname,
'..',
'..',
'app',
'_locales',
code,
'messages.json',
)
}
async function getLocale(code) {
try {
const localeFilePath = getLocalePath(code)
const fileContents = await readFile(localeFilePath, 'utf8')
return JSON.parse(fileContents)
} catch (e) {
if (e.code === 'ENOENT') {
log.error('Locale file not found')
} else {
log.error(`Error opening your locale ("${code}") file: `, e)
}
process.exit(1)
}
}
function compareLocalesForMissingItems({ base, subject }) {
return Object.keys(base).filter((key) => !subject[key])
}
module.exports = {
compareLocalesForMissingItems,
getLocale,
getLocalePath,
}

View File

@ -0,0 +1,78 @@
// //////////////////////////////////////////////////////////////////////////////
//
// Reports on missing localized strings
//
// usage:
//
// node app/scripts/missing-locale-strings.js [<locale>] [--verbose]
//
// This script will report on any missing localized strings. It will compare the
// chosen locale (or all locales, if none is chosen) with the `en` locale, and
// report the coverage percentage.
//
// The optional '--verbose' argument will print the key for each localized string
// to the console.
//
// //////////////////////////////////////////////////////////////////////////////
const log = require('loglevel')
const localeIndex = require('../app/_locales/index.json')
const { compareLocalesForMissingItems, getLocale } = require('./lib/locales')
log.setDefaultLevel('info')
let verbose = false
let specifiedLocale
for (const arg of process.argv.slice(2)) {
if (arg === '--verbose') {
verbose = true
} else {
specifiedLocale = arg
}
}
main().catch((error) => {
log.error(error)
process.exit(1)
})
async function main() {
if (specifiedLocale === 'en') {
throw new Error(
`Can't compare 'en' locale to itself to find missing messages`,
)
} else if (specifiedLocale) {
await reportMissingMessages(specifiedLocale)
} else {
const localeCodes = localeIndex
.filter((localeMeta) => localeMeta.code !== 'en')
.map((localeMeta) => localeMeta.code)
for (const code of localeCodes) {
await reportMissingMessages(code)
}
}
}
async function reportMissingMessages(code) {
const englishLocale = await getLocale('en')
const targetLocale = await getLocale(code)
const missingItems = compareLocalesForMissingItems({
base: englishLocale,
subject: targetLocale,
})
const englishEntryCount = Object.keys(englishLocale).length
const coveragePercent =
(100 * (englishEntryCount - missingItems.length)) / englishEntryCount
log.info(`**${code}**: ${coveragePercent.toFixed(2)}% coverage`)
if (missingItems.length && verbose) {
console.log(`**${code}**: ${missingItems.length} missing messages`)
log.info('Extra items that should not be localized:')
missingItems.forEach(function (key) {
log.info(` - [ ] ${key}`)
})
}
}

View File

@ -10,13 +10,12 @@
// the English locale against string literals found under `ui/`, and it will check
// other locales by comparing them to the English locale.
//
// A report will be printed to the console detailing any unused locales, and also
// any missing messages in the non-English locales.
// A report will be printed to the console detailing any unused messages.
//
// The if the optional '--fix' parameter is given, locales will be automatically
// The if the optional '--fix' argument is given, locales will be automatically
// updated to remove any unused messages.
//
// The optional '--quiet' parameter reduces the verbosity of the output, printing
// The optional '--quiet' argument reduces the verbosity of the output, printing
// just a single summary of results for each locale verified
//
// //////////////////////////////////////////////////////////////////////////////
@ -27,6 +26,11 @@ const { promisify } = require('util')
const log = require('loglevel')
const matchAll = require('string.prototype.matchall').getPolyfill()
const localeIndex = require('../app/_locales/index.json')
const {
compareLocalesForMissingItems,
getLocale,
getLocalePath,
} = require('./lib/locales')
const readdir = promisify(fs.readdir)
const readFile = promisify(fs.readFile)
@ -54,15 +58,21 @@ main().catch((error) => {
async function main() {
if (specifiedLocale) {
log.info(`Verifying selected locale "${specifiedLocale}":\n`)
const locale = localeIndex.find(
const localeEntry = localeIndex.find(
(localeMeta) => localeMeta.code === specifiedLocale,
)
if (!localeEntry) {
throw new Error(`No localize entry found for ${specifiedLocale}`)
}
const failed =
locale.code === 'en'
specifiedLocale === 'en'
? await verifyEnglishLocale()
: await verifyLocale(locale)
: await verifyLocale(specifiedLocale)
if (failed) {
process.exit(1)
} else {
console.log('No invalid entries!')
}
} else {
log.info('Verifying all locales:\n')
@ -72,33 +82,15 @@ async function main() {
.map((localeMeta) => localeMeta.code)
for (const code of localeCodes) {
log.info() // Separate each locale report by a newline when not in '--quiet' mode
const localeFailed = await verifyLocale(code, fix)
failed = failed || localeFailed
}
if (failed) {
process.exit(1)
}
}
}
function getLocalePath(code) {
return path.resolve(__dirname, '..', 'app', '_locales', code, 'messages.json')
}
async function getLocale(code) {
try {
const localeFilePath = getLocalePath(code)
const fileContents = await readFile(localeFilePath, 'utf8')
return JSON.parse(fileContents)
} catch (e) {
if (e.code === 'ENOENT') {
log.error('Locale file not found')
} else {
log.error(`Error opening your locale ("${code}") file: `, e)
console.log('No invalid entries!')
}
process.exit(1)
}
}
@ -128,14 +120,6 @@ async function verifyLocale(code) {
base: targetLocale,
subject: englishLocale,
})
const missingItems = compareLocalesForMissingItems({
base: englishLocale,
subject: targetLocale,
})
const englishEntryCount = Object.keys(englishLocale).length
const coveragePercent =
(100 * (englishEntryCount - missingItems.length)) / englishEntryCount
if (extraItems.length) {
console.log(`**${code}**: ${extraItems.length} unused messages`)
@ -143,20 +127,6 @@ async function verifyLocale(code) {
extraItems.forEach(function (key) {
log.info(` - [ ] ${key}`)
})
} else {
log.info(`**${code}**: ${extraItems.length} unused messages`)
}
log.info(`${coveragePercent.toFixed(2)}% coverage`)
if (missingItems.length) {
log.info(`Missing items not present in localized file:`)
missingItems.forEach(function (key) {
log.info(` - [ ] ${key}`)
})
}
if (!extraItems.length && !missingItems.length) {
log.info('Full coverage : )')
}
if (extraItems.length > 0) {
@ -230,7 +200,6 @@ async function verifyEnglishLocale() {
}
if (!unusedMessages.length && !templateUsage.length) {
log.info('Full coverage : )')
return false // failed === false
}
@ -265,7 +234,3 @@ async function* getFileContents(filenames) {
yield readFile(filename, 'utf8')
}
}
function compareLocalesForMissingItems({ base, subject }) {
return Object.keys(base).filter((key) => !subject[key])
}

View File

@ -76,7 +76,7 @@
"@formatjs/intl-relativetimeformat": "^5.2.6",
"@fortawesome/fontawesome-free": "^5.13.0",
"@material-ui/core": "^4.11.0",
"@metamask/contract-metadata": "^1.21.0",
"@metamask/contract-metadata": "^1.22.0",
"@metamask/controllers": "^5.1.0",
"@metamask/eth-ledger-bridge-keyring": "^0.2.6",
"@metamask/eth-token-tracker": "^3.0.1",
@ -86,7 +86,7 @@
"@metamask/logo": "^2.5.0",
"@metamask/obs-store": "^5.0.0",
"@popperjs/core": "^2.4.0",
"@reduxjs/toolkit": "^1.3.2",
"@reduxjs/toolkit": "^1.5.0",
"@sentry/browser": "^5.26.0",
"@sentry/integrations": "^5.26.0",
"@zxing/library": "^0.8.0",
@ -105,7 +105,6 @@
"end-of-stream": "^1.4.4",
"eth-block-tracker": "^4.4.2",
"eth-ens-namehash": "^2.0.8",
"eth-json-rpc-errors": "^2.0.2",
"eth-json-rpc-filters": "^4.2.1",
"eth-json-rpc-infura": "^5.1.0",
"eth-json-rpc-middleware": "^6.0.0",
@ -113,8 +112,9 @@
"eth-method-registry": "^2.0.0",
"eth-phishing-detect": "^1.1.14",
"eth-query": "^2.1.2",
"eth-rpc-errors": "^4.0.2",
"eth-sig-util": "^3.0.0",
"eth-trezor-keyring": "^0.4.0",
"eth-trezor-keyring": "^0.5.2",
"ethereum-ens-network-map": "^1.0.2",
"ethereumjs-abi": "^0.6.4",
"ethereumjs-tx": "1.3.7",
@ -246,7 +246,7 @@
"gulp-rename": "^2.0.0",
"gulp-replace": "^1.0.0",
"gulp-rtlcss": "^1.4.0",
"gulp-sass": "^4.0.0",
"gulp-sass": "^4.1.0",
"gulp-sourcemaps": "^2.6.0",
"gulp-stylelint": "^13.0.0",
"gulp-terser-js": "^5.2.2",
@ -259,7 +259,6 @@
"mocha": "^7.2.0",
"nock": "^9.0.14",
"node-fetch": "^2.6.1",
"node-sass": "^4.14.1",
"nyc": "^15.0.0",
"polyfill-crypto.getrandomvalues": "^1.0.0",
"prettier": "^2.1.1",
@ -275,7 +274,8 @@
"remote-redux-devtools": "^0.5.16",
"remotedev-server": "^0.3.1",
"resolve-url-loader": "^3.1.2",
"sass-loader": "^7.0.1",
"sass": "^1.32.4",
"sass-loader": "^10.1.1",
"selenium-webdriver": "4.0.0-alpha.7",
"serve-handler": "^6.1.2",
"ses": "0.11.0",

View File

@ -6,19 +6,18 @@
* background - The background process that powers the extension
* @typedef {'popup' | 'notification' | 'fullscreen' | 'background'} EnvironmentType
*/
export const ENVIRONMENT_TYPE_POPUP = 'popup'
export const ENVIRONMENT_TYPE_NOTIFICATION = 'notification'
export const ENVIRONMENT_TYPE_FULLSCREEN = 'fullscreen'
export const ENVIRONMENT_TYPE_BACKGROUND = 'background'
const ENVIRONMENT_TYPE_POPUP = 'popup'
const ENVIRONMENT_TYPE_NOTIFICATION = 'notification'
const ENVIRONMENT_TYPE_FULLSCREEN = 'fullscreen'
const ENVIRONMENT_TYPE_BACKGROUND = 'background'
export const PLATFORM_BRAVE = 'Brave'
export const PLATFORM_CHROME = 'Chrome'
export const PLATFORM_EDGE = 'Edge'
export const PLATFORM_FIREFOX = 'Firefox'
export const PLATFORM_OPERA = 'Opera'
const PLATFORM_BRAVE = 'Brave'
const PLATFORM_CHROME = 'Chrome'
const PLATFORM_EDGE = 'Edge'
const PLATFORM_FIREFOX = 'Firefox'
const PLATFORM_OPERA = 'Opera'
const MESSAGE_TYPE = {
export const MESSAGE_TYPE = {
ETH_DECRYPT: 'eth_decrypt',
ETH_GET_ENCRYPTION_PUBLIC_KEY: 'eth_getEncryptionPublicKey',
ETH_SIGN: 'eth_sign',
@ -29,16 +28,3 @@ const MESSAGE_TYPE = {
WATCH_ASSET: 'wallet_watchAsset',
WATCH_ASSET_LEGACY: 'metamask_watchAsset',
}
export {
ENVIRONMENT_TYPE_POPUP,
ENVIRONMENT_TYPE_NOTIFICATION,
ENVIRONMENT_TYPE_FULLSCREEN,
ENVIRONMENT_TYPE_BACKGROUND,
MESSAGE_TYPE,
PLATFORM_BRAVE,
PLATFORM_CHROME,
PLATFORM_EDGE,
PLATFORM_FIREFOX,
PLATFORM_OPERA,
}

View File

@ -1,6 +1,6 @@
// Type Imports
/**
* @typedef {import('../../app/scripts/lib/enums').EnvironmentType} EnvironmentType
* @typedef {import('../../shared/constants/app').EnvironmentType} EnvironmentType
*/
// Type Declarations

View File

@ -3,6 +3,7 @@ export const RINKEBY = 'rinkeby'
export const KOVAN = 'kovan'
export const MAINNET = 'mainnet'
export const GOERLI = 'goerli'
export const NETWORK_TYPE_RPC = 'rpc'
export const MAINNET_NETWORK_ID = '1'
export const ROPSTEN_NETWORK_ID = '3'
@ -16,6 +17,12 @@ export const RINKEBY_CHAIN_ID = '0x4'
export const GOERLI_CHAIN_ID = '0x5'
export const KOVAN_CHAIN_ID = '0x2a'
/**
* The largest possible chain ID we can handle.
* Explanation: https://gist.github.com/rekmarks/a47bd5f2525936c4b8eee31a16345553
*/
export const MAX_SAFE_CHAIN_ID = 4503599627370476
export const ROPSTEN_DISPLAY_NAME = 'Ropsten'
export const RINKEBY_DISPLAY_NAME = 'Rinkeby'
export const KOVAN_DISPLAY_NAME = 'Kovan'

View File

@ -0,0 +1,4 @@
export const CAVEAT_NAMES = {
exposedAccounts: 'exposedAccounts',
primaryAccountOnly: 'primaryAccountOnly',
}

View File

@ -1,3 +0,0 @@
### Shared Modules
This folder is reserved for modules that can be used globally within both the background and ui applications.

View File

@ -1,4 +1,10 @@
const fetchWithTimeout = ({ timeout = 120000 } = {}) => {
import { memoize } from 'lodash'
const getFetchWithTimeout = memoize((timeout) => {
if (!Number.isInteger(timeout) || timeout < 1) {
throw new Error('Must specify positive integer timeout.')
}
return async function _fetch(url, opts) {
const abortController = new window.AbortController()
const { signal } = abortController
@ -18,6 +24,6 @@ const fetchWithTimeout = ({ timeout = 120000 } = {}) => {
throw e
}
}
}
})
export default fetchWithTimeout
export default getFetchWithTimeout

30
shared/modules/utils.js Normal file
View File

@ -0,0 +1,30 @@
import { MAX_SAFE_CHAIN_ID } from '../constants/network'
/**
* Checks whether the given number primitive chain ID is safe.
* Because some cryptographic libraries we use expect the chain ID to be a
* number primitive, it must not exceed a certain size.
*
* @param {number} chainId - The chain ID to check for safety.
* @returns {boolean} Whether the given chain ID is safe.
*/
export function isSafeChainId(chainId) {
return (
Number.isSafeInteger(chainId) && chainId > 0 && chainId <= MAX_SAFE_CHAIN_ID
)
}
/**
* Checks whether the given value is a 0x-prefixed, non-zero, non-zero-padded,
* hexadecimal string.
*
* @param {any} value - The value to check.
* @returns {boolean} True if the value is a correctly formatted hex string,
* false otherwise.
*/
export function isPrefixedFormattedHexString(value) {
if (typeof value !== 'string') {
return false
}
return /^0x[1-9a-f]+[0-9a-f]*$/iu.test(value)
}

View File

@ -2,6 +2,7 @@ import assert from 'assert'
import freeze from 'deep-freeze-strict'
import reducers from '../../../ui/app/ducks'
import * as actionConstants from '../../../ui/app/store/actionConstants'
import { NETWORK_TYPE_RPC } from '../../../shared/constants/network'
describe('config view actions', function () {
const initialState = {
@ -25,7 +26,7 @@ describe('config view actions', function () {
}
const result = reducers(initialState, action)
assert.equal(result.metamask.provider.type, 'rpc')
assert.equal(result.metamask.provider.type, NETWORK_TYPE_RPC)
assert.equal(result.metamask.provider.rpcUrl, 'foo')
})
})

View File

@ -7,10 +7,7 @@ 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 '../../../../app/scripts/controllers/network/enums'
import { MAINNET, ROPSTEN } from '../../../../shared/constants/network'
describe('DetectTokensController', function () {
const sandbox = sinon.createSandbox()

View File

@ -14,7 +14,7 @@ import {
ROPSTEN,
ROPSTEN_CHAIN_ID,
ROPSTEN_NETWORK_ID,
} from '../../../../app/scripts/controllers/network/enums'
} from '../../../../shared/constants/network'
import {
TRANSACTION_CATEGORIES,
TRANSACTION_STATUSES,

View File

@ -1,7 +1,7 @@
import { strict as assert } from 'assert'
import sinon from 'sinon'
import MetaMetricsController from '../../../../app/scripts/controllers/metametrics'
import { ENVIRONMENT_TYPE_BACKGROUND } from '../../../../app/scripts/lib/enums'
import { ENVIRONMENT_TYPE_BACKGROUND } from '../../../../shared/constants/app'
import { createSegmentMock } from '../../../../app/scripts/lib/segment'
import {
METAMETRICS_ANONYMOUS_ID,

View File

@ -1,12 +1,12 @@
import { ethErrors, ERROR_CODES } from 'eth-json-rpc-errors'
import { ethErrors, errorCodes } from 'eth-rpc-errors'
import deepFreeze from 'deep-freeze-strict'
import { ApprovalController } from '@metamask/controllers'
import _getRestrictedMethods from '../../../../../app/scripts/controllers/permissions/restrictedMethods'
import { CAVEAT_NAMES } from '../../../../../shared/constants/permissions'
import {
CAVEAT_NAMES,
CAVEAT_TYPES,
NOTIFICATION_NAMES,
} from '../../../../../app/scripts/controllers/permissions/enums'
@ -336,7 +336,7 @@ export const getters = deepFreeze({
return {
// name: 'EthereumRpcError',
message: `Failed to add 'eth_accounts' to '${origin}'.`,
code: ERROR_CODES.rpc.internal,
code: errorCodes.rpc.internal,
}
},
},

View File

@ -8,7 +8,7 @@ import { ObservableStore } from '@metamask/obs-store'
import {
ROPSTEN_NETWORK_ID,
MAINNET_NETWORK_ID,
} from '../../../../app/scripts/controllers/network/enums'
} from '../../../../shared/constants/network'
import { ETH_SWAPS_TOKEN_ADDRESS } from '../../../../ui/app/helpers/constants/swaps'
import { createTestProviderTools } from '../../../stub/provider'
import SwapsController, {

View File

@ -1,14 +1,16 @@
import assert from 'assert'
import nock from 'nock'
import fetchWithTimeout from '../../../app/scripts/lib/fetch-with-timeout'
import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout'
describe('fetchWithTimeout', function () {
describe('getFetchWithTimeout', function () {
it('fetches a url', async function () {
nock('https://api.infura.io').get('/money').reply(200, '{"hodl": false}')
const fetch = fetchWithTimeout()
const response = await (await fetch('https://api.infura.io/money')).json()
const fetchWithTimeout = getFetchWithTimeout(30000)
const response = await (
await fetchWithTimeout('https://api.infura.io/money')
).json()
assert.deepEqual(response, {
hodl: false,
})
@ -20,12 +22,10 @@ describe('fetchWithTimeout', function () {
.delay(2000)
.reply(200, '{"moon": "2012-12-21T11:11:11Z"}')
const fetch = fetchWithTimeout({
timeout: 123,
})
const fetchWithTimeout = getFetchWithTimeout(123)
try {
await fetch('https://api.infura.io/moon').then((r) => r.json())
await fetchWithTimeout('https://api.infura.io/moon').then((r) => r.json())
assert.fail('Request should throw')
} catch (e) {
assert.ok(e)
@ -38,15 +38,20 @@ describe('fetchWithTimeout', function () {
.delay(2000)
.reply(200, '{"moon": "2012-12-21T11:11:11Z"}')
const fetch = fetchWithTimeout({
timeout: 123,
})
const fetchWithTimeout = getFetchWithTimeout(123)
try {
await fetch('https://api.infura.io/moon').then((r) => r.json())
await fetchWithTimeout('https://api.infura.io/moon').then((r) => r.json())
assert.fail('Request should be aborted')
} catch (e) {
assert.deepEqual(e.message, 'Aborted')
}
})
it('throws on invalid timeout', async function () {
assert.throws(() => getFetchWithTimeout(), 'should throw')
assert.throws(() => getFetchWithTimeout(-1), 'should throw')
assert.throws(() => getFetchWithTimeout({}), 'should throw')
assert.throws(() => getFetchWithTimeout(true), 'should throw')
})
})

View File

@ -2,15 +2,15 @@ import { strict as assert } from 'assert'
import {
getEnvironmentType,
sufficientBalance,
isPrefixedFormattedHexString,
} from '../../../app/scripts/lib/util'
import { isPrefixedFormattedHexString } from '../../../shared/modules/utils'
import {
ENVIRONMENT_TYPE_POPUP,
ENVIRONMENT_TYPE_NOTIFICATION,
ENVIRONMENT_TYPE_FULLSCREEN,
ENVIRONMENT_TYPE_BACKGROUND,
} from '../../../app/scripts/lib/enums'
} from '../../../shared/constants/app'
describe('app utils', function () {
describe('getEnvironmentType', function () {

View File

@ -1,3 +1,5 @@
import { NETWORK_TYPE_RPC } from '../../shared/constants/network'
/**
* @typedef {Object} FirstTimeState
* @property {Object} config Initial configuration parameters
@ -11,7 +13,7 @@ const initialState = {
config: {},
NetworkController: {
provider: {
type: 'rpc',
type: NETWORK_TYPE_RPC,
rpcUrl: 'http://localhost:8545',
chainId: '0x539',
},

View File

@ -3,7 +3,7 @@ import migration51 from '../../../app/scripts/migrations/051'
import {
INFURA_PROVIDER_TYPES,
NETWORK_TYPE_TO_ID_MAP,
} from '../../../app/scripts/controllers/network/enums'
} from '../../../shared/constants/network'
describe('migration #51', function () {
it('should update the version metadata', async function () {

View File

@ -4,7 +4,7 @@ import { debounce } from 'lodash'
import Fuse from 'fuse.js'
import InputAdornment from '@material-ui/core/InputAdornment'
import classnames from 'classnames'
import { ENVIRONMENT_TYPE_POPUP } from '../../../../../app/scripts/lib/enums'
import { ENVIRONMENT_TYPE_POPUP } from '../../../../../shared/constants/app'
import { getEnvironmentType } from '../../../../../app/scripts/lib/util'
import Identicon from '../../ui/identicon'
import SiteIcon from '../../ui/site-icon'

View File

@ -203,7 +203,7 @@
}
&__check-mark-icon {
background-image: url("images/check-white.svg");
background-image: url("/images/check-white.svg");
height: 18px;
width: 18px;
background-repeat: no-repeat;

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
import {
ENVIRONMENT_TYPE_POPUP,
ENVIRONMENT_TYPE_NOTIFICATION,
} from '../../../../../../app/scripts/lib/enums'
} from '../../../../../../shared/constants/app'
import { getEnvironmentType } from '../../../../../../app/scripts/lib/util'
import NetworkDisplay from '../../network-display'
import Identicon from '../../../ui/identicon'

View File

@ -9,11 +9,10 @@ import {
NETWORKS_ROUTE,
NETWORKS_FORM_ROUTE,
} from '../../../helpers/constants/routes'
import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../../app/scripts/lib/enums'
import {
getEnvironmentType,
isPrefixedFormattedHexString,
} from '../../../../../app/scripts/lib/util'
import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../../shared/constants/app'
import { NETWORK_TYPE_RPC } from '../../../../../shared/constants/network'
import { isPrefixedFormattedHexString } from '../../../../../shared/modules/utils'
import { getEnvironmentType } from '../../../../../app/scripts/lib/util'
import { Dropdown, DropdownMenuItem } from './components/dropdown'
import NetworkDropdownIcon from './components/network-dropdown-icon'
@ -117,7 +116,7 @@ class NetworkDropdown extends Component {
return reversedRpcListDetail.map((entry) => {
const { rpcUrl, chainId, ticker = 'ETH', nickname = '' } = entry
const isCurrentRpcTarget =
provider.type === 'rpc' && rpcUrl === provider.rpcUrl
provider.type === NETWORK_TYPE_RPC && rpcUrl === provider.rpcUrl
return (
<DropdownMenuItem

View File

@ -1,4 +1,5 @@
import { connect } from 'react-redux'
import { NETWORK_TYPE_RPC } from '../../../../../shared/constants/network'
import * as actions from '../../../store/actions'
import { getNetworkIdentifier } from '../../../selectors'
import LoadingNetworkScreen from './loading-network-screen.component'
@ -9,7 +10,9 @@ const mapStateToProps = (state) => {
const { rpcUrl, chainId, ticker, nickname, type } = provider
const setProviderArgs =
type === 'rpc' ? [rpcUrl, chainId, ticker, nickname] : [provider.type]
type === NETWORK_TYPE_RPC
? [rpcUrl, chainId, ticker, nickname]
: [provider.type]
return {
isLoadingNetwork: network === 'loading',

View File

@ -16,7 +16,7 @@ import {
import { useI18nContext } from '../../../hooks/useI18nContext'
import { useMetricEvent } from '../../../hooks/useMetricEvent'
import { getEnvironmentType } from '../../../../../app/scripts/lib/util'
import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../../app/scripts/lib/enums'
import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../../shared/constants/app'
export default function AccountOptionsMenu({ anchorElement, onClose }) {
const t = useI18nContext()

View File

@ -5,7 +5,7 @@ import { useSelector } from 'react-redux'
import SelectedAccount from '../selected-account'
import ConnectedStatusIndicator from '../connected-status-indicator'
import { getEnvironmentType } from '../../../../../app/scripts/lib/util'
import { ENVIRONMENT_TYPE_POPUP } from '../../../../../app/scripts/lib/enums'
import { ENVIRONMENT_TYPE_POPUP } from '../../../../../shared/constants/app'
import { CONNECTED_ACCOUNTS_ROUTE } from '../../../helpers/constants/routes'
import { useI18nContext } from '../../../hooks/useI18nContext'
import { useMetricEvent } from '../../../hooks/useMetricEvent'

View File

@ -6,7 +6,7 @@ import * as actions from '../../../store/actions'
import { resetCustomData as resetCustomGasData } from '../../../ducks/gas/gas.duck'
import isMobileView from '../../../../lib/is-mobile-view'
import { getEnvironmentType } from '../../../../../app/scripts/lib/util'
import { ENVIRONMENT_TYPE_POPUP } from '../../../../../app/scripts/lib/enums'
import { ENVIRONMENT_TYPE_POPUP } from '../../../../../shared/constants/app'
// Modal Components
import ConfirmCustomizeGasModal from '../gas-customization/gas-modal-page-container'

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
import log from 'loglevel'
import { BrowserQRCodeReader } from '@zxing/library'
import { getEnvironmentType } from '../../../../../../app/scripts/lib/util'
import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../../../app/scripts/lib/enums'
import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../../../shared/constants/app'
import Spinner from '../../../ui/spinner'
import WebcamUtils from '../../../../../lib/webcam-utils'
import PageContainerFooter from '../../../ui/page-container/page-container-footer/page-container-footer.component'

View File

@ -1,21 +1,7 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import {
MAINNET_NETWORK_ID,
ROPSTEN_NETWORK_ID,
RINKEBY_NETWORK_ID,
KOVAN_NETWORK_ID,
GOERLI_NETWORK_ID,
} from '../../../../../app/scripts/controllers/network/enums'
const networkIdToTypeMap = {
[MAINNET_NETWORK_ID]: 'mainnet',
[ROPSTEN_NETWORK_ID]: 'ropsten',
[RINKEBY_NETWORK_ID]: 'rinkeby',
[GOERLI_NETWORK_ID]: 'goerli',
[KOVAN_NETWORK_ID]: 'kovan',
}
import { NETWORK_TYPE_RPC } from '../../../../../shared/constants/network'
export default class NetworkDisplay extends Component {
static defaultProps = {
@ -23,9 +9,9 @@ export default class NetworkDisplay extends Component {
}
static propTypes = {
networkNickname: PropTypes.string.isRequired,
networkType: PropTypes.string.isRequired,
colored: PropTypes.bool,
network: PropTypes.string,
provider: PropTypes.object,
}
static contextTypes = {
@ -33,12 +19,11 @@ export default class NetworkDisplay extends Component {
}
renderNetworkIcon() {
const { network } = this.props
const networkClass = networkIdToTypeMap[network]
const { networkType } = this.props
return networkClass ? (
return networkType ? (
<div
className={`network-display__icon network-display__icon--${networkClass}`}
className={`network-display__icon network-display__icon--${networkType}`}
/>
) : (
<div
@ -52,24 +37,19 @@ export default class NetworkDisplay extends Component {
}
render() {
const {
colored,
network,
provider: { type, nickname },
} = this.props
const networkClass = networkIdToTypeMap[network]
const { colored, networkNickname, networkType } = this.props
return (
<div
className={classnames('network-display__container', {
'network-display__container--colored': colored,
[`network-display__container--${networkClass}`]:
colored && networkClass,
[`network-display__container--${networkType}`]:
colored && networkType,
})}
>
{networkClass ? (
{networkType ? (
<div
className={`network-display__icon network-display__icon--${networkClass}`}
className={`network-display__icon network-display__icon--${networkType}`}
/>
) : (
<div
@ -81,7 +61,9 @@ export default class NetworkDisplay extends Component {
/>
)}
<div className="network-display__name">
{type === 'rpc' && nickname ? nickname : this.context.t(type)}
{networkType === NETWORK_TYPE_RPC && networkNickname
? networkNickname
: this.context.t(networkType)}
</div>
</div>
)

View File

@ -1,10 +1,14 @@
import { connect } from 'react-redux'
import NetworkDisplay from './network-display.component'
const mapStateToProps = ({ metamask: { network, provider } }) => {
const mapStateToProps = ({
metamask: {
provider: { nickname, type },
},
}) => {
return {
network,
provider,
networkNickname: nickname,
networkType: type,
}
}

View File

@ -7,7 +7,7 @@ import { ObjectInspector } from 'react-inspector'
import {
ENVIRONMENT_TYPE_NOTIFICATION,
MESSAGE_TYPE,
} from '../../../../../app/scripts/lib/enums'
} from '../../../../../shared/constants/app'
import { getEnvironmentType } from '../../../../../app/scripts/lib/util'
import Identicon from '../../ui/identicon'
import AccountListItem from '../account-list-item'

View File

@ -2,7 +2,7 @@ import { connect } from 'react-redux'
import { compose } from 'redux'
import { withRouter } from 'react-router-dom'
import { MESSAGE_TYPE } from '../../../../../app/scripts/lib/enums'
import { MESSAGE_TYPE } from '../../../../../shared/constants/app'
import { goHome } from '../../../store/actions'
import {
accountsWithSendEtherInfoSelector,

View File

@ -1,3 +1,3 @@
import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../../app/scripts/lib/enums'
import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../../shared/constants/app'
export { ENVIRONMENT_TYPE_NOTIFICATION }

View File

@ -2,7 +2,7 @@ import { connect } from 'react-redux'
import { clearConfirmTransaction } from '../../../ducks/confirm-transaction/confirm-transaction.duck'
import { accountsWithSendEtherInfoSelector } from '../../../selectors'
import { getAccountByAddress } from '../../../helpers/utils/util'
import { MESSAGE_TYPE } from '../../../../../app/scripts/lib/enums'
import { MESSAGE_TYPE } from '../../../../../shared/constants/app'
import SignatureRequest from './signature-request.component'
function mapStateToProps(state) {

View File

@ -34,7 +34,7 @@ import {
setSwapsFromToken,
} from '../../../ducks/swaps/swaps'
import IconButton from '../../ui/icon-button'
import { MAINNET_CHAIN_ID } from '../../../../../app/scripts/controllers/network/enums'
import { MAINNET_CHAIN_ID } from '../../../../../shared/constants/network'
import WalletOverview from './wallet-overview'
const EthOverview = ({ className }) => {

View File

@ -27,7 +27,7 @@ import {
getCurrentKeyring,
getCurrentChainId,
} from '../../../selectors/selectors'
import { MAINNET_CHAIN_ID } from '../../../../../app/scripts/controllers/network/enums'
import { MAINNET_CHAIN_ID } from '../../../../../shared/constants/network'
import SwapIcon from '../../ui/icon/swap-icon.component'
import SendIcon from '../../ui/icon/overview-send-icon.component'

View File

@ -0,0 +1,55 @@
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import { omit } from 'lodash'
import Typography from '../typography'
import { COLORS } from '../../../helpers/constants/design-system'
export default function Chip({
className,
children,
borderColor = COLORS.UI1,
label,
labelProps = {},
leftIcon,
rightIcon,
onClick,
}) {
return (
<div
onClick={onClick}
className={classnames(className, 'chip', {
'chip--with-left-icon': Boolean(leftIcon),
'chip--with-right-icon': Boolean(rightIcon),
[`chip--${borderColor}`]: true,
})}
role="button"
tabIndex={0}
>
{leftIcon && <div className="chip__left-icon">{leftIcon}</div>}
{children ?? (
<Typography
className="chip__label"
variant="h6"
tag="span"
color="UI4"
{...labelProps}
>
{label}
</Typography>
)}
{rightIcon && <div className="chip__right-icon">{rightIcon}</div>}
</div>
)
}
Chip.propTypes = {
borderColor: PropTypes.oneOf(Object.values(COLORS)),
label: PropTypes.string,
children: PropTypes.node,
labelProps: PropTypes.shape(omit(Typography.propTypes, ['className'])),
leftIcon: PropTypes.node,
rightIcon: PropTypes.node,
className: PropTypes.string,
onClick: PropTypes.func,
}

View File

@ -0,0 +1,48 @@
@use "design-system";
.chip {
$self: &;
border-radius: 100px;
border: 1px solid design-system.$ui-1;
padding: 8px 16px;
margin: 0 4px;
display: flex;
align-items: center;
width: max-content;
&__left-icon,
&__right-icon {
display: flex;
align-items: center;
}
@each $variant, $color in design-system.$color-map {
&--#{$variant} {
border-color: $color;
}
}
&--with-left-icon,
&--with-right-icon {
padding-top: 4px;
padding-bottom: 4px;
}
&--with-left-icon {
padding-left: 4px;
#{$self}__label {
margin-left: 8px;
}
}
&--with-right-icon {
padding-right: 4px;
#{$self}__label {
margin-right: 8px;
}
}
}

View File

@ -0,0 +1,82 @@
/* eslint-disable react/prop-types */
import React from 'react'
import { select, text } from '@storybook/addon-knobs'
import { COLORS, TYPOGRAPHY } from '../../../helpers/constants/design-system'
import ApproveIcon from '../icon/approve-icon.component'
import Identicon from '../identicon/identicon.component'
import Chip from '.'
export default {
title: 'Chip',
}
export const Plain = ({
leftIcon,
rightIcon,
label = 'Hello',
borderColor = COLORS.UI1,
fontColor = COLORS.BLACK,
}) => (
<Chip
leftIcon={leftIcon}
rightIcon={rightIcon}
label={text('label', label)}
labelProps={{
color: select('color', COLORS, fontColor),
variant: select('typography', TYPOGRAPHY, TYPOGRAPHY.H6),
}}
borderColor={select('borderColor', COLORS, borderColor)}
/>
)
export const WithLeftIcon = () => (
<Plain
label="Done!"
borderColor={COLORS.SUCCESS3}
fontColor={COLORS.SUCCESS3}
leftIcon={<ApproveIcon size={24} color="#4cd964" />}
/>
)
export const WithRightIcon = () => (
<Plain
label="0x5CfE73b6021E818B776b421B1c4Db2474086a7e1"
borderColor={COLORS.UI4}
fontColor={COLORS.UI4}
rightIcon={
<Identicon
address="0x5CfE73b6021E818B776b421B1c4Db2474086a7e1"
diameter={25}
/>
}
/>
)
export const WithBothIcons = () => (
<Plain
label="Account 1"
borderColor={COLORS.UI4}
fontColor={COLORS.UI4}
rightIcon={
<svg
width="10"
height="6"
viewBox="0 0 10 6"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.45759 0.857142C9.45759 0.785714 9.42188 0.705357 9.3683 0.651785L8.92188 0.205357C8.8683 0.151785 8.78795 0.116071 8.71652 0.116071C8.64509 0.116071 8.56473 0.151785 8.51116 0.205357L5.00223 3.71429L1.4933 0.205357C1.43973 0.151785 1.35938 0.116071 1.28795 0.116071C1.20759 0.116071 1.13616 0.151785 1.08259 0.205357L0.636161 0.651785C0.582589 0.705357 0.546875 0.785714 0.546875 0.857142C0.546875 0.928571 0.582589 1.00893 0.636161 1.0625L4.79688 5.22321C4.85045 5.27679 4.9308 5.3125 5.00223 5.3125C5.07366 5.3125 5.15402 5.27679 5.20759 5.22321L9.3683 1.0625C9.42188 1.00893 9.45759 0.928571 9.45759 0.857142Z"
fill="#24292E"
/>
</svg>
}
leftIcon={
<Identicon
address="0x5CfE73b6021E818B776b421B1c4Db2474086a7e1"
diameter={25}
/>
}
/>
)

View File

@ -0,0 +1 @@
export { default } from './chip'

View File

@ -0,0 +1,50 @@
import React from 'react'
import classnames from 'classnames'
import PropTypes from 'prop-types'
import { COLORS } from '../../../helpers/constants/design-system'
export default function ColorIndicator({
size = 'small',
type = 'outlined',
color = COLORS.UI4,
borderColor,
iconClassName,
}) {
const colorIndicatorClassName = classnames('color-indicator', {
'color-indicator--filled': type === 'filled' || Boolean(iconClassName),
'color-indicator--partial-filled': type === 'partial-filled',
[`color-indicator--border-color-${borderColor}`]: Boolean(borderColor),
[`color-indicator--color-${color}`]: true,
[`color-indicator--size-${size}`]: true,
})
return (
<div className={colorIndicatorClassName}>
{iconClassName ? (
<i className={classnames('color-indicator__icon', iconClassName)} />
) : (
<span className="color-indicator__inner-circle" />
)}
</div>
)
}
ColorIndicator.SIZES = {
LARGE: 'large',
MEDIUM: 'medium',
SMALL: 'small,',
}
ColorIndicator.TYPES = {
FILLED: 'filled',
PARTIAL: 'partial-filled',
OUTLINE: 'outline',
}
ColorIndicator.propTypes = {
color: PropTypes.oneOf(Object.values(COLORS)),
borderColor: PropTypes.oneOf(Object.values(COLORS)),
size: PropTypes.oneOf(Object.values(ColorIndicator.SIZES)),
iconClassName: PropTypes.string,
type: PropTypes.oneOf(Object.values(ColorIndicator.TYPES)),
}

View File

@ -0,0 +1,61 @@
@use "utilities";
@use "design-system";
$sizes: (
'large': 6,
'medium': 5,
'small': 4,
);
.color-indicator {
$self: &;
border: 1px solid transparent;
display: flex;
align-items: center;
justify-content: center;
&__inner-circle {
background-color: transparent;
}
@each $variant, $size in $sizes {
&--size-#{$variant} {
height: #{2 * $size}px;
width: #{2 * $size}px;
border-radius: #{$size}px;
#{$self}__inner-circle {
border-radius: #{$size}px;
height: #{$size}px;
width: #{$size}px;
}
#{$self}__icon {
font-size: #{1.25 * $size}px;
}
}
}
@each $variant, $color in design-system.$color-map {
&--color-#{$variant} {
border-color: $color;
&#{$self}--partial-filled #{$self}__inner-circle {
background-color: $color;
}
&#{$self}--filled {
background-color: $color;
}
& #{$self}__icon {
color: #{utilities.choose-contrast-color($color)};
}
}
}
// separate iterator to ensure borderColor takes precedence
@each $variant, $color in design-system.$color-map {
&--border-color-#{$variant} {
border-color: $color;
}
}
}

View File

@ -0,0 +1,27 @@
import React from 'react'
import { select } from '@storybook/addon-knobs'
import { COLORS } from '../../../helpers/constants/design-system'
import ColorIndicator from './color-indicator'
export default {
title: 'ColorIndicator',
}
export const colorIndicator = () => (
<ColorIndicator
size={select('size', ColorIndicator.SIZES, ColorIndicator.SIZES.LARGE)}
type={select('type', ColorIndicator.TYPES, ColorIndicator.TYPES.FILLED)}
color={select('color', COLORS, COLORS.PRIMARY1)}
borderColor={select('borderColor', { NONE: undefined, ...COLORS })}
/>
)
export const withIcon = () => (
<ColorIndicator
size={select('size', ColorIndicator.SIZES, ColorIndicator.SIZES.LARGE)}
type={select('type', ColorIndicator.TYPES, ColorIndicator.TYPES.FILLED)}
color={select('color', COLORS, COLORS.PRIMARY1)}
iconClassName="fa fa-question"
borderColor={select('borderColor', { NONE: undefined, ...COLORS })}
/>
)

View File

@ -0,0 +1 @@
export { default } from './color-indicator'

View File

@ -0,0 +1 @@
export { default } from './typography'

View File

@ -0,0 +1,58 @@
import React from 'react'
import classnames from 'classnames'
import PropTypes from 'prop-types'
import { COLORS, TYPOGRAPHY } from '../../../helpers/constants/design-system'
const { H6, H7, H8, H9 } = TYPOGRAPHY
export default function Typography({
variant = TYPOGRAPHY.Paragraph,
className,
color = COLORS.BLACK,
tag,
children,
spacing = 1,
fontWeight = 'normal',
align,
}) {
const computedClassName = classnames(
'typography',
className,
`typography--${variant}`,
`typography--align-${align}`,
`typography--spacing-${spacing}`,
`typography--color-${color}`,
`typography--weight-${fontWeight}`,
)
let Tag = tag ?? variant
if (Tag === TYPOGRAPHY.Paragraph) {
Tag = 'p'
} else if ([H7, H8, H9].includes(Tag)) {
Tag = H6
}
return <Tag className={computedClassName}>{children}</Tag>
}
Typography.propTypes = {
variant: PropTypes.oneOf(Object.values(TYPOGRAPHY)),
children: PropTypes.node.isRequired,
color: PropTypes.oneOf(Object.values(COLORS)),
className: PropTypes.string,
align: PropTypes.oneOf(['center', 'right']),
spacing: PropTypes.oneOf([1, 2, 3, 4, 5, 6, 7, 8]),
fontWeight: PropTypes.oneOf(['bold', 'normal']),
tag: PropTypes.oneOf([
'p',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'span',
'div',
]),
}

View File

@ -0,0 +1,38 @@
@use "design-system";
@use "sass:map";
.typography {
@include design-system.Paragraph;
@each $variant in map.keys(design-system.$typography-variants) {
&--#{$variant} {
@include design-system.typography($variant);
}
}
@each $variant, $color in design-system.$color-map {
&--color-#{$variant} {
color: $color;
}
}
@each $variant, $weight in design-system.$typography-font-weights {
&--weight-#{$variant} {
font-weight: $weight;
}
}
&--align-center {
text-align: center;
}
&--align-right {
text-align: right;
}
@for $i from 1 through 8 {
&--spacing-#{$i} {
margin: #{$i * 4}px auto;
}
}
}

View File

@ -0,0 +1,53 @@
import React from 'react'
import { number, select, text } from '@storybook/addon-knobs'
import { COLORS, TYPOGRAPHY } from '../../../helpers/constants/design-system'
import Typography from '.'
export default {
title: 'Typography',
}
const fontWeightOptions = {
bold: 'bold',
normal: 'normal',
}
const alignOptions = {
left: undefined,
center: 'center',
right: 'right',
}
export const list = () => (
<div style={{ width: '80%', flexDirection: 'column' }}>
{Object.values(TYPOGRAPHY).map((variant) => (
<div key={variant} style={{ width: '100%' }}>
<Typography
variant={variant}
color={select('color', COLORS, COLORS.BLACK)}
spacing={number('spacing', 1, { range: true, min: 1, max: 8 })}
align={select('align', alignOptions, undefined)}
fontWeight={select('font weight', fontWeightOptions, 'normal')}
>
{variant}
</Typography>
</div>
))}
</div>
)
export const TheQuickOrangeFox = () => (
<div style={{ width: '80%', flexDirection: 'column' }}>
<div style={{ width: '100%' }}>
<Typography
color={select('color', COLORS, COLORS.BLACK)}
variant={select('variant', TYPOGRAPHY, TYPOGRAPHY.Paragraph)}
spacing={number('spacing', 1, { range: true, min: 1, max: 8 })}
align={select('align', alignOptions, undefined)}
fontWeight={select('font weight', fontWeightOptions, 'normal')}
>
{text('content', 'The quick orange fox jumped over the lazy dog.')}
</Typography>
</div>
</div>
)

View File

@ -7,7 +7,9 @@
@import 'button/buttons';
@import 'card/index';
@import 'check-box/index';
@import 'chip/chip';
@import 'circle-icon/index';
@import 'color-indicator/color-indicator';
@import 'currency-display/index';
@import 'currency-input/index';
@import 'dialog/dialog';
@ -37,5 +39,6 @@
@import 'toggle-button/index';
@import 'token-balance/index';
@import 'tooltip/index';
@import 'typography/typography';
@import 'unit-input/index';
@import 'url-icon/index';

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