diff --git a/.babelrc b/.babelrc
index 9b1d5409bc17..cfa759dd9047 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,4 +1,23 @@
{
- "presets": [["env", { "targets": { "browsers": [">0.25%", "not ie 11", "not op_mini all"] } } ], "react", "stage-0"],
- "plugins": ["transform-runtime", "transform-async-to-generator", "transform-class-properties"]
-}
+ "presets": [
+ [
+ "env",
+ {
+ "targets": {
+ "browsers": [
+ ">0.25%",
+ "not ie 11",
+ "not op_mini all"
+ ]
+ }
+ }
+ ],
+ "react",
+ "stage-0"
+ ],
+ "plugins": [
+ "transform-runtime",
+ "transform-async-to-generator",
+ "transform-class-properties"
+ ]
+}
\ No newline at end of file
diff --git a/.circleci/config.yml b/.circleci/config.yml
index cf5afc3a86eb..686a996c1fa2 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -23,11 +23,9 @@ workflows:
- test-e2e-chrome:
requires:
- prep-deps-npm
- - prep-build
- test-e2e-firefox:
requires:
- prep-deps-npm
- - prep-build
# - test-e2e-beta-drizzle:
# requires:
# - prep-deps-npm
@@ -82,28 +80,21 @@ workflows:
jobs:
prep-deps-npm:
docker:
- - image: circleci/node:8.11.3-browsers
+ - image: circleci/node:8.15.1-browsers
steps:
- checkout
- - restore_cache:
- keys:
- - v1.0-dependency-cache-{{ checksum "package-lock.json" }}
- run:
- name: Install npm 6 + deps via npm
+ name: Install deps via npm
command: |
- sudo npm install -g npm@6 && npm install --no-save --no-audit
+ npm ci
- persist_to_workspace:
root: .
paths:
- node_modules
- - save_cache:
- key: v1.0-dependency-cache-{{ checksum "package-lock.json" }}
- paths:
- - node_modules
prep-build:
docker:
- - image: circleci/node:8.11.3-browsers
+ - image: circleci/node:8.15.1-browsers
steps:
- checkout
- attach_workspace:
@@ -122,7 +113,7 @@ jobs:
prep-docs:
docker:
- - image: circleci/node:8.11.3-browsers
+ - image: circleci/node:8.15.1-browsers
steps:
- checkout
- attach_workspace:
@@ -137,7 +128,7 @@ jobs:
prep-scss:
docker:
- - image: circleci/node:8.11.3-browsers
+ - image: circleci/node:8.15.1-browsers
steps:
- checkout
- attach_workspace:
@@ -156,7 +147,7 @@ jobs:
test-lint:
docker:
- - image: circleci/node:8.11.3-browsers
+ - image: circleci/node:8.15.1-browsers
steps:
- checkout
- attach_workspace:
@@ -191,21 +182,22 @@ jobs:
# destination: test-artifacts
test-e2e-chrome:
docker:
- - image: circleci/node:8.11.3-browsers
+ - image: circleci/node:8.15.1-browsers
steps:
- checkout
- attach_workspace:
at: .
- run:
name: test:e2e:chrome
- command: npm run test:e2e:chrome
+ command: npm run build:test && npm run test:e2e:chrome
+ no_output_timeout: 20m
- store_artifacts:
path: test-artifacts
destination: test-artifacts
test-e2e-firefox:
docker:
- - image: circleci/node:8.11.3-browsers
+ - image: circleci/node:8.15.1-browsers
steps:
- checkout
- run:
@@ -215,14 +207,15 @@ jobs:
at: .
- run:
name: test:e2e:firefox
- command: npm run test:e2e:firefox
+ command: npm run build:test && npm run test:e2e:chrome
+ no_output_timeout: 20m
- store_artifacts:
path: test-artifacts
destination: test-artifacts
job-screens:
docker:
- - image: circleci/node:8.11.3-browsers
+ - image: circleci/node:8.15.1-browsers
steps:
- checkout
- attach_workspace:
@@ -237,7 +230,7 @@ jobs:
job-publish-prerelease:
docker:
- - image: circleci/node:8.11.3-browsers
+ - image: circleci/node:8.15.1-browsers
steps:
- checkout
- attach_workspace:
@@ -257,7 +250,7 @@ jobs:
job-publish-release:
docker:
- - image: circleci/node:8.11.3-browsers
+ - image: circleci/node:8.15.1-browsers
steps:
- checkout
- attach_workspace:
@@ -274,7 +267,7 @@ jobs:
test-unit:
docker:
- - image: circleci/node:8.11.3-browsers
+ - image: circleci/node:8.15.1-browsers
steps:
- checkout
- attach_workspace:
@@ -284,7 +277,7 @@ jobs:
command: npm run test:coverage
test-mozilla-lint:
docker:
- - image: circleci/node:8.11.3-browsers
+ - image: circleci/node:8.15.1-browsers
steps:
- checkout
- attach_workspace:
@@ -295,7 +288,7 @@ jobs:
test-integration-flat-firefox:
docker:
- - image: circleci/node:8.11.3-browsers
+ - image: circleci/node:8.15.1-browsers
steps:
- checkout
- attach_workspace:
@@ -311,7 +304,7 @@ jobs:
environment:
browsers: '["Chrome"]'
docker:
- - image: circleci/node:8.11.3-browsers
+ - image: circleci/node:8.15.1-browsers
steps:
- checkout
- attach_workspace:
@@ -320,36 +313,9 @@ jobs:
name: test:integration:flat
command: npm run test:flat
- test-integration-mascara-firefox:
- docker:
- - image: circleci/node:8.11.3-browsers
- steps:
- - checkout
- - attach_workspace:
- at: .
- - run:
- name: Install Firefox
- command: ./.circleci/scripts/firefox-install
- - run:
- name: test:integration:mascara
- command: npm run test:mascara
-
- test-integration-mascara-chrome:
- environment:
- browsers: '["Chrome"]'
- docker:
- - image: circleci/node:8.11.3-browsers
- steps:
- - checkout
- - attach_workspace:
- at: .
- - run:
- name: test:integration:mascara
- command: npm run test:mascara
-
all-tests-pass:
docker:
- - image: circleci/node:8.11.3-browsers
+ - image: circleci/node:8.15.1-browsers
steps:
- run:
name: All Tests Passed
diff --git a/.eslintignore b/.eslintignore
index f96d39a168a0..5923e1bc214d 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -14,9 +14,6 @@ app/vendor/**
ui/lib/blockies.js
-mascara/src/app/first-time/spinner.js
-mascara/test/jquery-3.1.0.min.js
-
test/integration/bundle.js
test/integration/jquery-3.1.0.min.js
test/integration/helpers.js
diff --git a/.nvmrc b/.nvmrc
index 812d970dc98d..9235dd0adaa1 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-v8.13
+v8.15
diff --git a/.stylelintignore b/.stylelintignore
index 854829a546a6..a42709a90e0b 100644
--- a/.stylelintignore
+++ b/.stylelintignore
@@ -4,7 +4,6 @@ dist/
docs/
fonts/
images/
-mascara/
node_modules/
notices/
test/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bf897c864d75..98ed6ac69b1f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,36 @@
## Current Develop Branch
+## 6.3.1 Fri Mar 26 2019
+
+- [#6353](https://github.com/MetaMask/metamask-extension/pull/6353): Open restore vault in full screen when clicked from popup
+- [#6372](https://github.com/MetaMask/metamask-extension/pull/6372): Prevents duplicates of account addresses from showing in send screen "To" dropdown
+- [#6374](https://github.com/MetaMask/metamask-extension/pull/6374): Ensures users are placed on correct confirm screens even when registry service fails
+
+## 6.3.0 Mon Mar 25 2019
+
+- [#6300](https://github.com/MetaMask/metamask-extension/pull/6300): Gas chart hidden on custom networks
+- [#6301](https://github.com/MetaMask/metamask-extension/pull/6301): Fix gas fee in the submitted step of the transaction details activity log
+- [#6302](https://github.com/MetaMask/metamask-extension/pull/6302): Replaces the coinbase link in the deposit modal with one for wyre
+- [#6307](https://github.com/MetaMask/metamask-extension/pull/6307): Centre the notification in the current window
+- [#6312](https://github.com/MetaMask/metamask-extension/pull/6312): Fixes popups not showing when screen size is odd
+- [#6326](https://github.com/MetaMask/metamask-extension/pull/6326): Fix oversized loading overlay on gas customization modal.
+- [#6330](https://github.com/MetaMask/metamask-extension/pull/6330): Stop reloading dapps on network change allowing dapps to decide if it should refresh or not
+- [#6332](https://github.com/MetaMask/metamask-extension/pull/6332): Enable mobile sync
+- [#6333](https://github.com/MetaMask/metamask-extension/pull/6333): Redesign of the settings screen
+- [#6340](https://github.com/MetaMask/metamask-extension/pull/6340): Cancel transactions and signature requests on the closing of notification windows
+- [#6341](https://github.com/MetaMask/metamask-extension/pull/6341): Disable transaction "Cancel" button when balance is insufficient
+- [#6347](https://github.com/MetaMask/metamask-extension/pull/6347): Enable privacy mode by default for first time users
+
+## 6.2.2 Tue Mar 12 2019
+
+- [#6271](https://github.com/MetaMask/metamask-extension/pull/6271): Centre all notification popups
+- [#6268](https://github.com/MetaMask/metamask-extension/pull/6268): Improve Korean translations
+- [#6279](https://github.com/MetaMask/metamask-extension/pull/6279): Nonmultiple notifications for batch txs
+- [#6280](https://github.com/MetaMask/metamask-extension/pull/6280): No longer check network when validating checksum addresses
+
+## 6.2.1 Wed Mar 06 2019
+
## 6.2.0 Tue Mar 05 2019
- [#6192](https://github.com/MetaMask/metamask-extension/pull/6192): Improves design and UX of onboarding flow
- [#6195](https://github.com/MetaMask/metamask-extension/pull/6195): Fixes gas estimation when sending to contracts
@@ -12,7 +42,7 @@
- [#6182](https://github.com/MetaMask/metamask-extension/pull/6182): Change "Token Address" to "Token Contract Address"
- [#6177](https://github.com/MetaMask/metamask-extension/pull/6177): Fixes #6176
-- [#6146](https://github.com/MetaMask/metamask-extension/pull/6146): * Add Copy Tx ID button to transaction-list-item-details
+- [#6146](https://github.com/MetaMask/metamask-extension/pull/6146): * Add Copy Tx ID button to transaction-list-item-details
- [#6133](https://github.com/MetaMask/metamask-extension/pull/6133): Checksum address before slicing it for the confirm screen
- [#6147](https://github.com/MetaMask/metamask-extension/pull/6147): Add button to force edit token symbol when adding custom token
- [#6124](https://github.com/MetaMask/metamask-extension/pull/6124): recent-blocks - dont listen for block when on infura providers
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index f9ec6293589a..000000000000
--- a/Dockerfile
+++ /dev/null
@@ -1,25 +0,0 @@
-FROM node:7
-MAINTAINER kumavis
-
-# setup app dir
-RUN mkdir -p /www/
-WORKDIR /www/
-
-# install dependencies
-COPY ./package.json /www/package.json
-# RUN npm install -g node-gyp
-RUN npm install >> npm_log 2>> npm_err || true
-
-RUN cat npm_log && cat npm_err
-
-# copy over app dir
-COPY ./ /www/
-
-# run tests
-# RUN npm test
-
-# build app
-RUN npm run dist
-
-# start server
-CMD node mascara/example/server.js
diff --git a/app/_locales/cs/messages.json b/app/_locales/cs/messages.json
index 2bce52cd9f8c..a28c4cb4af70 100644
--- a/app/_locales/cs/messages.json
+++ b/app/_locales/cs/messages.json
@@ -131,6 +131,12 @@
"buyCoinbaseExplainer": {
"message": "Coinbase je světově nejoblíbenější místo k nákupu a prodeji bitcoinu, etherea nebo litecoinu."
},
+ "buyCoinSwitch": {
+ "message": "Nákup na CoinSwitch"
+ },
+ "buyCoinSwitchExplainer": {
+ "message": "CoinSwitch je jediným místem, kde můžete vyměňovat více než 300 kryptocurrencí za nejlepší cenu."
+ },
"ok": {
"message": "Ok"
},
@@ -164,6 +170,9 @@
"continueToCoinbase": {
"message": "Přejít na Coinbase"
},
+ "continueToCoinSwitch": {
+ "message": "Přejít na CoinSwitch"
+ },
"contractDeployment": {
"message": "Nasazení kontraktu"
},
diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json
index 25ec628f02ac..b76f877728ac 100644
--- a/app/_locales/de/messages.json
+++ b/app/_locales/de/messages.json
@@ -131,6 +131,12 @@
"buyCoinbaseExplainer": {
"message": "Coinbase ist die weltweit bekannteste Art und Weise um Bitcoin, Ethereum und Litecoin zu kaufen und verkaufen."
},
+ "buyCoinSwitch": {
+ "message": "Auf CoinSwitch kaufen"
+ },
+ "buyCoinSwitchExplainer": {
+ "message": "CoinSwitch ist die One-Stop-Destination, um mehr als 300 Kryptowährungen zum besten Preis zu tauschen."
+ },
"ok": {
"message": "Ok"
},
@@ -164,6 +170,9 @@
"continueToCoinbase": {
"message": "Zu Coinbase fortfahren"
},
+ "continueToCoinSwitch": {
+ "message": "Zu CoinSwitch fortfahren"
+ },
"contractDeployment": {
"message": "Smart Contract Ausführung"
},
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index 3d2cd35bd096..154925d1a216 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -11,6 +11,9 @@
"exposeDescription": {
"message": "Expose accounts to the current website. Useful for legacy dapps."
},
+ "chartOnlyAvailableEth": {
+ "message": "Chart only available on Ethereum networks."
+ },
"confirmExpose": {
"message": "Are you sure you want to expose your accounts to the current website?"
},
@@ -41,6 +44,15 @@
"providerRequestInfo": {
"message": "This site is requesting access to view your current account address. Always make sure you trust the sites you interact with."
},
+ "about": {
+ "message": "About"
+ },
+ "aboutSettingsDescription": {
+ "message": "Version, support center, and contact info."
+ },
+ "aboutUs": {
+ "message": "About Us"
+ },
"accept": {
"message": "Accept"
},
@@ -71,6 +83,12 @@
"address": {
"message": "Address"
},
+ "advanced": {
+ "message": "Advanced"
+ },
+ "advancedSettingsDescription": {
+ "message": "Access developer features, download State Logs, Reset Account, setup testnets and custom RPC."
+ },
"advancedOptions": {
"message": "Advanced Options"
},
@@ -89,9 +107,6 @@
"addAcquiredTokens": {
"message": "Add the tokens you've acquired using MetaMask"
},
- "advanced": {
- "message": "Advanced"
- },
"agreeTermsOfService": {
"message": "I agree to the Terms of Service"
},
@@ -188,6 +203,18 @@
"buyCoinbaseExplainer": {
"message": "Coinbase is the world’s most popular way to buy and sell Bitcoin, Ethereum, and Litecoin."
},
+ "buyWithWyre": {
+ "message": "Buy ETH with Wyre"
+ },
+ "buyWithWyreDescription": {
+ "message": "Wyre lets you use a credit card to deposit ETH right in to your MetaMask account."
+ },
+ "buyCoinSwitch": {
+ "message": "Buy on CoinSwitch"
+ },
+ "buyCoinSwitchExplainer": {
+ "message": "CoinSwitch is the one-stop destination to exchange more than 300 cryptocurrencies at the best rate."
+ },
"bytes": {
"message": "Bytes"
},
@@ -227,6 +254,9 @@
"chromeRequiredForHardwareWallets": {
"message": "You need to use MetaMask on Google Chrome in order to connect to your Hardware Wallet."
},
+ "company": {
+ "message": "Company"
+ },
"confirm": {
"message": "Confirm"
},
@@ -293,6 +323,12 @@
"continueToCoinbase": {
"message": "Continue to Coinbase"
},
+ "continueToWyre": {
+ "message": "Continue to Wyre"
+ },
+ "continueToCoinSwitch": {
+ "message": "Continue to CoinSwitch"
+ },
"contractDeployment": {
"message": "Contract Deployment"
},
@@ -607,6 +643,12 @@
"gasPriceRequired": {
"message": "Gas Price Required"
},
+ "general": {
+ "message": "General"
+ },
+ "generalSettingsDescription": {
+ "message": "Currency conversion, primary currency, language, blockies identicon"
+ },
"generatingTransaction": {
"message": "Generating transaction"
},
@@ -778,6 +820,9 @@
"ledgerAccountRestriction": {
"message": "You need to make use your last account before you can add a new one."
},
+ "legal": {
+ "message": "Legal"
+ },
"lessThanMax": {
"message": "must be less than or equal to $1.",
"description": "helper for inputting hex as decimal input"
@@ -959,6 +1004,9 @@
"noTransactions": {
"message": "You have no transactions"
},
+ "notEnoughGas": {
+ "message": "Not Enough Gas"
+ },
"notFound": {
"message": "Not Found"
},
@@ -1228,6 +1276,12 @@
"secretPhrase": {
"message": "Enter your secret twelve word phrase here to restore your vault."
},
+ "securityAndPrivacy": {
+ "message": "Security & Privacy"
+ },
+ "securitySettingsDescription": {
+ "message": "Privacy settings and wallet seed phrase"
+ },
"secondsShorthand": {
"message": "Sec"
},
@@ -1313,10 +1367,10 @@
"message": "Select this to show gas price and limit controls directly on the send and confirm screens."
},
"showFiatConversionInTestnets": {
- "message": "Show Conversion in Testnets"
+ "message": "Show Conversion on Testnets"
},
"showFiatConversionInTestnetsDescription": {
- "message": "Select this to show fiat conversion in when you are on Testnets"
+ "message": "Select this to show fiat conversion on Testnets"
},
"showPrivateKeys": {
"message": "Show Private Keys"
diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json
index 04e0c9f0514a..3bdbfa85252e 100644
--- a/app/_locales/es/messages.json
+++ b/app/_locales/es/messages.json
@@ -131,6 +131,12 @@
"buyCoinbaseExplainer": {
"message": "Coinbase es la plataforma global más popular para comprar y vender Bitcoin, Ethereum y Litecoin"
},
+ "buyCoinSwitch": {
+ "message": "Comprar en CoinSwitch"
+ },
+ "buyCoinSwitchExplainer": {
+ "message": "CoinSwitch es el destino de una sola parada para intercambiar más de 300 criptomonedas al mejor precio."
+ },
"cancel": {
"message": "Cancelar"
},
@@ -176,6 +182,9 @@
"continueToCoinbase": {
"message": "Continuar a Coinbase"
},
+ "continueToCoinSwitch": {
+ "message": "Continuar a CoinSwitch"
+ },
"contractDeployment": {
"message": "Desplegar (Deploy) contrato"
},
@@ -686,7 +695,7 @@
"message": "Restaurar desde semilla"
},
"restoreVault": {
- "message": "Restaurar Bóveda"
+ "message": "Restaurar bóveda"
},
"retryWithMoreGas": {
"message": "Vuelva a intentar con un precio de gas más alto aquí"
@@ -1168,10 +1177,10 @@
"message": "Nuevo URL de RPC"
},
"showAdvancedOptions": {
- "message": "Mostrar Opciones Avanzadas"
+ "message": "Mostrar opciones avanzadas"
},
"hideAdvancedOptions": {
- "message": "Ocultar Opciones Avanzadas"
+ "message": "Ocultar opciones avanzadas"
},
"optionalChainId": {
"message": "ChainID (opcional)"
@@ -1183,13 +1192,13 @@
"message": "Apodo (opcional)"
},
"newTotal": {
- "message": "Nuevo Total"
+ "message": "Nuevo total"
},
"newTransactionFee": {
- "message": "Nueva Comisión por Transacción"
+ "message": "Nueva Comisión por transacción"
},
"noConversionRateAvailable": {
- "message": "No Hay Tasa de Conversión"
+ "message": "No hay tasa de conversión"
},
"notFound": {
"message": "No se encontró"
@@ -1238,7 +1247,7 @@
"message": "Prev"
},
"primaryCurrencySetting": {
- "message": "Moneda Principal"
+ "message": "Moneda principal"
},
"primaryCurrencySettingDescription": {
"message": "Seleccionar nativa para prioritizar el que se muestren los valores en la moneda nativa de la cadena (p.ej. ETH). Seleccionar Fíat para prioritzar el que se muestren los valores en la moneda fíat seleccionada."
@@ -1247,10 +1256,10 @@
"message": "Cola"
},
"rejectAll": {
- "message": "Rechazar a Todos"
+ "message": "Rechazar todas"
},
"rejectTxsN": {
- "message": "Rechazar a transacciones de $1"
+ "message": "Rechazar transacciones de $1"
},
"rejectTxsDescription": {
"message": "Está al punto de rechazar transacciones de $1 en lote."
@@ -1271,7 +1280,7 @@
"message": "Restaurar"
},
"revealSeedWordsTitle": {
- "message": "Frase Semilla"
+ "message": "Frase semilla"
},
"revealSeedWordsDescription": {
"message": "Si en algún momento cambias de navegador o de ordenador, necesitarás esta frase semilla para acceder a tus cuentas. Guárdatela en un lugar seguro y secreto."
@@ -1298,7 +1307,7 @@
"message": "Lento"
},
"slower": {
- "message": "Más Lento"
+ "message": "Más lento"
},
"saveAsCsvFile": {
"message": "Guardar como archivo CSV"
@@ -1316,10 +1325,10 @@
"message": "Seg"
},
"selectLocale": {
- "message": "Seleccionar Local"
+ "message": "Seleccionar local"
},
"sendAmount": {
- "message": "Mandar Cantidad"
+ "message": "Mandar cantidad"
},
"sentEther": {
"message": "se mandó ether"
@@ -1361,7 +1370,7 @@
"message": "Petición de Firma"
},
"somethingWentWrong": {
- "message": "¡Ups! Algo se estropeó."
+ "message": "¡Ups! Algo funcionó mal."
},
"speedUp": {
"message": "Agilizar"
@@ -1382,13 +1391,13 @@
"message": "Cambiar de Red"
},
"step1HardwareWallet": {
- "message": "1. Conectar Monedero Físico."
+ "message": "1. Conectar monedero físico."
},
"step1HardwareWalletMsg": {
"message": "Conéctate el monedero físico directamente al ordenador."
},
"step2HardwareWallet": {
- "message": "2. Seleccionar una Cuenta"
+ "message": "2. Seleccionar una cuenta"
},
"step2HardwareWalletMsg": {
"message": "Seleccione la cuenta que quieres ver. Sólo se puede eligir una a la vez."
@@ -1454,10 +1463,10 @@
"message": "Traspasar"
},
"transferFrom": {
- "message": "Traspasar De"
+ "message": "Traspasar de"
},
"trezorHardwareWallet": {
- "message": "Monedero Físico TREZOR"
+ "message": "Monedero físico TREZOR"
},
"tryAgain": {
"message": "Vuelve a intentar"
diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json
index 9018ef578a0f..d2ba100092a8 100644
--- a/app/_locales/fr/messages.json
+++ b/app/_locales/fr/messages.json
@@ -152,6 +152,12 @@
"buyCoinbaseExplainer": {
"message": "Coinbase est le moyen le plus populaire d'acheter et de vendre des Ethers."
},
+ "buyCoinSwitch": {
+ "message": "Acheter sur CoinSwitch"
+ },
+ "buyCoinSwitchExplainer": {
+ "message": "CoinSwitch est la destination unique pour échanger plus de 300 crypto-devises au meilleur taux."
+ },
"bytes": {
"message": "Bytes"
},
@@ -233,6 +239,9 @@
"continueToCoinbase": {
"message": "Continuer vers Coinbase"
},
+ "continueToCoinSwitch": {
+ "message": "Continuer vers CoinSwitch"
+ },
"contractDeployment": {
"message": "Déploiement de contrat"
},
diff --git a/app/_locales/hn/messages.json b/app/_locales/hn/messages.json
index 4face7bd6b15..6c27ee1bcfef 100644
--- a/app/_locales/hn/messages.json
+++ b/app/_locales/hn/messages.json
@@ -128,6 +128,12 @@
"buyCoinbaseExplainer": {
"message": "बिल्टकोइन, इथीरियम और लाइटकोइन खरीदने और बेचने के लिए दुनिया का सबसे लोकप्रिय तरीका कॉइनबेस है।"
},
+ "buyCoinSwitch": {
+ "message": "कॉइनस्विच पर खरीदें"
+ },
+ "buyCoinSwitchExplainer": {
+ "message": "कॉइनस्विच 300 से अधिक क्रिप्टोक्रांसियों को सर्वोत्तम दर पर विनिमय करने का एक-स्टॉप गंतव्य है।"
+ },
"cancel": {
"message": "रद्द करें"
},
@@ -155,6 +161,9 @@
"continueToCoinbase": {
"message": "कॉइनबेस को ब्हेजना जारी रखें"
},
+ "continueToCoinSwitch": {
+ "message": "कॉइनस्विच को ब्हेजना जारी रखें"
+ },
"contractDeployment": {
"message": "अनुबंध परिनियोजन व तैनाती"
},
diff --git a/app/_locales/ht/messages.json b/app/_locales/ht/messages.json
index c94ddc8d2b29..4a4c92f3a987 100644
--- a/app/_locales/ht/messages.json
+++ b/app/_locales/ht/messages.json
@@ -155,6 +155,12 @@
"buyCoinbaseExplainer": {
"message": "Coinbase se fason ki pi popilè nan mond lan yo achte ak vann Bitcoin, Ethereum, ak Litecoin."
},
+ "buyCoinSwitch": {
+ "message": "Achte sou CoinSwitch"
+ },
+ "buyCoinSwitchExplainer": {
+ "message": "CoinSwitch se destinasyon nan yon sèl-Stop nan echanj plis pase 300 kriptoksèr nan pousantaj la pi byen."
+ },
"bytes": {
"message": "Bytes"
},
@@ -239,6 +245,9 @@
"continueToCoinbase": {
"message": "Kontinye Coinbase"
},
+ "continueToCoinSwitch": {
+ "message": "Kontinye CoinSwitch"
+ },
"contractDeployment": {
"message": "Kontra Deplwaman"
},
diff --git a/app/_locales/it/messages.json b/app/_locales/it/messages.json
index 999bb8ec431a..9e0f1f06dbde 100644
--- a/app/_locales/it/messages.json
+++ b/app/_locales/it/messages.json
@@ -179,6 +179,12 @@
"buyCoinbaseExplainer": {
"message": "Coinbase è il servizio più popolare al mondo per comprare e vendere Bitcoin, Ethereum e Litecoin."
},
+ "buyCoinSwitch": {
+ "message": "Compra su CoinSwitch"
+ },
+ "buyCoinSwitchExplainer": {
+ "message": "CoinSwitch è la destinazione one-stop per lo scambio di oltre 300 criptovalute alla migliore tariffa."
+ },
"bytes": {
"message": "Bytes"
},
@@ -281,6 +287,9 @@
"continueToCoinbase": {
"message": "Continua su Coinbase"
},
+ "continueToCoinSwitch": {
+ "message": "Continua su CoinSwitch"
+ },
"contractDeployment": {
"message": "Distribuzione Contratto"
},
diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json
index 93b5447d71c3..fcac67894ca3 100644
--- a/app/_locales/ja/messages.json
+++ b/app/_locales/ja/messages.json
@@ -122,6 +122,12 @@
"buyCoinbaseExplainer": {
"message": "Etherを購入できます。Coinbaseは、世界的なBitcoin、Ethereum、そしてLitecoinの取引所です。"
},
+ "buyCoinSwitch": {
+ "message": "CoinSwitchのサイトで購入"
+ },
+ "buyCoinSwitchExplainer": {
+ "message": "CoinSwitchは、最高のレートで300以上の暗号化交換を行うワンストップの宛先です。"
+ },
"cancel": {
"message": "キャンセル"
},
@@ -149,6 +155,9 @@
"continueToCoinbase": {
"message": "Coinbaseを開く"
},
+ "continueToCoinSwitch": {
+ "message": "CoinSwitchを開く"
+ },
"contractDeployment": {
"message": "コントラクトのデプロイ"
},
diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json
index 4c6541d216ef..bf11640e26f7 100644
--- a/app/_locales/ko/messages.json
+++ b/app/_locales/ko/messages.json
@@ -30,13 +30,16 @@
"message": "승인 데이터 삭제"
},
"reject": {
- "message": "받지 않다"
+ "message": "거부"
},
"providerAPIRequest": {
"message": "Web3 API 요청"
},
"reviewProviderRequest": {
- "message": "이 Ethereum API 요청을 검토하십시오."
+ "message": "이 Ethereum API 요청을 검토하세요."
+ },
+ "providerRequest": {
+ "message": "$1이 당신의 계정에 연결하길 원합니다."
},
"providerRequestInfo": {
"message": "아래 나열된 도메인은 Web3 API에 대한 액세스를 요청하여 Ethereum 블록 체인과 상호 작용할 수 있습니다. Ethereum 액세스를 승인하기 전에 항상 올바른 사이트에 있는지 다시 확인하십시오."
@@ -44,6 +47,9 @@
"accept": {
"message": "수락"
},
+ "acceleratingATransaction": {
+ "message": "* 더 높은 가스 요금을 사용하여 트랜잭션을 가속화하면 네트워크에 의해 더 빨리 처리될 가능성이 증가하지만 항상 빠른 처리가 보장되는 것은 아닙니다."
+ },
"accessingYourCamera": {
"message": "카메라에 접근 중..."
},
@@ -68,6 +74,9 @@
"address": {
"message": "주소"
},
+ "advancedOptions": {
+ "message": "고급 옵션"
+ },
"addCustomToken": {
"message": "사용자 정의 토큰 추가"
},
@@ -83,12 +92,21 @@
"addAcquiredTokens": {
"message": "메타마스크를 통해 획득한 토큰 추가"
},
+ "advanced": {
+ "message": "고급"
+ },
+ "allDone": {
+ "message": "모두 완료"
+ },
"amount": {
"message": "수량"
},
"amountPlusGas": {
"message": "수량 + 가스"
},
+ "amountPlusTxFee": {
+ "message": "Amount + TX Fee"
+ },
"appDescription": {
"message": "이더리움 브라우저 확장 프로그램",
"description": "애플리케이션 설명"
@@ -109,12 +127,18 @@
"attemptToCancel": {
"message": "취소 하시겠습니까?"
},
+ "attemptToCancelDescription": {
+ "message": "이 취소 시도가 기존 트랜잭션의 취소를 보장하지 않습니다. 취소 시도가 성공하면 위 거래 수수료가 부과됩니다."
+ },
"attributions": {
"message": "속성"
},
"available": {
"message": "사용 가능"
},
+ "average": {
+ "message": "평균"
+ },
"back": {
"message": "돌아가기"
},
@@ -127,6 +151,9 @@
"balanceIsInsufficientGas": {
"message": "현재 가스 총합에 대해 잔액이 부족합니다"
},
+ "basic": {
+ "message": "Basic"
+ },
"beta": {
"message": "BETA"
},
@@ -155,6 +182,12 @@
"buyCoinbaseExplainer": {
"message": "코인베이스는 비트코인, 이더리움, 라이트코인을 거래할 수 있는 유명한 거래소입니다."
},
+ "buyCoinSwitch": {
+ "message": "코인 스위치 구입"
+ },
+ "buyCoinSwitchExplainer": {
+ "message": "코인 스위치는 최상의 속도로 300 개 이상의 크립토 카드를 교환하는 원 스톱 목적지입니다."
+ },
"bytes": {
"message": "바이트"
},
@@ -170,6 +203,9 @@
"cancellationGasFee": {
"message": "취소 가스 수수료"
},
+ "cancelled": {
+ "message": "취소됨"
+ },
"cancelN": {
"message": "모든 $1 트랜잭션 취소"
},
@@ -179,6 +215,12 @@
"clickCopy": {
"message": "클릭하여 복사"
},
+ "clickToAdd": {
+ "message": "계정에 토큰을 추가하기 위해 $1를 클릭하세요."
+ },
+ "clickToRevealSeed": {
+ "message": "비밀 단어를 보기 위해 여기를 클릭하세요."
+ },
"close": {
"message": "닫기"
},
@@ -188,6 +230,9 @@
"confirm": {
"message": "승인"
},
+ "confirmationTime": {
+ "message": "처리 시간 (초)"
+ },
"confirmed": {
"message": "승인됨"
},
@@ -197,27 +242,39 @@
"confirmPassword": {
"message": "비밀번호 확인"
},
+ "confirmSecretBackupPhrase": {
+ "message": "비밀 백업 구문 확인"
+ },
"confirmTransaction": {
"message": "트랜잭션 승인"
},
+ "congratulations": {
+ "message": "축하합니다."
+ },
"connectHardwareWallet": {
"message": "하드웨어 지갑 연결"
},
"connect": {
"message": "연결"
},
+ "connectRequest": {
+ "message": "연결 요청"
+ },
"connecting": {
"message": "연결 중..."
},
+ "connectingTo": {
+ "message": "$1에 연결"
+ },
+ "connectingToKovan": {
+ "message": "Kovan 테스트넷 접속 중"
+ },
"connectingToMainnet": {
"message": "이더리움 메인넷 접속 중"
},
"connectingToRopsten": {
"message": "Ropsten 테스트넷 접속 중"
},
- "connectingToKovan": {
- "message": "Kovan 테스트넷 접속 중"
- },
"connectingToRinkeby": {
"message": "Rinkeby 테스트넷 접속 중"
},
@@ -236,6 +293,9 @@
"continueToCoinbase": {
"message": "코인베이스로 계속하기"
},
+ "continueToCoinSwitch": {
+ "message": "코인 스위치 계속하기"
+ },
"contractDeployment": {
"message": "컨트랙트 배포"
},
@@ -263,6 +323,12 @@
"copyToClipboard": {
"message": "클립보드로 복사"
},
+ "copyTransactionId": {
+ "message": "트랜잭션 아이디 복사"
+ },
+ "copiedTransactionId": {
+ "message": "트랜잭션 아이디 복사됨"
+ },
"copyButton": {
"message": " 복사 "
},
@@ -275,9 +341,15 @@
"createAccount": {
"message": "계정 생성"
},
+ "createAWallet": {
+ "message": "지갑 생성하기"
+ },
"createDen": {
"message": "생성"
},
+ "createPassword": {
+ "message": "비밀번호 생성"
+ },
"crypto": {
"message": "암호화폐",
"description": "거래 유형 (암호화폐)"
@@ -285,6 +357,9 @@
"currentConversion": {
"message": "현재 통화"
},
+ "currencyConversion": {
+ "message": "통화 변환"
+ },
"currentLanguage": {
"message": "현재 언어"
},
@@ -297,6 +372,9 @@
"customGas": {
"message": "가스 설정"
},
+ "customGasSubTitle": {
+ "message": "수수료를 높히면 처리 시간이 단축될 수 있지만, 그것이 보장되진 않습니다."
+ },
"customToken": {
"message": "사용자 정의 토큰"
},
@@ -310,7 +388,7 @@
"message": "소수점은 0 이상이고 36 이하여야 합니다."
},
"decimal": {
- "message": "소수점 정확도"
+ "message": "소수 자릿수"
},
"defaultNetwork": {
"message": "이더리움 트랜잭션의 기본 네트워크는 메인넷입니다."
@@ -364,6 +442,9 @@
"downloadGoogleChrome": {
"message": "구글 크롬 다운로드"
},
+ "downloadSecretBackup": {
+ "message": "이 비밀 백업 구문을 다운로드하여 암호화된 외장 하드 드라이브나 저장 매체에 안전하게 보관하세요."
+ },
"downloadStateLogs": {
"message": "상태 로그 다운로드"
},
@@ -388,6 +469,30 @@
"encryptNewDen": {
"message": "새로운 DEN을 암호화"
},
+ "endOfFlowMessage1": {
+ "message": "인증을 통과했습니다 - 시드 구문을 안전하게 보관하세요. 그것은 당신의 의무입니다."
+ },
+ "endOfFlowMessage2": {
+ "message": "안전하게 시드 구문을 보관하는 팁"
+ },
+ "endOfFlowMessage3": {
+ "message": "여러 군데에 시드 구문을 백업하세요."
+ },
+ "endOfFlowMessage4": {
+ "message": "누구와도 시드 구문을 공유하지마세요."
+ },
+ "endOfFlowMessage5": {
+ "message": "피싱에 주의하세요! 메타마스크는 절대 갑작스럽게 당신의 시드 구문을 묻지 않습니다."
+ },
+ "endOfFlowMessage6": {
+ "message": "만약 시드 구문을 다시 백업해야한다면, 설정 -> 보안에서 확인할 수 있습니다. "
+ },
+ "endOfFlowMessage7": {
+ "message": "만약 질문이 있거나 피싱을 목격했다면 support@metamask.io으로 메일을 보내주세요."
+ },
+ "endOfFlowMessage8": {
+ "message": "메타마스크는 당신의 시드 구문을 복원해줄 수 없습니다. 더 알아보기."
+ },
"ensNameNotFound": {
"message": "ENS 이름을 찾을 수 없습니다"
},
@@ -400,9 +505,15 @@
"enterPasswordContinue": {
"message": "계속하기 위해 비밀번호 입력"
},
+ "eth": {
+ "message": "ETH"
+ },
"etherscanView": {
"message": "이더스캔에서 계정보기"
},
+ "estimatedProcessingTimes": {
+ "message": "예상 처리 시간"
+ },
"exchangeRate": {
"message": "환율"
},
@@ -418,6 +529,12 @@
"failed": {
"message": "실패"
},
+ "fast": {
+ "message": "빠름"
+ },
+ "faster": {
+ "message": "빨라짐"
+ },
"fiat": {
"message": "FIAT",
"description": "거래 형식"
@@ -457,18 +574,33 @@
"gasLimitCalculation": {
"message": "네트워크 성공률을 기반으로 적합한 가스 한도를 계산합니다."
},
+ "gasLimitInfoModalContent": {
+ "message": "가스 한도는 당신이 기꺼이 소비할 수 있는 가스의 최대 수량입니다."
+ },
"gasLimitRequired": {
"message": "가스 한도가 필요합니다."
},
"gasLimitTooLow": {
"message": "가스 한도는 최소 21000 이상이어야 합니다."
},
+ "gasUsed": {
+ "message": "사용된 가스"
+ },
"generatingSeed": {
"message": "시드 생성 중..."
},
"gasPrice": {
"message": "가스 가격 (GWEI)"
},
+ "gasPriceExtremelyLow": {
+ "message": "가스 가격 매우 낮음"
+ },
+ "gasPriceInfoModalContent": {
+ "message": "가스 가격은 가스 1당 지불할 gwei 단위의 이더 수량을 명시합니다."
+ },
+ "gasPriceNoDenom": {
+ "message": "가스 가격"
+ },
"gasPriceCalculation": {
"message": "네트워크 성공률을 기반으로 적합한 가스 가격을 계산합니다."
},
@@ -488,10 +620,16 @@
"getHelp": {
"message": "도움말"
},
+ "getStarted": {
+ "message": "시작하기"
+ },
"greaterThanMin": {
"message": "$1 이상이어야 합니다.",
"description": "10진수 입력으로 hex값 입력을 도와줍니다"
},
+ "happyToSeeYou": {
+ "message": "We’re happy to see you."
+ },
"hardware": {
"message": "하드웨어"
},
@@ -545,12 +683,21 @@
"importAccountMsg": {
"message": " 가져온 계정은 메타마스크에서 원래 생성된 계정의 시드구문과 연관성이 없습니다. 가져온 계정에 대해 더 배우기 "
},
+ "importAccountSeedPhrase": {
+ "message": "시드 구문으로 계정 가져오기"
+ },
"importAnAccount": {
"message": "계정 가져오기"
},
"importDen": {
"message": "기존의 DEN 가져오기"
},
+ "importWallet": {
+ "message": "지갑 가져오기"
+ },
+ "importYourExisting": {
+ "message": "12개 단어로 구성된 시드 구문으로 이미 만들어진 지갑을 가져오기"
+ },
"imported": {
"message": "가져온 계정",
"description": "이 상태는 해당 계정이 keyring으로 완전히 로드된 상태임을 표시합니다"
@@ -558,6 +705,9 @@
"importUsingSeed": {
"message": "계정 시드 구문으로 가져오기"
},
+ "importWithSeedPhrase": {
+ "message": "시드 구문 가져오기"
+ },
"info": {
"message": "정보"
},
@@ -567,6 +717,9 @@
"initialTransactionConfirmed": {
"message": "초기 트랜잭션이 네트워크를 통해 확정되었습니다. 확인을 누르면 이전으로 돌아갑니다."
},
+ "insufficientBalance": {
+ "message": "잔액 부족."
+ },
"insufficientFunds": {
"message": "충분하지 않은 자금."
},
@@ -623,6 +776,9 @@
"message": "$1 이하여야합니다.",
"description": "10진수 입력으로 hex값 입력을 도와줍니다"
},
+ "letsGoSetUp": {
+ "message": "네, 설정해볼게요!"
+ },
"likeToAddTokens": {
"message": "토큰을 추가하시겠습니까?"
},
@@ -632,6 +788,9 @@
"limit": {
"message": "한도"
},
+ "liveGasPricePredictions": {
+ "message": "실시간 가스 가격 예측"
+ },
"loading": {
"message": "로딩 중..."
},
@@ -656,6 +815,9 @@
"mainnet": {
"message": "이더리움 메인넷"
},
+ "memorizePhrase": {
+ "message": "이 구문을 기억하세요."
+ },
"menu": {
"message": "메뉴"
},
@@ -663,17 +825,20 @@
"message": "메시지"
},
"metamaskDescription": {
- "message": "메타마스크는 이더리움을 위한 안전한 신분 저장소입니다."
- },
- "metamaskVersion": {
- "message": "메타마스크 버전"
+ "message": "메타마스크는 이더리움을 위한 안전한 저장소입니다."
},
"metamaskSeedWords": {
"message": "메타마스크 시드 단어"
},
+ "metamaskVersion": {
+ "message": "메타마스크 버전"
+ },
"min": {
"message": "최소"
},
+ "missingYourTokens": {
+ "message": "당신의 토큰이 보이지 않나요?"
+ },
"myAccounts": {
"message": "내 계정"
},
@@ -713,11 +878,44 @@
"newPassword": {
"message": "새 비밀번호 (최소 8자 이상)"
},
+ "newPassword8Chars": {
+ "message": "새 비밀번호 (최소 8문자)"
+ },
"newRecipient": {
"message": "받는 사람"
},
- "newRPC": {
- "message": "새로운 RPC URL"
+ "newNetwork": {
+ "message": "새 네트워크"
+ },
+ "newToMetaMask": {
+ "message": "메타마스크를 처음 사용하시나요?"
+ },
+ "noAlreadyHaveSeed": {
+ "message": "아니요, 이미 시드 구문을 가지고 있습니다."
+ },
+ "rpcURL": {
+ "message": "새 RPC 주소"
+ },
+ "showAdvancedOptions": {
+ "message": "고급 옵션 보기"
+ },
+ "hideAdvancedOptions": {
+ "message": "고급 옵션 숨기기"
+ },
+ "optionalChainId": {
+ "message": "ChainID (선택)"
+ },
+ "optionalSymbol": {
+ "message": "Symbol (선택)"
+ },
+ "optionalNickname": {
+ "message": "Nickname (선택)"
+ },
+ "newTotal": {
+ "message": "새 합계"
+ },
+ "newTransactionFee": {
+ "message": "새 트랜잭션 수수료"
},
"next": {
"message": "다음"
@@ -743,12 +941,12 @@
"notStarted": {
"message": "시작 안 됨"
},
- "noWebcamFound": {
- "message": "컴퓨터의 웹캠을 찾을 수 없습니다. 다시 시도해보세요."
- },
"noWebcamFoundTitle": {
"message": "웹캠이 없습니다"
},
+ "noWebcamFound": {
+ "message": "컴퓨터의 웹캠을 찾을 수 없습니다. 다시 시도해보세요."
+ },
"oldUI": {
"message": "구버전 UI"
},
@@ -778,6 +976,12 @@
"parameters": {
"message": "매개변수"
},
+ "participateInMetaMetrics": {
+ "message": "MetaMetrics 참여"
+ },
+ "participateInMetaMetricsDescription": {
+ "message": "메타마스크를 더 좋게 만들기 위해 MetaMetrics에 참여하세요."
+ },
"password": {
"message": "비밀번호"
},
@@ -820,6 +1024,12 @@
"prev": {
"message": "이전"
},
+ "primaryCurrencySetting": {
+ "message": "주 화폐"
+ },
+ "primaryCurrencySettingDescription": {
+ "message": "체인의 고유 통화 값으로 우선 표기하시려면 네이티브를 선택하세요. (예: ETH) 설정하신 Fiat 통화 값으로 우선 표기하시려면 Fiat을 선택하세요."
+ },
"privacyMsg": {
"message": "개인정보 보호 정책"
},
@@ -887,6 +1097,9 @@
"restoreAccountWithSeed": {
"message": "시드 구문으로 계정 복구하기"
},
+ "requestsAwaitingAcknowledgement": {
+ "message": "요청이 인정되기까지 대기중"
+ },
"required": {
"message": "필요함"
},
@@ -942,11 +1155,11 @@
"save": {
"message": "저장"
},
- "speedUpTitle": {
- "message": "트랜잭션 속도 향상하기"
+ "slow": {
+ "message": "느림"
},
- "speedUpSubtitle": {
- "message": "트랜잭션 가스 가격을 올려서 해당 트랜잭션에 덮어쓰고 속도를 빠르게 합니다"
+ "slower": {
+ "message": "느려짐"
},
"saveAsCsvFile": {
"message": "CSV 파일로 저장"
@@ -970,11 +1183,23 @@
"searchResults": {
"message": "검색 결과"
},
+ "secretBackupPhrase": {
+ "message": "비밀 백업 구문"
+ },
+ "secretBackupPhraseDescription": {
+ "message": "비밀 백업 구문을 사용하면 계정을 쉽게 백업하고 복원할 수 있습니다."
+ },
+ "secretBackupPhraseWarning": {
+ "message": "경고: 백업 구문을 절대 공개하지 마세요. 이 구문을 가진 누군가 당신의 이더를 영원히 가지고 갈 수 있습니다."
+ },
"secretPhrase": {
"message": "12개 단어로 구성된 비밀 구문을 입력하여 저장소를 복구하세요."
},
- "newPassword8Chars": {
- "message": "새 비밀번호 (최소 8문자)"
+ "secondsShorthand": {
+ "message": "초"
+ },
+ "seedPhrasePlaceholder": {
+ "message": "각 단어를 스페이스로 구분해주세요"
},
"seedPhraseReq": {
"message": "시드 구문은 12개의 단어입니다"
@@ -985,6 +1210,9 @@
"selectCurrency": {
"message": "통화 선택"
},
+ "selectEachPhrase": {
+ "message": "백업 구문이 올바른지 확인하기 위해 각 단어를 순서에 맞게 선택해주세요."
+ },
"selectLocale": {
"message": "언어 선택"
},
@@ -997,6 +1225,9 @@
"send": {
"message": "전송"
},
+ "sendAmount": {
+ "message": "전송 수량"
+ },
"sendETH": {
"message": "ETH 보내기"
},
@@ -1024,6 +1255,9 @@
"selectAnAccountHelp": {
"message": "메타마스크에서 보기 위한 계정 선택"
},
+ "selectAHigherGasFee": {
+ "message": "트랜잭션 처리를 가속하기 위해 더 높은 가스 요금을 선택하세요.*"
+ },
"selectHdPath": {
"message": "HD 경로 지정"
},
@@ -1039,6 +1273,18 @@
"shapeshiftBuy": {
"message": "Shapeshift를 통해서 구매하기"
},
+ "showAdvancedGasInline": {
+ "message": "고급 가스 제어"
+ },
+ "showAdvancedGasInlineDescription": {
+ "message": "전송 및 확인 화면에서 직접 가스 가격과 한도 제어를 표시하려면 이 옵션을 선택해주세요."
+ },
+ "showFiatConversionInTestnets": {
+ "message": "테스트 넷에서 fiat 변환 보여주기"
+ },
+ "showFiatConversionInTestnetsDescription": {
+ "message": "테스트 넷에서 fiat 변환을 보기 위해 활성화하세요"
+ },
"showPrivateKeys": {
"message": "개인키 보기"
},
@@ -1054,12 +1300,12 @@
"sign": {
"message": "서명"
},
- "signed": {
- "message": "서명됨"
- },
"signatureRequest": {
"message": "서명 요청"
},
+ "signed": {
+ "message": "서명됨"
+ },
"signMessage": {
"message": "메시지 서명"
},
@@ -1072,9 +1318,30 @@
"sigRequested": {
"message": "서명이 요청됨"
},
+ "somethingWentWrong": {
+ "message": "헉! 뭔가 잘못됐어요."
+ },
"spaceBetween": {
"message": "단어 사이에는 공백만 올 수 있습니다"
},
+ "speedUp": {
+ "message": "속도 향상"
+ },
+ "speedUpTitle": {
+ "message": "트랜잭션 속도 향상하기"
+ },
+ "speedUpSubtitle": {
+ "message": "트랜잭션 가스 가격을 올려서 해당 트랜잭션에 덮어쓰고 속도를 빠르게 합니다"
+ },
+ "speedUpCancellation": {
+ "message": "취소 속도 향상"
+ },
+ "speedUpTransaction": {
+ "message": "트랜잭션 속도 향상"
+ },
+ "switchNetworks": {
+ "message": "네트워크 변경"
+ },
"status": {
"message": "상태"
},
@@ -1105,6 +1372,9 @@
"step3HardwareWalletMsg": {
"message": "다른 이더리움 계정을 사용하듯 하드웨어 계정을 사용합니다. dApps을 로그인하거나, 이더를 보내거나, ERC20 토큰 혹은 대체 가능하지 않은 토큰 (예를 들어 CryptoKitties)을 사거나 저장하거나 합니다."
},
+ "storePhrase": {
+ "message": "이 구문을 1Password같은 암호 관리자에 저장하세요."
+ },
"submit": {
"message": "제출"
},
@@ -1126,8 +1396,14 @@
"testFaucet": {
"message": "파우셋 테스트"
},
+ "thisWillCreate": {
+ "message": "새로운 지갑과 시드 구문을 생성"
+ },
+ "tips": {
+ "message": "팁"
+ },
"to": {
- "message": "받는이: "
+ "message": "받는이"
},
"toETHviaShapeShift": {
"message": "ShapeShift를 통해 $1를 ETH로 바꾸기",
@@ -1145,6 +1421,9 @@
"tokenBalance": {
"message": "현재 토큰 잔액:"
},
+ "tokenContractAddress": {
+ "message": "토큰 컨트랙트 주소"
+ },
"tokenSelection": {
"message": "토큰을 검색하거나 유명한 토큰 리스트에서 선택하시기 바랍니다."
},
@@ -1160,21 +1439,63 @@
"transaction": {
"message": "트랜잭션"
},
+ "transactionConfirmed": {
+ "message": "트랜잭션이 승인됨 $2."
+ },
+ "transactionCreated": {
+ "message": "$1에 대한 트랜잭션이 생성됨 $2."
+ },
+ "transactionWithNonce": {
+ "message": "트랜잭션 $1"
+ },
+ "transactionDropped": {
+ "message": "트랜잭션이 드롭됨 $2."
+ },
+ "transactionSubmitted": {
+ "message": "$1의 가스 요금으로 트랜잭션이 제출됨 $2."
+ },
+ "transactionResubmitted": {
+ "message": "$1으로 가스 요금을 올려 트랜잭션이 다시 제출됨 $2."
+ },
+ "transactionUpdated": {
+ "message": "트랜잭션이 수정됨 $2."
+ },
+ "transactionUpdatedGas": {
+ "message": "$1의 가스 요금으로 트랜잭션이 수정됨 $2."
+ },
+ "transactionErrored": {
+ "message": "트랜잭션 오류 발생."
+ },
+ "transactionCancelAttempted": {
+ "message": "$1의 가스 요금으로 트랜잭션 취소가 시도됨 $2."
+ },
+ "transactionCancelSuccess": {
+ "message": "트랜잭션이 성공적으로 취소됨 $2."
+ },
"transactions": {
"message": "트랜잭션"
},
"transactionError": {
"message": "트랜잭션 오류. 컨트랙트 코드에서 예외 발생(Exception thrown)."
},
+ "transactionFee": {
+ "message": "수수료"
+ },
"transactionMemo": {
"message": "트랜잭션 메모 (선택사항)"
},
"transactionNumber": {
"message": "트랜잭션 번호"
},
+ "transactionTime": {
+ "message": "트랜잭션 시간"
+ },
"transfer": {
"message": "전송"
},
+ "transferFrom": {
+ "message": "보내는 이"
+ },
"transfers": {
"message": "전송"
},
@@ -1194,6 +1515,9 @@
"typePassword": {
"message": "비밀번호를 입력하세요"
},
+ "uiMigrationAnnouncement": {
+ "message": "새로운 메타마스크 UI에 오신 것을 환영합니다. UI에 대한 피드백 또는 기능 요청이 있는 경우, 당사의 지원팀 또는 GitHub에 연락해주세요."
+ },
"uiWelcome": {
"message": "새로운 UI에 오신 것을 환영합니다. (Beta)"
},
@@ -1234,7 +1558,10 @@
"message": "잠금 해제"
},
"unlockMessage": {
- "message": "우리가 기다리던 분권형 웹입니다"
+ "message": "우리가 기다리던 탈 중앙화 웹입니다"
+ },
+ "updatedWithDate": {
+ "message": "$1에 업데이트 됨"
},
"uriErrorMsg": {
"message": "URI는 HTTP/HTTPS로 시작해야 합니다."
@@ -1279,9 +1606,15 @@
"whatsThis": {
"message": "이것은 무엇인가요?"
},
+ "writePhrase": {
+ "message": "이 구문을 종이에 써서 안전한 장소에 보관하세요. 만약 당신이 더 높은 수준의 보안을 원한다면, 그것을 여러 장의 종이에 적어서 각각 2-3개의 다른 위치에 보관하세요."
+ },
"yesLetsTry": {
"message": "네, 시도해보겠습니다."
},
+ "youNeedToAllowCameraAccess": {
+ "message": "이 기능을 사용하려면 카메라 접근을 허용해야 합니다."
+ },
"yourSigRequested": {
"message": "서명을 요청 중입니다."
},
@@ -1290,8 +1623,5 @@
},
"yourPrivateSeedPhrase": {
"message": "개인 시드 구문"
- },
- "youNeedToAllowCameraAccess": {
- "message": "이 기능을 사용하려면 카메라 접근을 허용해야 합니다."
}
}
diff --git a/app/_locales/nl/messages.json b/app/_locales/nl/messages.json
index b7b06e0751e3..12bde6585ce3 100644
--- a/app/_locales/nl/messages.json
+++ b/app/_locales/nl/messages.json
@@ -128,6 +128,12 @@
"buyCoinbaseExplainer": {
"message": "Coinbase is 's werelds populairste manier om Bitcoin, Ethereum en Litecoin te kopen en verkopen."
},
+ "buyCoinSwitch": {
+ "message": "Koop op CoinSwitch"
+ },
+ "buyCoinSwitchExplainer": {
+ "message": "CoinSwitch is de one-stop-bestemming om meer dan 300 cryptocurrencies tegen de beste prijs in te wisselen."
+ },
"cancel": {
"message": "Annuleer"
},
@@ -155,6 +161,9 @@
"continueToCoinbase": {
"message": "Ga verder naar Coinbase"
},
+ "continueToCoinSwitch": {
+ "message": "Ga verder naar CoinSwitch"
+ },
"contractDeployment": {
"message": "Contractimplementatie"
},
diff --git a/app/_locales/ph/messages.json b/app/_locales/ph/messages.json
index cef6868682b0..da3cf266dc01 100644
--- a/app/_locales/ph/messages.json
+++ b/app/_locales/ph/messages.json
@@ -110,6 +110,12 @@
"buyCoinbaseExplainer": {
"message": "Ang Coinbase ang pinakasikat na paraan upang bumili at magbenta ng Bitcoin, Ethereum, at Litecoin sa buong mundo."
},
+ "buyCoinSwitch": {
+ "message": "Bumili sa CoinSwitch"
+ },
+ "buyCoinSwitchExplainer": {
+ "message": "Ang CoinSwitch ay isang one-stop destination upang makipagpalitan ng higit sa 300 mga cryptocurrency sa pinakamahusay na rate."
+ },
"cancel": {
"message": "Kanselahin"
},
@@ -131,6 +137,9 @@
"continueToCoinbase": {
"message": "Magpatuloy sa Coinbase"
},
+ "continueToCoinSwitch": {
+ "message": "Magpatuloy sa CoinSwitch"
+ },
"contractDeployment": {
"message": "Pag-deploy ng Contract"
},
diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json
index 08af4b2a60a5..e51b1f72e734 100644
--- a/app/_locales/pt/messages.json
+++ b/app/_locales/pt/messages.json
@@ -128,6 +128,12 @@
"buyCoinbaseExplainer": {
"message": "Coinbase é a forma mais conhecida para comprar e vender Bitcoin, Ethereum, e Litecoin."
},
+ "buyCoinSwitch": {
+ "message": "Comprar no CoinSwitch"
+ },
+ "buyCoinSwitchExplainer": {
+ "message": "CoinSwitch é o destino único para trocar mais de 300 moedas criptográficas com a melhor taxa."
+ },
"cancel": {
"message": "Cancelar"
},
@@ -155,6 +161,9 @@
"continueToCoinbase": {
"message": "Continuar para o Coinbase"
},
+ "continueToCoinSwitch": {
+ "message": "Continuar para o CoinSwitch"
+ },
"contractDeployment": {
"message": "Distribuição do Contrato"
},
diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json
index 9568c64e6aba..f80efcc56ae1 100644
--- a/app/_locales/ru/messages.json
+++ b/app/_locales/ru/messages.json
@@ -131,6 +131,12 @@
"buyCoinbaseExplainer": {
"message": "Биржа Coinbase – это наиболее популярный способ купить или продать Bitcoin, Ethereum и Litecoin."
},
+ "buyCoinSwitch": {
+ "message": "Купить на CoinSwitch"
+ },
+ "buyCoinSwitchExplainer": {
+ "message": "CoinSwitch - это однонаправленное место для обмена более 300 криптоконверсий по наилучшей цене."
+ },
"ok": {
"message": "ОК"
},
@@ -164,6 +170,9 @@
"continueToCoinbase": {
"message": "Продолжить в Coinbase"
},
+ "continueToCoinSwitch": {
+ "message": "Продолжить в CoinSwitch"
+ },
"contractDeployment": {
"message": "Развертывание контракта"
},
diff --git a/app/_locales/sl/messages.json b/app/_locales/sl/messages.json
index 857c37021c2a..2f3616deece5 100644
--- a/app/_locales/sl/messages.json
+++ b/app/_locales/sl/messages.json
@@ -218,6 +218,12 @@
"ok": {
"message": "V redu"
},
+ "buyCoinSwitch": {
+ "message": "Kupi na CoinSwitch"
+ },
+ "buyCoinSwitchExplainer": {
+ "message": "CoinSwitch je destinacija na enem mestu za izmenjavo več kot 300 kriptokotovitev po najboljši hitrosti."
+ },
"cancel": {
"message": "Prekliči"
},
@@ -305,6 +311,9 @@
"continueToCoinbase": {
"message": "Nadaljuj na Coinbase"
},
+ "continueToCoinSwitch": {
+ "message": "Nadaljuj na CoinSwitch"
+ },
"contractDeployment": {
"message": "Ustvarjanje pogodbe"
},
diff --git a/app/_locales/th/messages.json b/app/_locales/th/messages.json
index b80c39b98692..d851d325fdc4 100644
--- a/app/_locales/th/messages.json
+++ b/app/_locales/th/messages.json
@@ -128,6 +128,12 @@
"buyCoinbaseExplainer": {
"message": "Coinbase เป็นแหล่งซื้อขายบิตคอยน์ไลท์คอยน์และอีเธอเรียมที่ได้รับความนิยมสูงสุดในโลก"
},
+ "buyCoinSwitch": {
+ "message": "ซื้อด้วย CoinSwitch"
+ },
+ "buyCoinSwitchExplainer": {
+ "message": "CoinSwitch เป็นจุดหมายปลายทางแบบครบวงจรในการแลกเปลี่ยนสกุลเงินมากกว่า 300 ครั้งในอัตราที่ดีที่สุด"
+ },
"cancel": {
"message": "ยกเลิก"
},
@@ -155,6 +161,9 @@
"continueToCoinbase": {
"message": "ไปที่ Coinbase"
},
+ "continueToCoinSwitch": {
+ "message": "ไปที่ CoinSwitch"
+ },
"contractDeployment": {
"message": "การติดตั้งสัญญา"
},
diff --git a/app/_locales/tml/messages.json b/app/_locales/tml/messages.json
index 03ae4b7ff26b..e1ef451381b6 100644
--- a/app/_locales/tml/messages.json
+++ b/app/_locales/tml/messages.json
@@ -131,6 +131,12 @@
"buyCoinbaseExplainer": {
"message": "கோஇன்பசே பிறகாய்ன் , எதெரியும் மற்றும் ளிட்டசோன் வாங்க மற்றும் விற்க உலகின் மிகவும் பிரபலமான வழி"
},
+ "buyCoinSwitch": {
+ "message": "நாணயம் ஸ்விட்சில் வாங்கவும்"
+ },
+ "buyCoinSwitchExplainer": {
+ "message": "சிறந்த விகிதத்தில் 300 க்கும் அதிகமான cryptocurrencies ஐ பரிமாறிக்கொள்ள ஒரு நாணயமாற்று இலக்கு நாணயம் ஸ்விட்ச் ஆகும்."
+ },
"ok": {
"message": "சரி"
},
@@ -164,6 +170,9 @@
"continueToCoinbase": {
"message": "கோஇன்பசே ஐத் தொடரவும்"
},
+ "continueToCoinSwitch": {
+ "message": "நாணயம் மாறாமல் தொடர்க"
+ },
"contractDeployment": {
"message": "ஒப்பந்த வரிசைப்படுத்தல்"
},
diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json
index b085828a2606..bcf96dece344 100644
--- a/app/_locales/tr/messages.json
+++ b/app/_locales/tr/messages.json
@@ -131,6 +131,12 @@
"buyCoinbaseExplainer": {
"message": "Coinbase Bitcoin, Ethereum, and Litecoin alıp satmanın dünyadaki en popüler yolu"
},
+ "buyCoinSwitch": {
+ "message": "CoinSwitch'de satın al"
+ },
+ "buyCoinSwitchExplainer": {
+ "message": "Cairnswich, 300'den fazla kriptona en iyi oranda eşlik eden tek durak noktasıdır."
+ },
"ok": {
"message": "Tamam"
},
@@ -164,6 +170,9 @@
"continueToCoinbase": {
"message": "Coinbase'e devam et"
},
+ "continueToCoinSwitch": {
+ "message": "CoinSwitch'e devam et"
+ },
"contractDeployment": {
"message": "Sözleşme kurulumu"
},
diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json
index f0837ba98fab..857f78597856 100644
--- a/app/_locales/vi/messages.json
+++ b/app/_locales/vi/messages.json
@@ -110,6 +110,12 @@
"buyCoinbaseExplainer": {
"message": "Coinbase là dịch vụ ví điện tử thông dụng nhất thế giới để mua bán Bitcoin, Ethereum, và Litecoin"
},
+ "buyCoinSwitch": {
+ "message": "Mua trên CoinSwitch"
+ },
+ "buyCoinSwitchExplainer": {
+ "message": "CoinSwitch là điểm đến duy nhất để trao đổi hơn 300 tiền điện tử với tốc độ tốt nhất."
+ },
"cancel": {
"message": "Hủy"
},
@@ -131,6 +137,9 @@
"continueToCoinbase": {
"message": "Tiếp tục đến Coinbase"
},
+ "continueToCoinSwitch": {
+ "message": "Tiếp tục đến CoinSwitch"
+ },
"contractDeployment": {
"message": "Triển khai hợp đồng"
},
diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json
index 9d929d9a3c9a..0d37277030df 100644
--- a/app/_locales/zh_CN/messages.json
+++ b/app/_locales/zh_CN/messages.json
@@ -134,6 +134,12 @@
"buyCoinbaseExplainer": {
"message": "Coinbase 是世界上最流行的买卖比特币,以太币和莱特币的交易所。"
},
+ "buyCoinSwitch": {
+ "message": "在CoinSwitch上购买"
+ },
+ "buyCoinSwitchExplainer": {
+ "message": "CoinSwitch是以最优惠的价格交换超过300种加密货币的一站式目的地。"
+ },
"ok": {
"message": "确认"
},
@@ -173,6 +179,9 @@
"continueToCoinbase": {
"message": "继续访问 Coinbase"
},
+ "continueToCoinSwitch": {
+ "message": "继续访问 CoinSwitch"
+ },
"contractDeployment": {
"message": "合约部署"
},
diff --git a/app/_locales/zh_TW/messages.json b/app/_locales/zh_TW/messages.json
index d9d84b1732f1..c645f2af123c 100644
--- a/app/_locales/zh_TW/messages.json
+++ b/app/_locales/zh_TW/messages.json
@@ -188,6 +188,12 @@
"bytes": {
"message": "位元組"
},
+ "buyCoinSwitch": {
+ "message": "在CoinSwitch上购买"
+ },
+ "buyCoinSwitchExplainer": {
+ "message": "CoinSwitch是以最优惠的价格交换超过300种加密货币的一站式目的地。"
+ },
"ok": {
"message": "Ok"
},
@@ -272,6 +278,9 @@
"continueToCoinbase": {
"message": "繼續前往 Coinbase"
},
+ "continueToCoinSwitch": {
+ "message": "繼續前往 CoinSwitch"
+ },
"contractDeployment": {
"message": "部署合約"
},
diff --git a/app/images/caret-left-black.svg b/app/images/caret-left-black.svg
new file mode 100644
index 000000000000..872135ece42d
--- /dev/null
+++ b/app/images/caret-left-black.svg
@@ -0,0 +1,18 @@
+
+
diff --git a/app/images/coinswitch_logo.png b/app/images/coinswitch_logo.png
new file mode 100644
index 000000000000..445ecf02eef9
Binary files /dev/null and b/app/images/coinswitch_logo.png differ
diff --git a/app/images/wyre.svg b/app/images/wyre.svg
new file mode 100644
index 000000000000..a5209329d552
--- /dev/null
+++ b/app/images/wyre.svg
@@ -0,0 +1,9 @@
+
diff --git a/app/manifest.json b/app/manifest.json
index 6dbd8ad09376..941842636a60 100644
--- a/app/manifest.json
+++ b/app/manifest.json
@@ -1,7 +1,7 @@
{
"name": "__MSG_appName__",
"short_name": "__MSG_appName__",
- "version": "6.2.0",
+ "version": "6.3.1",
"manifest_version": 2,
"author": "https://metamask.io",
"description": "__MSG_appDescription__",
diff --git a/app/scripts/background.js b/app/scripts/background.js
index 189858a1d3b3..7fea3c8c67b9 100644
--- a/app/scripts/background.js
+++ b/app/scripts/background.js
@@ -102,7 +102,6 @@ setupMetamaskMeshMetrics()
* @property {boolean} isInitialized - Whether the first vault has been created.
* @property {boolean} isUnlocked - Whether the vault is currently decrypted and accounts are available for selection.
* @property {boolean} isAccountMenuOpen - Represents whether the main account selection UI is currently displayed.
- * @property {boolean} isMascara - True if the current context is the extensionless MetaMascara project.
* @property {boolean} isPopup - Returns true if the current view is an externally-triggered notification.
* @property {string} rpcTarget - DEPRECATED - The URL of the current RPC provider.
* @property {Object} identities - An object matching lower-case hex addresses to Identity objects with "address" and "name" (nickname) keys.
@@ -448,7 +447,7 @@ function setupController (initState, initLangCode) {
function triggerUi () {
extension.tabs.query({ active: true }, tabs => {
const currentlyActiveMetamaskTab = Boolean(tabs.find(tab => openMetamaskTabsIDs[tab.id]))
- if (!popupIsOpen && !currentlyActiveMetamaskTab) {
+ if (!popupIsOpen && !currentlyActiveMetamaskTab && !notificationIsOpen) {
notificationManager.showPopup()
notificationIsOpen = true
}
diff --git a/app/scripts/controllers/address-book.js b/app/scripts/controllers/address-book.js
deleted file mode 100644
index 4697e074c9bd..000000000000
--- a/app/scripts/controllers/address-book.js
+++ /dev/null
@@ -1,98 +0,0 @@
-const ObservableStore = require('obs-store')
-const extend = require('xtend')
-
-class AddressBookController {
-
-
- /**
- * Controller in charge of managing the address book functionality from the
- * recipients field on the send screen. Manages a history of all saved
- * addresses and all currently owned addresses.
- *
- * @typedef {Object} AddressBookController
- * @param {object} opts Overrides the defaults for the initial state of this.store
- * @property {array} opts.initState initializes the the state of the AddressBookController. Can contain an
- * addressBook property to initialize the addressBook array
- * @property {object} opts.preferencesStore the {@code PreferencesController} store
- * @property {object} store The the store of the current users address book
- * @property {array} store.addressBook An array of addresses and nicknames. These are set by the user when sending
- * to a new address.
- *
- */
- constructor ({initState, preferencesStore}) {
- this.store = new ObservableStore(extend({
- addressBook: [],
- }, initState))
- this._preferencesStore = preferencesStore
- }
-
- //
- // PUBLIC METHODS
- //
-
- /**
- * Sets a new address book in store by accepting a new address and nickname.
- *
- * @param {string} address A hex address of a new account that the user is sending to.
- * @param {string} name The name the user wishes to associate with the new account
- * @returns {Promise} Promise resolves with undefined
- *
- */
- setAddressBook (address, name) {
- return this._addToAddressBook(address, name)
- .then((addressBook) => {
- this.store.updateState({
- addressBook,
- })
- return Promise.resolve()
- })
- }
-
- /**
- * Performs the logic to add the address and name into the address book. The pushed object is an object of two
- * fields. Current behavior does not set an upper limit to the number of addresses.
- *
- * @private
- * @param {string} address A hex address of a new account that the user is sending to.
- * @param {string} name The name the user wishes to associate with the new account
- * @returns {Promise} Promises the updated addressBook array
- *
- */
- _addToAddressBook (address, name) {
- const addressBook = this._getAddressBook()
- const {identities} = this._preferencesStore.getState()
-
- const addressBookIndex = addressBook.findIndex((element) => { return element.address.toLowerCase() === address.toLowerCase() || element.name === name })
- const identitiesIndex = Object.keys(identities).findIndex((element) => { return element.toLowerCase() === address.toLowerCase() })
- // trigger this condition if we own this address--no need to overwrite.
- if (identitiesIndex !== -1) {
- return Promise.resolve(addressBook)
- // trigger this condition if we've seen this address before--may need to update nickname.
- } else if (addressBookIndex !== -1) {
- addressBook.splice(addressBookIndex, 1)
- } else if (addressBook.length > 15) {
- addressBook.shift()
- }
-
-
- addressBook.push({
- address: address,
- name,
- })
- return Promise.resolve(addressBook)
- }
-
- /**
- * Internal method to get the address book. Current persistence behavior should not require that this method be
- * called from the UI directly.
- *
- * @private
- * @returns {array} The addressBook array from the store.
- *
- */
- _getAddressBook () {
- return this.store.getState().addressBook
- }
-}
-
-module.exports = AddressBookController
diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js
index 0c6327f6e4e4..47432c1e24c9 100644
--- a/app/scripts/controllers/network/network.js
+++ b/app/scripts/controllers/network/network.js
@@ -25,10 +25,18 @@ const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET]
const env = process.env.METAMASK_ENV
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
-const testMode = (METAMASK_DEBUG || env === 'test')
+
+let defaultProviderConfigType
+if (process.env.IN_TEST === 'true') {
+ defaultProviderConfigType = LOCALHOST
+} else if (METAMASK_DEBUG || env === 'test') {
+ defaultProviderConfigType = RINKEBY
+} else {
+ defaultProviderConfigType = MAINNET
+}
const defaultProviderConfig = {
- type: testMode ? RINKEBY : MAINNET,
+ type: defaultProviderConfigType,
}
const defaultNetworkConfig = {
diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js
index f923413537dd..7374118904c9 100644
--- a/app/scripts/controllers/preferences.js
+++ b/app/scripts/controllers/preferences.js
@@ -40,7 +40,9 @@ class PreferencesController {
// Feature flag toggling is available in the global namespace
// for convenient testing of pre-release features, and should never
// perform sensitive operations.
- featureFlags: {},
+ featureFlags: {
+ privacyMode: true,
+ },
knownMethodData: {},
participateInMetaMetrics: null,
firstTimeFlowType: null,
diff --git a/app/scripts/controllers/transactions/tx-gas-utils.js b/app/scripts/controllers/transactions/tx-gas-utils.js
index b296dc5eb5d5..76555116720f 100644
--- a/app/scripts/controllers/transactions/tx-gas-utils.js
+++ b/app/scripts/controllers/transactions/tx-gas-utils.js
@@ -7,7 +7,7 @@ const {
const { addHexPrefix } = require('ethereumjs-util')
const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send.
-import { TRANSACTION_NO_CONTRACT_ERROR_KEY } from '../../../../ui/app/constants/error-keys'
+import { TRANSACTION_NO_CONTRACT_ERROR_KEY } from '../../../../ui/app/helpers/constants/error-keys'
/**
tx-gas-utils are gas utility methods for Transaction manager
diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js
index c7f0c56696dd..68394d1ae743 100644
--- a/app/scripts/inpage.js
+++ b/app/scripts/inpage.js
@@ -91,6 +91,10 @@ inpageProvider.enable = function ({ force } = {}) {
})
}
+// give the dapps control of a refresh they can toggle this off on the window.ethereum
+// this will be default true so it does not break any old apps.
+inpageProvider.autoRefreshOnNetworkChange = true
+
// add metamask-specific convenience methods
inpageProvider._metamask = new Proxy({
/**
diff --git a/app/scripts/lib/ComposableObservableStore.js b/app/scripts/lib/ComposableObservableStore.js
index d5ee708a1814..abb77967291b 100644
--- a/app/scripts/lib/ComposableObservableStore.js
+++ b/app/scripts/lib/ComposableObservableStore.js
@@ -40,7 +40,9 @@ class ComposableObservableStore extends ObservableStore {
getFlatState () {
let flatState = {}
for (const key in this.config) {
- flatState = { ...flatState, ...this.config[key].getState() }
+ const controller = this.config[key]
+ const state = controller.getState ? controller.getState() : controller.state
+ flatState = { ...flatState, ...state }
}
return flatState
}
diff --git a/app/scripts/lib/auto-reload.js b/app/scripts/lib/auto-reload.js
index 558391a06448..44fbe847c6a6 100644
--- a/app/scripts/lib/auto-reload.js
+++ b/app/scripts/lib/auto-reload.js
@@ -20,6 +20,10 @@ function setupDappAutoReload (web3, observable) {
})
observable.subscribe(function (state) {
+ // if the auto refresh on network change is false do not
+ // do anything
+ if (!window.ethereum.autoRefreshOnNetworkChange) return
+
// if reload in progress, no need to check reload logic
if (reloadInProgress) return
diff --git a/app/scripts/lib/buy-eth-url.js b/app/scripts/lib/buy-eth-url.js
index 4e2d0bc79429..fbe6c6c9e23d 100644
--- a/app/scripts/lib/buy-eth-url.js
+++ b/app/scripts/lib/buy-eth-url.js
@@ -11,24 +11,37 @@ module.exports = getBuyEthUrl
* network does not match any of the specified cases, or if no network is given, returns undefined.
*
*/
-function getBuyEthUrl ({ network, amount, address }) {
- let url
+function getBuyEthUrl ({ network, amount, address, service }) {
+ // default service by network if not specified
+ if (!service) service = getDefaultServiceForNetwork(network)
+
+ switch (service) {
+ case 'wyre':
+ return `https://dash.sendwyre.com/sign-up`
+ case 'coinswitch':
+ return `https://metamask.coinswitch.co/?address=${address}&to=eth`
+ case 'coinbase':
+ return `https://buy.coinbase.com/?code=9ec56d01-7e81-5017-930c-513daa27bb6a&amount=${amount}&address=${address}&crypto_currency=ETH`
+ case 'metamask-faucet':
+ return 'https://faucet.metamask.io/'
+ case 'rinkeby-faucet':
+ return 'https://www.rinkeby.io/'
+ case 'kovan-faucet':
+ return 'https://github.com/kovan-testnet/faucet'
+ }
+ throw new Error(`Unknown cryptocurrency exchange or faucet: "${service}"`)
+}
+
+function getDefaultServiceForNetwork (network) {
switch (network) {
case '1':
- url = `https://buy.coinbase.com/?code=9ec56d01-7e81-5017-930c-513daa27bb6a&amount=${amount}&address=${address}&crypto_currency=ETH`
- break
-
+ return 'wyre'
case '3':
- url = 'https://faucet.metamask.io/'
- break
-
+ return 'metamask-faucet'
case '4':
- url = 'https://www.rinkeby.io/'
- break
-
+ return 'rinkeby-faucet'
case '42':
- url = 'https://github.com/kovan-testnet/faucet'
- break
+ return 'kovan-faucet'
}
- return url
+ throw new Error(`No default cryptocurrency exchange or faucet for networkId: "${network}"`)
}
diff --git a/app/scripts/lib/nodeify.js b/app/scripts/lib/nodeify.js
index 25be6537b473..a813ae679c36 100644
--- a/app/scripts/lib/nodeify.js
+++ b/app/scripts/lib/nodeify.js
@@ -1,5 +1,5 @@
const promiseToCallback = require('promise-to-callback')
-const noop = function () {}
+const callbackNoop = function (err) { if (err) throw err }
/**
* A generator that returns a function which, when passed a promise, can treat that promise as a node style callback.
@@ -11,6 +11,7 @@ const noop = function () {}
*/
module.exports = function nodeify (fn, context) {
return function () {
+ // parse arguments
const args = [].slice.call(arguments)
const lastArg = args[args.length - 1]
const lastArgIsCallback = typeof lastArg === 'function'
@@ -19,8 +20,16 @@ module.exports = function nodeify (fn, context) {
callback = lastArg
args.pop()
} else {
- callback = noop
+ callback = callbackNoop
}
- promiseToCallback(fn.apply(context, args))(callback)
+ // call the provided function and ensure result is a promise
+ let result
+ try {
+ result = Promise.resolve(fn.apply(context, args))
+ } catch (err) {
+ result = Promise.reject(err)
+ }
+ // wire up promise resolution to callback
+ promiseToCallback(result)(callback)
}
}
diff --git a/app/scripts/lib/notification-manager.js b/app/scripts/lib/notification-manager.js
index 969a9459a135..721d109a1a95 100644
--- a/app/scripts/lib/notification-manager.js
+++ b/app/scripts/lib/notification-manager.js
@@ -1,7 +1,6 @@
const extension = require('extensionizer')
-const height = 620
-const width = 360
-
+const NOTIFICATION_HEIGHT = 620
+const NOTIFICATION_WIDTH = 360
class NotificationManager {
@@ -26,13 +25,18 @@ class NotificationManager {
// bring focus to existing chrome popup
extension.windows.update(popup.id, { focused: true })
} else {
+ const {screenX, screenY, outerWidth, outerHeight} = window
+ const notificationTop = Math.round(screenY + (outerHeight / 2) - (NOTIFICATION_HEIGHT / 2))
+ const notificationLeft = Math.round(screenX + (outerWidth / 2) - (NOTIFICATION_WIDTH / 2))
const cb = (currentPopup) => { this._popupId = currentPopup.id }
// create new notification popup
const creation = extension.windows.create({
url: 'notification.html',
type: 'popup',
- width,
- height,
+ width: NOTIFICATION_WIDTH,
+ height: NOTIFICATION_HEIGHT,
+ top: Math.max(notificationTop, 0),
+ left: Math.max(notificationLeft, 0),
}, cb)
creation && creation.then && creation.then(cb)
}
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index 653868066374..058d527c09e7 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -26,7 +26,6 @@ const PreferencesController = require('./controllers/preferences')
const CurrencyController = require('./controllers/currency')
const NoticeController = require('./notice-controller')
const ShapeShiftController = require('./controllers/shapeshift')
-const AddressBookController = require('./controllers/address-book')
const InfuraController = require('./controllers/infura')
const BlacklistController = require('./controllers/blacklist')
const CachedBalancesController = require('./controllers/cached-balances')
@@ -55,6 +54,7 @@ const HW_WALLETS_KEYRINGS = [TrezorKeyring.type, LedgerBridgeKeyring.type]
const EthQuery = require('eth-query')
const ethUtil = require('ethereumjs-util')
const sigUtil = require('eth-sig-util')
+const { AddressBookController } = require('gaba')
module.exports = class MetamaskController extends EventEmitter {
@@ -175,11 +175,7 @@ module.exports = class MetamaskController extends EventEmitter {
keyringMemStore: this.keyringController.memStore,
})
- // address book controller
- this.addressBookController = new AddressBookController({
- initState: initState.AddressBookController,
- preferencesStore: this.preferencesController.store,
- })
+ this.addressBookController = new AddressBookController(undefined, initState.AddressBookController)
// tx mgmt
this.txController = new TransactionController({
@@ -245,7 +241,7 @@ module.exports = class MetamaskController extends EventEmitter {
TransactionController: this.txController.store,
KeyringController: this.keyringController.store,
PreferencesController: this.preferencesController.store,
- AddressBookController: this.addressBookController.store,
+ AddressBookController: this.addressBookController,
CurrencyController: this.currencyController.store,
NoticeController: this.noticeController.store,
ShapeShiftController: this.shapeshiftController.store,
@@ -267,7 +263,7 @@ module.exports = class MetamaskController extends EventEmitter {
KeyringController: this.keyringController.memStore,
PreferencesController: this.preferencesController.store,
RecentBlocksController: this.recentBlocksController.store,
- AddressBookController: this.addressBookController.store,
+ AddressBookController: this.addressBookController,
CurrencyController: this.currencyController.store,
NoticeController: this.noticeController.memStore,
ShapeshiftController: this.shapeshiftController.store,
@@ -376,7 +372,6 @@ module.exports = class MetamaskController extends EventEmitter {
const preferencesController = this.preferencesController
const txController = this.txController
const noticeController = this.noticeController
- const addressBookController = this.addressBookController
const networkController = this.networkController
const providerApprovalController = this.providerApprovalController
@@ -443,7 +438,7 @@ module.exports = class MetamaskController extends EventEmitter {
whitelistPhishingDomain: this.whitelistPhishingDomain.bind(this),
// AddressController
- setAddressBook: nodeify(addressBookController.setAddressBook, addressBookController),
+ setAddressBook: this.addressBookController.set.bind(this.addressBookController),
// KeyringController
setLocked: nodeify(this.setLocked, this),
@@ -478,6 +473,7 @@ module.exports = class MetamaskController extends EventEmitter {
// notices
checkNotices: noticeController.updateNoticesList.bind(noticeController),
markNoticeRead: noticeController.markNoticeRead.bind(noticeController),
+ markAllNoticesRead: nodeify(noticeController.markAllNoticesRead, noticeController),
approveProviderRequest: providerApprovalController.approveProviderRequest.bind(providerApprovalController),
clearApprovedOrigins: providerApprovalController.clearApprovedOrigins.bind(providerApprovalController),
diff --git a/app/scripts/notice-controller.js b/app/scripts/notice-controller.js
index 6fe8b8cf020f..63b422c5b99a 100644
--- a/app/scripts/notice-controller.js
+++ b/app/scripts/notice-controller.js
@@ -58,6 +58,18 @@ module.exports = class NoticeController extends EventEmitter {
}
}
+ markAllNoticesRead () {
+ const noticeList = this.getNoticesList()
+ noticeList.forEach(notice => {
+ notice.read = true
+ notice.body = ''
+ })
+ this.setNoticesList(noticeList)
+ const latestNotice = this.getNextUnreadNotice()
+ return latestNotice
+ }
+
+
async updateNoticesList () {
const newNotices = await this._retrieveNoticeData()
const oldNotices = this.getNoticesList()
diff --git a/auto-changelog.sh b/development/auto-changelog.sh
similarity index 100%
rename from auto-changelog.sh
rename to development/auto-changelog.sh
diff --git a/development/metamaskbot-build-announce.js b/development/metamaskbot-build-announce.js
index 96b5572fed42..07adbcb5d833 100755
--- a/development/metamaskbot-build-announce.js
+++ b/development/metamaskbot-build-announce.js
@@ -23,7 +23,6 @@ async function start () {
const SHORT_SHA1 = CIRCLE_SHA1.slice(0, 7)
const BUILD_LINK_BASE = `https://${CIRCLE_BUILD_NUM}-42009758-gh.circle-artifacts.com/0`
- const MASCARA = `${BUILD_LINK_BASE}/builds/mascara/home.html`
const CHROME = `${BUILD_LINK_BASE}/builds/metamask-chrome-${VERSION}.zip`
const FIREFOX = `${BUILD_LINK_BASE}/builds/metamask-firefox-${VERSION}.zip`
const EDGE = `${BUILD_LINK_BASE}/builds/metamask-edge-${VERSION}.zip`
@@ -34,7 +33,6 @@ async function start () {
Builds ready [${SHORT_SHA1}]:
- mascara,
chrome,
firefox,
edge,
diff --git a/development/mock-dev.js b/development/mock-dev.js
index 1af10a13178d..4a3217a06b38 100644
--- a/development/mock-dev.js
+++ b/development/mock-dev.js
@@ -14,9 +14,9 @@
const render = require('react-dom').render
const h = require('react-hyperscript')
-const Root = require('../ui/app/root')
-const configureStore = require('../ui/app/store')
-const actions = require('../ui/app/actions')
+const Root = require('../ui/app/pages')
+const configureStore = require('../ui/app/store/store')
+const actions = require('../ui/app/store/actions')
const states = require('./states')
const backGroundConnectionModifiers = require('./backGroundConnectionModifiers')
const Selector = require('./selector')
diff --git a/development/rollback.sh b/development/rollback.sh
new file mode 100755
index 000000000000..0a1d8ad62022
--- /dev/null
+++ b/development/rollback.sh
@@ -0,0 +1,35 @@
+#! /bin/bash
+
+[[ -z "$1" ]] && { echo "Rollback version is required!" ; exit 1; }
+echo "Rolling back to version $1"
+
+# Checkout branch to increment version
+git checkout -b version-increment-$1
+npm run version:bump patch
+
+# Store the new version name
+NEW_VERSION=$(cat app/manifest.json | jq -r .version)
+
+# Make sure origin tags are loaded
+git fetch origin
+
+# check out the rollback branch
+git checkout origin/v$1
+
+# Create the rollback branch.
+git checkout -b Version-$NEW_VERSION-Rollback-to-$1
+
+# Set the version files to the next one.
+git checkout master CHANGELOG.md
+git checkout master app/manifest.json
+git commit -m "Version $NEW_VERSION (Rollback to $1)"
+
+# Push the new branch to PR
+git push -u origin HEAD
+
+# Create tag and push that up too
+git tag v${NEW_VERSION}
+git push origin v${NEW_VERSION}
+
+# Cleanup version branch
+git branch -D version-increment-$1
diff --git a/development/sourcemap-validator.js b/development/sourcemap-validator.js
index 14388812870b..546745f1665d 100644
--- a/development/sourcemap-validator.js
+++ b/development/sourcemap-validator.js
@@ -1,6 +1,9 @@
const fs = require('fs')
const { SourceMapConsumer } = require('source-map')
const path = require('path')
+const pify = require('pify')
+const fsAsync = pify(fs)
+
//
// Utility to help check if sourcemaps are working
//
@@ -9,39 +12,85 @@ const path = require('path')
// if not working it may error or print minified garbage
//
-start()
+start().catch(console.error)
async function start () {
- const rawBuild = fs.readFileSync(path.join(__dirname, '/../dist/chrome/', 'inpage.js')
- , 'utf8')
- const rawSourceMap = fs.readFileSync(path.join(__dirname, '/../dist/sourcemaps/', 'inpage.js.map'), 'utf8')
+ const targetFiles = [`inpage.js`, `contentscript.js`, `ui.js`, `background.js`]
+ for (const buildName of targetFiles) {
+ await validateSourcemapForFile({ buildName })
+ }
+}
+
+async function validateSourcemapForFile ({ buildName }) {
+ console.log(`build "${buildName}"`)
+ const platform = `chrome`
+ // load build and sourcemaps
+ let rawBuild
+ try {
+ const filePath = path.join(__dirname, `/../dist/${platform}/`, `${buildName}`)
+ rawBuild = await fsAsync.readFile(filePath, 'utf8')
+ } catch (err) {}
+ if (!rawBuild) {
+ throw new Error(`SourcemapValidator - failed to load source file for "${buildName}"`)
+ }
+ // attempt to load in dist mode
+ let rawSourceMap
+ try {
+ const filePath = path.join(__dirname, `/../dist/sourcemaps/`, `${buildName}.map`)
+ rawSourceMap = await fsAsync.readFile(filePath, 'utf8')
+ } catch (err) {}
+ // attempt to load in dev mode
+ try {
+ const filePath = path.join(__dirname, `/../dist/${platform}/`, `${buildName}.map`)
+ rawSourceMap = await fsAsync.readFile(filePath, 'utf8')
+ } catch (err) {}
+ if (!rawSourceMap) {
+ throw new Error(`SourcemapValidator - failed to load sourcemaps for "${buildName}"`)
+ }
+
const consumer = await new SourceMapConsumer(rawSourceMap)
- console.log('hasContentsOfAllSources:', consumer.hasContentsOfAllSources(), '\n')
- console.log('sources:')
- consumer.sources.map((sourcePath) => console.log(sourcePath))
-
- console.log('\nexamining "new Error" statements:\n')
- const sourceLines = rawBuild.split('\n')
- sourceLines.map(line => indicesOf('new Error', line))
- .forEach((errorIndices, lineIndex) => {
- // if (errorIndex === null) return console.log('line does not contain "new Error"')
- errorIndices.forEach((errorIndex) => {
- const position = { line: lineIndex + 1, column: errorIndex }
+ const hasContentsOfAllSources = consumer.hasContentsOfAllSources()
+ if (!hasContentsOfAllSources) console.warn('SourcemapValidator - missing content of some sources...')
+
+ console.log(` sampling from ${consumer.sources.length} files`)
+ let sampleCount = 0
+
+ const buildLines = rawBuild.split('\n')
+ const targetString = 'new Error'
+ // const targetString = 'null'
+ const matchesPerLine = buildLines.map(line => indicesOf(targetString, line))
+ matchesPerLine.forEach((matchIndices, lineIndex) => {
+ matchIndices.forEach((matchColumn) => {
+ sampleCount++
+ const position = { line: lineIndex + 1, column: matchColumn }
const result = consumer.originalPositionFor(position)
- if (!result.source) return console.warn(`!! missing source for position: ${position}`)
- // filter out deps distributed minified without sourcemaps
- if (result.source === 'node_modules/browserify/node_modules/browser-pack/_prelude.js') return // minified mess
- if (result.source === 'node_modules/web3/dist/web3.min.js') return // minified mess
+ // warn if source content is missing
+ if (!result.source) {
+ console.warn(`!! missing source for position: ${JSON.stringify(position)}`)
+ // const buildLine = buildLines[position.line - 1]
+ console.warn(` origin in build:`)
+ console.warn(` ${buildLines[position.line - 2]}`)
+ console.warn(`-> ${buildLines[position.line - 1]}`)
+ console.warn(` ${buildLines[position.line - 0]}`)
+ return
+ }
const sourceContent = consumer.sourceContentFor(result.source)
const sourceLines = sourceContent.split('\n')
const line = sourceLines[result.line - 1]
- console.log(`\n========================== ${result.source} ====================================\n`)
- console.log(line)
- console.log(`\n==============================================================================\n`)
+ // this sometimes includes the whole line though we tried to match somewhere in the middle
+ const portion = line.slice(result.column)
+ const isMaybeValid = portion.includes(targetString)
+ if (!isMaybeValid) {
+ console.error('Sourcemap seems invalid:')
+ console.log(`\n========================== ${result.source} ====================================\n`)
+ console.log(line)
+ console.log(`\n==============================================================================\n`)
+ }
})
})
+ console.log(` checked ${sampleCount} samples`)
}
function indicesOf (substring, string) {
diff --git a/development/states/navigate-txs.json b/development/states/navigate-txs.json
index 813b02172379..23255abed60b 100644
--- a/development/states/navigate-txs.json
+++ b/development/states/navigate-txs.json
@@ -3,7 +3,6 @@
"isInitialized": true,
"isUnlocked": true,
"isAccountMenuOpen": false,
- "isMascara": false,
"isPopup": false,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
diff --git a/development/states/pending-tx.json b/development/states/pending-tx.json
index bfa93f7ae3c9..b4a6565fa414 100644
--- a/development/states/pending-tx.json
+++ b/development/states/pending-tx.json
@@ -2,7 +2,6 @@
"metamask": {
"isInitialized": true,
"isUnlocked": true,
- "isMascara": false,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": {
diff --git a/development/ui-dev.js b/development/ui-dev.js
index bae0ce50eb53..70f513972c38 100644
--- a/development/ui-dev.js
+++ b/development/ui-dev.js
@@ -17,7 +17,7 @@
const render = require('react-dom').render
const h = require('react-hyperscript')
-const Root = require('../ui/app/root')
+const Root = require('../ui/app/pages')
const configureStore = require('./uiStore')
const states = require('./states')
const Selector = require('./selector')
diff --git a/development/uiStore.js b/development/uiStore.js
index c71d66d3b670..bfec8f5e472e 100644
--- a/development/uiStore.js
+++ b/development/uiStore.js
@@ -2,7 +2,7 @@ const createStore = require('redux').createStore
const applyMiddleware = require('redux').applyMiddleware
const thunkMiddleware = require('redux-thunk').default
const createLogger = require('redux-logger').createLogger
-const rootReducer = require('../ui/app/reducers')
+const rootReducer = require('../ui/app/ducks')
module.exports = configureStore
diff --git a/docker-compose.yml b/docker-compose.yml
deleted file mode 100644
index 9a57617dd84a..000000000000
--- a/docker-compose.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-metamascara:
- build: ./
- restart: always
- ports:
- - "9001"
- environment:
- MASCARA_ORIGIN: "https://wallet.metamask.io"
- VIRTUAL_PORT: "9001"
- VIRTUAL_HOST: "wallet.metamask.io"
- LETSENCRYPT_HOST: "wallet.metamask.io"
- LETSENCRYPT_EMAIL: "admin@metamask.io"
\ No newline at end of file
diff --git a/docs/adding-new-networks.md b/docs/adding-new-networks.md
index b74233fa6863..40925f135054 100644
--- a/docs/adding-new-networks.md
+++ b/docs/adding-new-networks.md
@@ -5,7 +5,7 @@ To add another network to our dropdown menu, make sure the following files are a
```
app/scripts/config.js
app/scripts/lib/buy-eth-url.js
-ui/app/app.js
+ui/app/index.js
ui/app/components/buy-button-subview.js
ui/app/components/drop-menu-item.js
ui/app/components/network.js
diff --git a/docs/secret-preferences.md b/docs/secret-preferences.md
index f9d01a5038c2..58a4554c41ed 100644
--- a/docs/secret-preferences.md
+++ b/docs/secret-preferences.md
@@ -6,5 +6,5 @@ One example is our "sync with mobile" feature, which didn't make sense to roll o
To enable features like this, first open the background console, and then you can use the global method `global.setPreference(key, value)`.
-For example, if the feature flag was a booelan was called `mobileSync`, you might type `setPreference('mobileSync', true)`.
+For example, if the feature flag was a boolean was called `useNativeCurrencyAsPrimaryCurrency`, you might type `setPreference('useNativeCurrencyAsPrimaryCurrency', true)`.
diff --git a/docs/sensitive-release.md b/docs/sensitive-release.md
index e2c4e2f3d611..288df57eb9a3 100644
--- a/docs/sensitive-release.md
+++ b/docs/sensitive-release.md
@@ -18,10 +18,10 @@ Simply follow the steps in [the publishing guide](./publishing.md).
### Prepare Rollback Release
-1. Check out the tagged commit for the release before this new release.
-2. Increment the version over the new release by one patch.
-3. Merge the changelog of the new release into this branch, and make its own changelog refer to rolling back those changes.
-4. Push to the main repository and pull request against `master` prominently noting this is a `DO NOT MERGE` rollback pull request.
+1. Be on the new risky version branch.
+2. Run `npm run rollback $ROLLBACK_TARGET_VERSION`.
+
+The rollback version will now be a branch and tag on the origin.
### Roll the normal release out
diff --git a/gulpfile.js b/gulpfile.js
index c672a9eff581..caddb620ae54 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -51,8 +51,6 @@ const browserPlatforms = [
'opera',
]
const commonPlatforms = [
- // browser webapp
- 'mascara',
// browser extensions
...browserPlatforms,
]
@@ -110,14 +108,6 @@ createCopyTasks('manifest', {
destinations: browserPlatforms.map(platform => `./dist/${platform}`),
})
-// copy mascara
-
-createCopyTasks('html:mascara', {
- source: './mascara/',
- pattern: 'proxy/index.html',
- destinations: [`./dist/mascara/`],
-})
-
function createCopyTasks (label, opts) {
if (!opts.devOnly) {
const copyTaskName = `copy:${label}`
@@ -205,6 +195,21 @@ gulp.task('manifest:production', function () {
.pipe(gulp.dest('./dist/', { overwrite: true }))
})
+gulp.task('manifest:testing', function () {
+ return gulp.src([
+ './dist/firefox/manifest.json',
+ './dist/chrome/manifest.json',
+ ], {base: './dist/'})
+
+ // Exclude chromereload script in production:
+ .pipe(jsoneditor(function (json) {
+ json.permissions = [...json.permissions, 'webRequestBlocking']
+ return json
+ }))
+
+ .pipe(gulp.dest('./dist/', { overwrite: true }))
+})
+
gulp.task('copy',
gulp.series(
gulp.parallel(...copyTaskNames),
@@ -222,6 +227,15 @@ gulp.task('dev:copy',
)
)
+gulp.task('test:copy',
+ gulp.series(
+ gulp.parallel(...copyDevTaskNames),
+ 'manifest:chrome',
+ 'manifest:opera',
+ 'manifest:testing'
+ )
+)
+
// scss compilation and autoprefixing tasks
gulp.task('build:scss', createScssBuildTask({
@@ -297,9 +311,9 @@ const buildJsFiles = [
// bundle tasks
createTasksForBuildJsUIDeps({ dependenciesToBundle: uiDependenciesToBundle, filename: 'libs' })
createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'dev:extension:js', devMode: true })
+createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'dev:test-extension:js', devMode: true, testing: 'true' })
createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'build:extension:js' })
-createTasksForBuildJsMascara({ taskPrefix: 'build:mascara:js' })
-createTasksForBuildJsMascara({ taskPrefix: 'dev:mascara:js', devMode: true })
+createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'build:test:extension:js', testing: 'true' })
function createTasksForBuildJsUIDeps ({ dependenciesToBundle, filename }) {
const destinations = browserPlatforms.map(platform => `./dist/${platform}`)
@@ -322,7 +336,7 @@ function createTasksForBuildJsUIDeps ({ dependenciesToBundle, filename }) {
}
-function createTasksForBuildJsExtension ({ buildJsFiles, taskPrefix, devMode, bundleTaskOpts = {} }) {
+function createTasksForBuildJsExtension ({ buildJsFiles, taskPrefix, devMode, testing, bundleTaskOpts = {} }) {
// inpage must be built before all other scripts:
const rootDir = './app/scripts'
const nonInpageFiles = buildJsFiles.filter(file => file !== 'inpage')
@@ -336,26 +350,11 @@ function createTasksForBuildJsExtension ({ buildJsFiles, taskPrefix, devMode, bu
buildWithFullPaths: devMode,
watch: devMode,
devMode,
+ testing,
}, bundleTaskOpts)
createTasksForBuildJs({ rootDir, taskPrefix, bundleTaskOpts, destinations, buildPhase1, buildPhase2 })
}
-function createTasksForBuildJsMascara ({ taskPrefix, devMode, bundleTaskOpts = {} }) {
- // inpage must be built before all other scripts:
- const rootDir = './mascara/src/'
- const buildPhase1 = ['ui', 'proxy', 'background', 'metamascara']
- const destinations = ['./dist/mascara']
- bundleTaskOpts = Object.assign({
- buildSourceMaps: true,
- sourceMapDir: './',
- minifyBuild: !devMode,
- buildWithFullPaths: devMode,
- watch: devMode,
- devMode,
- }, bundleTaskOpts)
- createTasksForBuildJs({ rootDir, taskPrefix, bundleTaskOpts, destinations, buildPhase1 })
-}
-
function createTasksForBuildJs ({ rootDir, taskPrefix, bundleTaskOpts, destinations, buildPhase1 = [], buildPhase2 = [] }) {
// bundle task for each file
const jsFiles = [].concat(buildPhase1, buildPhase2)
@@ -405,31 +404,30 @@ gulp.task('dev',
'dev:scss',
gulp.parallel(
'dev:extension:js',
- 'dev:mascara:js',
'dev:copy',
'dev:reload'
)
)
)
-gulp.task('dev:extension',
+gulp.task('dev:test',
gulp.series(
'clean',
'dev:scss',
gulp.parallel(
- 'dev:extension:js',
- 'dev:copy',
+ 'dev:test-extension:js',
+ 'test:copy',
'dev:reload'
)
)
)
-gulp.task('dev:mascara',
+gulp.task('dev:extension',
gulp.series(
'clean',
'dev:scss',
gulp.parallel(
- 'dev:mascara:js',
+ 'dev:extension:js',
'dev:copy',
'dev:reload'
)
@@ -443,29 +441,30 @@ gulp.task('build',
gulpParallel(
'build:extension:js:uideps',
'build:extension:js',
- 'build:mascara:js',
'copy'
)
)
)
-gulp.task('build:extension',
+gulp.task('build:test',
gulp.series(
'clean',
'build:scss',
- gulp.parallel(
- 'build:extension:js',
+ gulpParallel(
+ 'build:extension:js:uideps',
+ 'build:test:extension:js',
'copy'
- )
+ ),
+ 'manifest:testing'
)
)
-gulp.task('build:mascara',
+gulp.task('build:extension',
gulp.series(
'clean',
'build:scss',
gulp.parallel(
- 'build:mascara:js',
+ 'build:extension:js',
'copy'
)
)
@@ -513,6 +512,7 @@ function generateBundler (opts, performBundle) {
bundler.transform(envify({
METAMASK_DEBUG: opts.devMode,
NODE_ENV: opts.devMode ? 'development' : 'production',
+ IN_TEST: opts.testing,
PUBNUB_SUB_KEY: process.env.PUBNUB_SUB_KEY || '',
PUBNUB_PUB_KEY: process.env.PUBNUB_PUB_KEY || '',
}), {
diff --git a/mascara/README.md b/mascara/README.md
deleted file mode 100644
index 6e3bfe96beed..000000000000
--- a/mascara/README.md
+++ /dev/null
@@ -1,33 +0,0 @@
-start the dual servers (dapp + mascara)
-```
-npm run mascara
-```
-
-### First time use:
-
-- navigate to: http://localhost:9001
-- Create an Account
-- go back to http://localhost:9002
-- open devTools
-- click Sync Tx
-
-### Tests:
-
-```
-npm run testMascara
-```
-
-Test will run in browser, you will have to have these browsers installed:
-
-- Chrome
-- Firefox
-- Opera
-
-
-### Deploy:
-
-Will build and deploy mascara via docker
-
-```
-docker-compose build && docker-compose stop && docker-compose up -d && docker-compose logs --tail 200 -f
-```
\ No newline at end of file
diff --git a/mascara/example/app.js b/mascara/example/app.js
deleted file mode 100644
index 7b6421fdca1a..000000000000
--- a/mascara/example/app.js
+++ /dev/null
@@ -1,38 +0,0 @@
-const EthQuery = require('ethjs-query')
-
-window.addEventListener('load', loadProvider)
-window.addEventListener('message', console.warn)
-
-async function loadProvider () {
- const ethereumProvider = window.metamask.createDefaultProvider({ host: 'http://localhost:9001' })
- const ethQuery = new EthQuery(ethereumProvider)
- const accounts = await ethQuery.accounts()
- window.METAMASK_ACCOUNT = accounts[0] || 'locked'
- logToDom(accounts.length ? accounts[0] : 'LOCKED or undefined', 'account')
- setupButtons(ethQuery)
-}
-
-
-function logToDom (message, context) {
- document.getElementById(context).innerText = message
- console.log(message)
-}
-
-function setupButtons (ethQuery) {
- const accountButton = document.getElementById('action-button-1')
- accountButton.addEventListener('click', async () => {
- const accounts = await ethQuery.accounts()
- window.METAMASK_ACCOUNT = accounts[0] || 'locked'
- logToDom(accounts.length ? accounts[0] : 'LOCKED or undefined', 'account')
- })
- const txButton = document.getElementById('action-button-2')
- txButton.addEventListener('click', async () => {
- if (!window.METAMASK_ACCOUNT || window.METAMASK_ACCOUNT === 'locked') return
- const txHash = await ethQuery.sendTransaction({
- from: window.METAMASK_ACCOUNT,
- to: window.METAMASK_ACCOUNT,
- data: '',
- })
- logToDom(txHash, 'cb-value')
- })
-}
diff --git a/mascara/example/app/index.html b/mascara/example/app/index.html
deleted file mode 100644
index 8afb6f3f2ae3..000000000000
--- a/mascara/example/app/index.html
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
- MetaMask ZeroClient Example
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/mascara/example/server.js b/mascara/example/server.js
deleted file mode 100644
index bdb1efa16b80..000000000000
--- a/mascara/example/server.js
+++ /dev/null
@@ -1,31 +0,0 @@
-const express = require('express')
-const path = require('path')
-const createMetamascaraServer = require('../server/')
-const createBundle = require('../server/util').createBundle
-const serveBundle = require('../server/util').serveBundle
-//
-// Iframe Server
-//
-
-const mascaraServer = createMetamascaraServer()
-
-// start the server
-const mascaraPort = 9001
-mascaraServer.listen(mascaraPort)
-console.log(`Mascara service listening on port ${mascaraPort}`)
-
-
-//
-// Dapp Server
-//
-
-const dappServer = express()
-
-// serve dapp bundle
-serveBundle(dappServer, '/app.js', createBundle(require.resolve('./app.js')))
-dappServer.use(express.static(path.join(__dirname, '/app/')))
-
-// start the server
-const dappPort = '9002'
-dappServer.listen(dappPort)
-console.log(`Dapp listening on port ${dappPort}`)
diff --git a/mascara/proxy/index.html b/mascara/proxy/index.html
deleted file mode 100644
index 4e167db72753..000000000000
--- a/mascara/proxy/index.html
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
- MetaMask ZeroClient Iframe
-
-
-
-
-
-
-
- Hello! I am the MetaMask iframe.
-
-
-
diff --git a/mascara/server/index.js b/mascara/server/index.js
deleted file mode 100644
index a30120438212..000000000000
--- a/mascara/server/index.js
+++ /dev/null
@@ -1,21 +0,0 @@
-const path = require('path')
-const express = require('express')
-const compression = require('compression')
-
-module.exports = createMetamascaraServer
-
-
-function createMetamascaraServer () {
-
- // setup server
- const server = express()
- server.use(compression())
-
- // serve assets
- server.use(express.static(path.join(__dirname, '/../ui/'), { setHeaders: (res) => res.set('X-Frame-Options', 'DENY') }))
- server.use(express.static(path.join(__dirname, '/../../dist/mascara')))
- server.use(express.static(path.join(__dirname, '/../proxy')))
-
- return server
-
-}
diff --git a/mascara/server/util.js b/mascara/server/util.js
deleted file mode 100644
index f9692afb6065..000000000000
--- a/mascara/server/util.js
+++ /dev/null
@@ -1,47 +0,0 @@
-const browserify = require('browserify')
-const watchify = require('watchify')
-
-module.exports = {
- serveBundle,
- createBundle,
-}
-
-
-function serveBundle (server, path, bundle) {
- server.get(path, function (req, res) {
- res.setHeader('Content-Type', 'application/javascript; charset=UTF-8')
- res.send(bundle.latest)
- })
-}
-
-function createBundle (entryPoint) {
-
- var bundleContainer = {}
-
- var bundler = browserify({
- entries: [entryPoint],
- cache: {},
- packageCache: {},
- plugin: [watchify],
- })
- .transform('babelify')
- .transform('uglifyify', { global: true })
-
- bundler.on('update', bundle)
- bundle()
-
- return bundleContainer
-
- function bundle () {
- bundler.bundle(function (err, result) {
- if (err) {
- console.log(`Bundle failed! (${entryPoint})`)
- console.error(err)
- return
- }
- console.log(`Bundle updated! (${entryPoint})`)
- bundleContainer.latest = result.toString()
- })
- }
-
-}
diff --git a/mascara/src/app/buy-ether-widget/index.js b/mascara/src/app/buy-ether-widget/index.js
index c8530ba4c71b..d0d6ff343699 100644
--- a/mascara/src/app/buy-ether-widget/index.js
+++ b/mascara/src/app/buy-ether-widget/index.js
@@ -5,7 +5,7 @@ import {connect} from 'react-redux'
import {qrcode} from 'qrcode-generator'
import copyToClipboard from 'copy-to-clipboard'
import ShapeShiftForm from '../shapeshift-form'
-import {buyEth, showAccountDetail} from '../../../../ui/app/actions'
+import {buyEth, showAccountDetail} from '../../../../ui/app/store/actions'
const OPTION_VALUES = {
COINBASE: 'coinbase',
diff --git a/mascara/src/app/shapeshift-form/index.js b/mascara/src/app/shapeshift-form/index.js
index fe7f7ffcb0c3..c044f9ecc6e7 100644
--- a/mascara/src/app/shapeshift-form/index.js
+++ b/mascara/src/app/shapeshift-form/index.js
@@ -3,8 +3,8 @@ import PropTypes from 'prop-types'
import classnames from 'classnames'
import qrcode from 'qrcode-generator'
import {connect} from 'react-redux'
-import {shapeShiftSubview, pairUpdate, buyWithShapeShift} from '../../../../ui/app/actions'
-import {isValidAddress} from '../../../../ui/app/util'
+import {shapeShiftSubview, pairUpdate, buyWithShapeShift} from '../../../../ui/app/store/actions'
+import {isValidAddress} from '../../../../ui/app/helpers/utils/util'
export class ShapeShiftForm extends Component {
static propTypes = {
diff --git a/mascara/src/background.js b/mascara/src/background.js
deleted file mode 100644
index d8d1c8c54770..000000000000
--- a/mascara/src/background.js
+++ /dev/null
@@ -1,134 +0,0 @@
-global.window = global
-
-const SwGlobalListener = require('sw-stream/lib/sw-global-listener.js')
-const connectionListener = new SwGlobalListener(global)
-const setupMultiplex = require('../../app/scripts/lib/stream-utils.js').setupMultiplex
-
-const DbController = require('idb-global')
-
-const SwPlatform = require('../../app/scripts/platforms/sw')
-const MetamaskController = require('../../app/scripts/metamask-controller')
-
-const Migrator = require('../../app/scripts/lib/migrator/')
-const migrations = require('../../app/scripts/migrations/')
-const firstTimeState = require('../../app/scripts/first-time-state')
-
-const STORAGE_KEY = 'metamask-config'
-const METAMASK_DEBUG = process.env.METAMASK_DEBUG
-global.metamaskPopupIsOpen = false
-
-const log = require('loglevel')
-global.log = log
-log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn')
-
-global.addEventListener('install', function (event) {
- event.waitUntil(global.skipWaiting())
-})
-global.addEventListener('activate', function (event) {
- event.waitUntil(global.clients.claim())
-})
-
-log.debug('inside:open')
-
-// state persistence
-const dbController = new DbController({
- key: STORAGE_KEY,
-})
-
-start().catch(log.error)
-
-async function start () {
- log.debug('MetaMask initializing...')
- const initState = await loadStateFromPersistence()
- await setupController(initState)
- log.debug('MetaMask initialization complete.')
-}
-
-//
-// State and Persistence
-//
-async function loadStateFromPersistence () {
- // migrations
- const migrator = new Migrator({ migrations })
- const initialState = migrator.generateInitialState(firstTimeState)
- dbController.initialState = initialState
- const versionedData = await dbController.open()
- const migratedData = await migrator.migrateData(versionedData)
- await dbController.put(migratedData)
- return migratedData.data
-}
-
-async function setupController (initState, client) {
-
- //
- // MetaMask Controller
- //
-
- const platform = new SwPlatform()
-
- const controller = new MetamaskController({
- // platform specific implementation
- platform,
- // User confirmation callbacks:
- showUnconfirmedMessage: noop,
- unlockAccountMessage: noop,
- showUnapprovedTx: noop,
- // initial state
- initState,
- })
- global.metamaskController = controller
-
- controller.store.subscribe(async (state) => {
- try {
- const versionedData = await versionifyData(state)
- await dbController.put(versionedData)
- } catch (e) { console.error('METAMASK Error:', e) }
- })
-
- async function versionifyData (state) {
- const rawData = await dbController.get()
- return {
- data: state,
- meta: rawData.meta,
- }
- }
-
- //
- // connect to other contexts
- //
-
- connectionListener.on('remote', (portStream, messageEvent) => {
- log.debug('REMOTE CONECTION FOUND***********')
- connectRemote(portStream, messageEvent.data.context)
- })
-
- function connectRemote (connectionStream, context) {
- var isMetaMaskInternalProcess = (context === 'popup')
- if (isMetaMaskInternalProcess) {
- // communication with popup
- controller.setupTrustedCommunication(connectionStream, 'MetaMask')
- global.metamaskPopupIsOpen = true
- } else {
- // communication with page
- setupUntrustedCommunication(connectionStream, context)
- }
- }
-
- function setupUntrustedCommunication (connectionStream, originDomain) {
- // setup multiplexing
- var mx = setupMultiplex(connectionStream)
- // connect features
- controller.setupProviderConnection(mx.createStream('provider'), originDomain)
- controller.setupPublicConfig(mx.createStream('publicConfig'))
- }
-}
-// // this will be useful later but commented out for linting for now (liiiinting)
-// function sendMessageToAllClients (message) {
-// global.clients.matchAll().then(function (clients) {
-// clients.forEach(function (client) {
-// client.postMessage(message)
-// })
-// })
-// }
-
-function noop () {}
diff --git a/mascara/src/metamascara.js b/mascara/src/metamascara.js
deleted file mode 100644
index 0af6f532fb27..000000000000
--- a/mascara/src/metamascara.js
+++ /dev/null
@@ -1 +0,0 @@
-global.metamask = require('metamascara')
diff --git a/mascara/src/proxy.js b/mascara/src/proxy.js
deleted file mode 100644
index 80b4dc516c1b..000000000000
--- a/mascara/src/proxy.js
+++ /dev/null
@@ -1,25 +0,0 @@
-const createParentStream = require('iframe-stream').ParentStream
-const SwController = require('sw-controller')
-const SwStream = require('sw-stream/lib/sw-stream.js')
-
-const keepAliveDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000
-const background = new SwController({
- fileName: './scripts/background.js',
- keepAlive: true,
- keepAliveInterval: 30000,
- keepAliveDelay,
-})
-
-const pageStream = createParentStream()
-background.on('ready', () => {
- const swStream = SwStream({
- serviceWorker: background.controller,
- context: 'dapp',
- })
- pageStream.pipe(swStream).pipe(pageStream)
-
-})
-background.on('updatefound', () => window.location.reload())
-
-background.on('error', console.error)
-background.startWorker()
diff --git a/mascara/src/ui.js b/mascara/src/ui.js
deleted file mode 100644
index f9e7670eafd1..000000000000
--- a/mascara/src/ui.js
+++ /dev/null
@@ -1,73 +0,0 @@
-const injectCss = require('inject-css')
-const SwController = require('sw-controller')
-const SwStream = require('sw-stream')
-const MetaMaskUiCss = require('../../ui/css')
-const MetamascaraPlatform = require('../../app/scripts/platforms/window')
-const startPopup = require('../../app/scripts/popup-core')
-
-// create platform global
-global.platform = new MetamascaraPlatform()
-
-var css = MetaMaskUiCss()
-injectCss(css)
-const container = document.getElementById('app-content')
-
-const name = 'popup'
-window.METAMASK_UI_TYPE = name
-window.METAMASK_PLATFORM_TYPE = 'mascara'
-
-const keepAliveDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000
-
-const swController = new SwController({
- fileName: './background.js',
- keepAlive: true,
- keepAliveDelay,
- keepAliveInterval: 20000,
-})
-
-swController.once('updatefound', windowReload)
-swController.once('ready', async () => {
- try {
- swController.removeListener('updatefound', windowReload)
- console.log('swController ready')
- await timeout(1000)
- console.log('connecting to app')
- await connectApp()
- console.log('app connected')
- } catch (err) {
- console.error(err)
- }
-})
-
-console.log('starting service worker')
-swController.startWorker()
-
-// Setup listener for when the service worker is read
-function connectApp () {
- const connectionStream = SwStream({
- serviceWorker: swController.getWorker(),
- context: name,
- })
- return new Promise((resolve, reject) => {
- startPopup({ container, connectionStream }, (err, store) => {
- console.log('hello from MetaMascara ui!')
- if (err) reject(err)
- store.subscribe(() => {
- const state = store.getState()
- if (state.appState.shouldClose) window.close()
- })
- resolve()
- })
- })
-}
-
-function windowReload () {
- if (window.METAMASK_SKIP_RELOAD) return
- window.location.reload()
-}
-
-function timeout (time) {
- return new Promise((resolve) => {
- setTimeout(resolve, time || 1500)
- })
-}
diff --git a/mascara/test/helpers.js b/mascara/test/helpers.js
deleted file mode 100644
index 8fc5c816bda9..000000000000
--- a/mascara/test/helpers.js
+++ /dev/null
@@ -1,7 +0,0 @@
-export default function wait (time) {
- return new Promise(function (resolve, reject) {
- setTimeout(function () {
- resolve()
- }, time * 3 || 1500)
- })
-}
diff --git a/mascara/test/index.js b/mascara/test/index.js
deleted file mode 100644
index d62e43705d06..000000000000
--- a/mascara/test/index.js
+++ /dev/null
@@ -1,22 +0,0 @@
-var fs = require('fs')
-var path = require('path')
-var browserify = require('browserify')
-var tests = fs.readdirSync(path.join(__dirname, 'lib'))
-var bundlePath = path.join(__dirname, 'test-bundle.js')
-var b = browserify()
-
-// Remove old bundle
-try {
- fs.unlinkSync(bundlePath)
-} catch (e) {
- console.error(e)
-}
-
-var writeStream = fs.createWriteStream(bundlePath)
-
-tests.forEach(function (fileName) {
- b.add(path.join(__dirname, 'lib', fileName))
-})
-
-b.bundle().pipe(writeStream)
-
diff --git a/mascara/test/jquery-3.1.0.min.js b/mascara/test/jquery-3.1.0.min.js
deleted file mode 100644
index f6a6a99e60ee..000000000000
--- a/mascara/test/jquery-3.1.0.min.js
+++ /dev/null
@@ -1,4 +0,0 @@
-/*! jQuery v3.1.0 | (c) jQuery Foundation | jquery.org/license */
-!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.1.0",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null!=a?a<0?this[a+this.length]:this[a]:f.call(this)},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"label"in b&&b.disabled===a||"form"in b&&b.disabled===a||"form"in b&&b.disabled===!1&&(b.isDisabled===a||b.isDisabled!==!a&&("label"in b||!ea(b))!==a)}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(_,aa),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=V.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(_,aa),$.test(j[0].type)&&qa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&sa(j),!a)return G.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||$.test(a)&&qa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext,B=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,C=/^.[^:#\[\.,]*$/;function D(a,b,c){if(r.isFunction(b))return r.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return r.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(C.test(b))return r.filter(b,a,c);b=r.filter(b,a)}return r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType})}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(D(this,a||[],!1))},not:function(a){return this.pushStack(D(this,a||[],!0))},is:function(a){return!!D(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var E,F=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,G=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||E,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:F.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),B.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};G.prototype=r.fn,E=r(d);var H=/^(?:parents|prev(?:Until|All))/,I={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function J(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return J(a,"nextSibling")},prev:function(a){return J(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return a.contentDocument||r.merge([],a.childNodes)}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(I[a]||r.uniqueSort(e),H.test(a)&&e.reverse()),this.pushStack(e)}});var K=/\S+/g;function L(a){var b={};return r.each(a.match(K)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?L(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function M(a){return a}function N(a){throw a}function O(a,b,c){var d;try{a&&r.isFunction(d=a.promise)?d.call(a).done(b).fail(c):a&&r.isFunction(d=a.then)?d.call(a,b,c):b.call(void 0,a)}catch(a){c.call(void 0,a)}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b=f&&(d!==N&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:M,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:M)),c[2][3].add(g(0,a,r.isFunction(d)?d:N))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(O(a,g.done(h(c)).resolve,g.reject),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)O(e[c],h(c),g.reject);return g.promise()}});var P=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&P.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var Q=r.Deferred();r.fn.ready=function(a){return Q.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,holdReady:function(a){a?r.readyWait++:r.ready(!0)},ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||Q.resolveWith(d,[r]))}}),r.ready.then=Q.then;function R(){d.removeEventListener("DOMContentLoaded",R),a.removeEventListener("load",R),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",R),a.addEventListener("load",R));var S=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)S(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,
-r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h1,null,!0)},removeData:function(a){return this.each(function(){W.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=V.get(a,b),c&&(!d||r.isArray(c)?d=V.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return V.get(a,c)||V.access(a,c,{empty:r.Callbacks("once memory").add(function(){V.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length\x20\t\r\n\f]+)/i,ja=/^$|\/(?:java|ecma)script/i,ka={option:[1,""],thead:[1,""],col:[2,""],tr:[2,""],td:[3,""],_default:[0,"",""]};ka.optgroup=ka.option,ka.tbody=ka.tfoot=ka.colgroup=ka.caption=ka.thead,ka.th=ka.td;function la(a,b){var c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&r.nodeName(a,b)?r.merge([a],c):c}function ma(a,b){for(var c=0,d=a.length;c-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=la(l.appendChild(f),"script"),j&&ma(g),c){k=0;while(f=g[k++])ja.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var pa=d.documentElement,qa=/^key/,ra=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,sa=/^([^.]*)(?:\.(.+)|)/;function ta(){return!0}function ua(){return!1}function va(){try{return d.activeElement}catch(a){}}function wa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)wa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=ua;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(pa,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(K)||[""],j=b.length;while(j--)h=sa.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.hasData(a)&&V.get(a);if(q&&(i=q.events)){b=(b||"").match(K)||[""],j=b.length;while(j--)if(h=sa.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&V.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(V.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c-1:r.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h\x20\t\r\n\f]*)[^>]*)\/>/gi,ya=/
-