diff options
author | brunobar79 <brunobar79@gmail.com> | 2018-09-06 04:26:09 +0800 |
---|---|---|
committer | brunobar79 <brunobar79@gmail.com> | 2018-09-06 04:26:09 +0800 |
commit | 8ee01f4e99d59dbe1bfa5703da9b5efda41a7a52 (patch) | |
tree | 106d2ab08621f972b4b9140df16e129a84526703 /app | |
parent | b208ce723459a13f9b1fd6837af9d2858ba4cc17 (diff) | |
parent | dc2431fe62bc7e50ebbf864389e9590f29d2136f (diff) | |
download | tangerine-wallet-browser-8ee01f4e99d59dbe1bfa5703da9b5efda41a7a52.tar tangerine-wallet-browser-8ee01f4e99d59dbe1bfa5703da9b5efda41a7a52.tar.gz tangerine-wallet-browser-8ee01f4e99d59dbe1bfa5703da9b5efda41a7a52.tar.bz2 tangerine-wallet-browser-8ee01f4e99d59dbe1bfa5703da9b5efda41a7a52.tar.lz tangerine-wallet-browser-8ee01f4e99d59dbe1bfa5703da9b5efda41a7a52.tar.xz tangerine-wallet-browser-8ee01f4e99d59dbe1bfa5703da9b5efda41a7a52.tar.zst tangerine-wallet-browser-8ee01f4e99d59dbe1bfa5703da9b5efda41a7a52.zip |
Merge branch 'develop' of github.com:MetaMask/metamask-extension into trezor-v5
Diffstat (limited to 'app')
-rw-r--r-- | app/_locales/cs/messages.json | 2 | ||||
-rw-r--r-- | app/_locales/de/messages.json | 6 | ||||
-rw-r--r-- | app/_locales/en/messages.json | 34 | ||||
-rw-r--r-- | app/_locales/es/messages.json | 2 | ||||
-rw-r--r-- | app/_locales/fr/messages.json | 2 | ||||
-rw-r--r-- | app/_locales/ko/messages.json | 822 | ||||
-rw-r--r-- | app/_locales/ru/messages.json | 2 | ||||
-rw-r--r-- | app/_locales/tml/messages.json | 2 | ||||
-rw-r--r-- | app/_locales/tr/messages.json | 2 | ||||
-rw-r--r-- | app/manifest.json | 1 | ||||
-rw-r--r-- | app/scripts/background.js | 20 | ||||
-rw-r--r-- | app/scripts/controllers/preferences.js | 120 | ||||
-rw-r--r-- | app/scripts/lib/account-tracker.js | 18 | ||||
-rw-r--r-- | app/scripts/lib/ipfsContent.js | 2 | ||||
-rw-r--r-- | app/scripts/metamask-controller.js | 66 |
15 files changed, 941 insertions, 160 deletions
diff --git a/app/_locales/cs/messages.json b/app/_locales/cs/messages.json index 6a4ebc8a5..55344f3e1 100644 --- a/app/_locales/cs/messages.json +++ b/app/_locales/cs/messages.json @@ -796,7 +796,7 @@ "message": "Testovací faucet" }, "to": { - "message": "Komu: " + "message": "Komu" }, "toETHviaShapeShift": { "message": "$1 na ETH přes ShapeShift", diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index c06a99250..352d5ad7d 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -384,7 +384,7 @@ "infoHelp": { "message": "Info & Hilfe" }, - "insufficientFunds": { + "insufficientFunds": { "message": "Nicht genügend Guthaben." }, "insufficientTokens": { @@ -572,7 +572,7 @@ "description": "Wähle diesen Dateityp um damit einen Account zu importieren" }, "privateKeyWarning": { - "message": "Warnung: Niemals jemanden deinen Private Key mitteilen. Jeder der im Besitz deines Private Keys ist, kann jegliches Guthaben deines Accounts stehlen." + "message": "Warnung: Niemals jemanden deinen Private Key mitteilen. Jeder der im Besitz deines Private Keys ist, kann jegliches Guthaben deines Accounts stehlen." }, "privateNetwork": { "message": "Privates Netzwerk" @@ -775,7 +775,7 @@ "message": "Testfaucet" }, "to": { - "message": "An:" + "message": "An" }, "toETHviaShapeShift": { "message": "$1 an ETH via ShapeShift", diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index a25a2bd59..14e867b33 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -29,6 +29,9 @@ "addTokens": { "message": "Add Tokens" }, + "addSuggestedTokens": { + "message": "Add Suggested Tokens" + }, "addAcquiredTokens": { "message": "Add the tokens you've acquired using MetaMask" }, @@ -451,6 +454,9 @@ "hideTokenPrompt": { "message": "Hide Token?" }, + "history": { + "message": "History" + }, "howToDeposit": { "message": "How would you like to deposit Ether?" }, @@ -651,7 +657,7 @@ "message": "No transaction history." }, "noTransactions": { - "message": "No Transactions" + "message": "You have no transactions" }, "notFound": { "message": "Not Found" @@ -702,6 +708,9 @@ "pasteSeed": { "message": "Paste your seed phrase here!" }, + "pending": { + "message": "pending" + }, "personalAddressDetected": { "message": "Personal address detected. Input the token contract address." }, @@ -730,6 +739,9 @@ "qrCode": { "message": "Show QR Code" }, + "queue": { + "message": "Queue" + }, "readdToken": { "message": "You can add this token back in the future by going go to “Add token” in your accounts options menu." }, @@ -870,6 +882,12 @@ "secretPhrase": { "message": "Enter your secret twelve word phrase here to restore your vault." }, + "showHexData": { + "message": "Show Hex Data" + }, + "showHexDataDescription": { + "message": "Select this to show the hex data field on the send screen" + }, "newPassword8Chars": { "message": "New Password (min 8 chars)" }, @@ -897,6 +915,12 @@ "sendTokens": { "message": "Send Tokens" }, + "sentEther": { + "message": "sent ether" + }, + "sentTokens": { + "message": "sent tokens" + }, "separateEachWord": { "message": "Separate each word with a single space" }, @@ -910,6 +934,9 @@ "orderOneHere": { "message": "Order a Trezor or Ledger and keep your funds in cold storage" }, + "outgoing": { + "message": "Outgoing" + }, "searchTokens": { "message": "Search Tokens" }, @@ -973,6 +1000,9 @@ "sign": { "message": "Sign" }, + "signatureRequest": { + "message": "Signature Request" + }, "signed": { "message": "Signed" }, @@ -1025,7 +1055,7 @@ "message": "Test Faucet" }, "to": { - "message": "To: " + "message": "To" }, "toETHviaShapeShift": { "message": "$1 to ETH via ShapeShift", diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index ed7f8f681..3e43a7b43 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -772,7 +772,7 @@ "message": "Probar Faucet" }, "to": { - "message": "Para:" + "message": "Para" }, "toETHviaShapeShift": { "message": "$1 a ETH via ShapeShift", diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index 1463e2b5f..6f850d89b 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -490,7 +490,7 @@ "message": "Sélectionner un service" }, "send": { - "message": "Envoyé" + "message": "Envoyer" }, "sendTokens": { "message": "Envoyer des jetons" diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index d3801c4f5..30d032357 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -3,86 +3,125 @@ "message": "수락" }, "account": { - "message": "계좌" + "message": "계정" }, "accountDetails": { - "message": "계좌 상세보기" + "message": "계정 상세보기" }, "accountName": { - "message": "계좌 이름" + "message": "계정 이름" }, "address": { "message": "주소" }, + "addCustomToken": { + "message": "사용자 정의 토큰 추가" + }, "addToken": { "message": "토큰 추가" }, + "addTokens": { + "message": "토큰 추가" + }, + "addAcquiredTokens": { + "message": "메타마스크를 통해 획득한 토큰 추가" + }, "amount": { - "message": "금액" + "message": "수량" }, "amountPlusGas": { - "message": "금액 + 가스" + "message": "수량 + 가스" }, "appDescription": { "message": "이더리움 브라우저 확장 프로그램", - "description": "어플리케이션 내용" + "description": "어플리케이션 설명" }, "appName": { "message": "메타마스크", "description": "어플리케이션 이름" }, + "approved": { + "message": "수락" + }, "attemptingConnect": { - "message": "블록체인에 접속 시도 중입니다." + "message": "블록체인에 접속을 시도하는 중입니다." + }, + "attributions": { + "message": "속성" }, "available": { - "message": "사용 가능한" + "message": "사용 가능" }, "back": { - "message": "뒤로" + "message": "돌아가기" }, "balance": { - "message": "잔액:" + "message": "잔액" + }, + "balances": { + "message": "토큰 잔액" }, "balanceIsInsufficientGas": { - "message": "가스가 충분하지 않습니다." + "message": "현재 가스 총합에 대해 잔액이 부족합니다" }, "beta": { - "message": "베타" + "message": "BETA" }, "betweenMinAndMax": { "message": "$1 이상 $2 이하여야 합니다.", - "description": "helper for inputting hex as decimal input" + "description": "10진수 입력으로 hex값 입력을 도와줍니다" + }, + "blockiesIdenticon": { + "message": "Blockies 아이덴티콘 사용" }, "borrowDharma": { - "message": "Dharma에서 빌리기(베타)" + "message": "Dharma에서 대여하기(Beta)" + }, + "builtInCalifornia": { + "message": "메타마스크는 캘리포니아에서 디자인되고 만들어졌습니다." }, "buy": { "message": "구매" }, "buyCoinbase": { - "message": "코인베이스에서 구매" + "message": "코인베이스에서 구매하기" }, "buyCoinbaseExplainer": { - "message": "코인베이스에서 비트코인, 이더리움, 라이트코인을 구매하실 수 있습니다." + "message": "코인베이스는 비트코인, 이더리움, 라이트코인을 거래할 수 있는 유명한 거래소입니다." + }, + "ok": { + "message": "확인" }, "cancel": { "message": "취소" }, + "classicInterface": { + "message": "예전 인터페이스" + }, "clickCopy": { "message": "클릭하여 복사" }, + "close": { + "message": "닫기" + }, "confirm": { "message": "승인" }, + "confirmed": { + "message": "승인됨" + }, "confirmContract": { "message": "컨트랙트 승인" }, "confirmPassword": { - "message": "패스워드 승인" + "message": "비밀번호 확인" }, "confirmTransaction": { "message": "트랜잭션 승인" }, + "continue": { + "message": "계속" + }, "continueToCoinbase": { "message": "코인베이스로 계속하기" }, @@ -90,72 +129,93 @@ "message": "컨트랙트 배포" }, "conversionProgress": { - "message": "변환중.." + "message": "변환 진행중" }, "copiedButton": { - "message": "복사되었습니다." + "message": "복사됨" }, "copiedClipboard": { - "message": "클립보드에 복사되었습니다." + "message": "클립보드에 복사되었습니다" }, "copiedExclamation": { - "message": "복사되었습니다." + "message": "복사됨!" + }, + "copiedSafe": { + "message": "안전한 곳에 복사하였습니다" }, "copy": { - "message": "복사하기" + "message": "복사" + }, + "copyContractAddress": { + "message": "컨트랙트 주소 복사" }, "copyToClipboard": { - "message": "클립보드에 복사" + "message": "클립보드로 복사" }, "copyButton": { "message": " 복사 " }, "copyPrivateKey": { - "message": "비밀 키 (클릭하여 복사)" + "message": "비밀 키입니다 (클릭하여 복사)" }, "create": { "message": "생성" }, "createAccount": { - "message": "계좌 생성" + "message": "계정 생성" }, "createDen": { "message": "생성" }, "crypto": { "message": "암호화폐", - "description": "Exchange type (cryptocurrencies)" + "description": "거래 유형 (암호화폐)" + }, + "currentConversion": { + "message": "선택된 단위" + }, + "currentNetwork": { + "message": "현재 네트워크" }, "customGas": { "message": "가스 설정" }, + "customToken": { + "message": "사용자 정의 토큰" + }, "customize": { - "message": "커스터마이즈" + "message": "맞춤화 하기" }, "customRPC": { - "message": "커스텀 RPC" + "message": "사용자 정의 RPC" + }, + "decimalsMustZerotoTen": { + "message": "소수점은 0 이상이고 36 이하여야 합니다." + }, + "decimal": { + "message": "소수점 정확도" }, "defaultNetwork": { "message": "이더리움 트랜잭션의 기본 네트워크는 메인넷입니다." }, "denExplainer": { - "message": "DEN은 비밀번호가 암호화 된 MetaMask의 스토리지입니다." + "message": "DEN은 비밀번호로 암호화 된 메타마스크의 저장소입니다." }, "deposit": { "message": "입금" }, "depositBTC": { - "message": "아래 주소로 BTC를 입급해주세요." + "message": "다음 주소로 BTC를 입급해주세요." }, "depositCoin": { - "message": "아래 주소로 $1를 입금해주세요.", - "description": "Tells the user what coin they have selected to deposit with shapeshift" + "message": "다음 주소로 $1 만큼 입금해주세요.", + "description": "사용자에게 shapeshift에서 어떤 코인을 선택해 입금했는지 알려줍니다" }, "depositEth": { - "message": "이더 입금" + "message": "이더 입금하기" }, "depositEther": { - "message": "이더 입금" + "message": "이더리움 입금하기" }, "depositFiat": { "message": "현금으로 입금하기" @@ -167,10 +227,10 @@ "message": "ShapeShift를 통해 입금하기" }, "depositShapeShiftExplainer": { - "message": "다른 암호화폐를 가지고 있으면, 계좌 생성 필요없이, 거래를 하거나 메타마스크 지갑을 통해 이더를 입금할 수 있습니다." + "message": "다른 암호화폐를 가지고 있으면, 계정을 생성할 필요없이 메타마스크 지갑에 이더리움을 바로 거래하거나 입금할 수 있습니다." }, "details": { - "message": "상세" + "message": "세부사항" }, "directDeposit": { "message": "즉시 입금" @@ -179,70 +239,103 @@ "message": "이더 즉시 입금" }, "directDepositEtherExplainer": { - "message": "이더를 이미 보유하고 있다면, 직접 입금을 통해 이더를 즉시 입금하실 수 있습니다." + "message": "약간의 이더를 이미 보유하고 있다면, 새로 만든 지갑에 직접 입금하여 이더를 보유할 수 있습니다." }, "done": { "message": "완료" }, + "downloadStateLogs": { + "message": "상태 로그 다운로드" + }, + "dropped": { + "message": "중단됨" + }, "edit": { "message": "수정" }, "editAccountName": { - "message": "계좌명 수정" + "message": "계정 이름 수정" + }, + "editingTransaction": { + "message": "트랜젝션을 변경합니다" + }, + "emailUs": { + "message": "저자에게 메일 보내기!" }, "encryptNewDen": { - "message": "새 DEN 암호화" + "message": "새로운 DEN을 암호화" }, "enterPassword": { - "message": "패스워드를 입력해주세요." + "message": "비밀번호를 입력해주세요" + }, + "enterPasswordConfirm": { + "message": "비밀번호를 다시 입력해 주세요" + }, + "enterPasswordContinue": { + "message": "계속하기 위해 비밀번호 입력" + }, + "passwordNotLongEnough": { + "message": "비밀번호가 충분히 길지 않습니다" + }, + "passwordsDontMatch": { + "message": "비밀번호가 맞지 않습니다" }, "etherscanView": { - "message": "이더스캔에서 계좌보기" + "message": "이더스캔에서 계정보기" }, "exchangeRate": { "message": "환율" }, "exportPrivateKey": { - "message": "비밀키 내보내기" + "message": "개인키 내보내기" }, "exportPrivateKeyWarning": { - "message": "Export private keys at your own risk." + "message": "개인키 내보내기는 위험을 감수해야 합니다." }, "failed": { "message": "실패" }, "fiat": { "message": "FIAT", - "description": "Exchange type" + "description": "거래 형식" }, "fileImportFail": { - "message": "파일을 가져올 수 없나요? 여기를 클릭해주세요!", - "description": "Helps user import their account from a JSON file" + "message": "파일을 가져올 수 없나요? 이곳을 클릭해주세요!", + "description": "JSON 파일로부터 계정 가져오기를 도와줍니다" + }, + "followTwitter": { + "message": "트위터에서 팔로우하세요" }, "from": { - "message": "보내는 사람" + "message": "보내는 이" + }, + "fromToSame": { + "message": "보내고 받는 주소는 동일할 수 없습니다" }, "fromShapeShift": { "message": "ShapeShift로 부터" }, "gas": { "message": "가스", - "description": "Short indication of gas cost" + "description": "가스 가격의 줄임" }, "gasFee": { "message": "가스 수수료" }, "gasLimit": { - "message": "가스 리밋" + "message": "가스 한도" }, "gasLimitCalculation": { - "message": "네트워크 성공률을 기반으로 적합한 가스 리밋을 계산합니다." + "message": "네트워크 성공률을 기반으로 적합한 가스 한도를 계산합니다." }, "gasLimitRequired": { - "message": "가스 리밋이 필요합니다." + "message": "가스 한도가 필요합니다." }, "gasLimitTooLow": { - "message": "가스 리밋은 21000 이상이여야 합니다." + "message": "가스 한도는 최소 21000 이상이여야 합니다." + }, + "generatingSeed": { + "message": "시드 생성중..." }, "gasPrice": { "message": "가스 가격 (GWEI)" @@ -253,21 +346,27 @@ "gasPriceRequired": { "message": "가스 가격이 필요합니다." }, + "generatingTransaction": { + "message": "트랜잭션 생성중" + }, "getEther": { "message": "이더 얻기" }, "getEtherFromFaucet": { - "message": "faucet에서 $1에 달하는 이더를 얻으세요.", - "description": "Displays network name for Ether faucet" + "message": "파우셋에서 $1에 달하는 이더를 얻으세요.", + "description": "이더 파우셋에 대한 네트워크 이름을 표시합니다" }, "greaterThanMin": { "message": "$1 이상이어야 합니다.", - "description": "helper for inputting hex as decimal input" + "description": "10진수 입력으로 hex값 입력을 도와줍니다" }, "here": { "message": "여기", "description": "as in -click here- for more information (goes with troubleTokenBalances)" }, + "hereList": { + "message": "리스트가 있습니다!!!!" + }, "hide": { "message": "숨기기" }, @@ -280,51 +379,96 @@ "howToDeposit": { "message": "어떤 방법으로 이더를 입금하시겠습니까?" }, + "holdEther": { + "message": "It allows you to hold ether & tokens, and serves as your bridge to decentralized applications." + }, "import": { - "message": "파일에서 가져오기", - "description": "Button to import an account from a selected file" + "message": "가져오기", + "description": "선택된 파일로부터 계정 가져오기 버튼" }, "importAccount": { - "message": "계좌 가져오기" + "message": "계정 가져오기" + }, + "importAccountMsg": { + "message": " 가져온 계정은 메타마스크에서 원래 생성된 계정의 시드구문과 연관성이 없습니다. 가져온 계정에 대해 더 배우기 " }, "importAnAccount": { - "message": "계좌 가져오기" + "message": "계정 가져오기" }, "importDen": { - "message": "기존 DEN 가져오기" + "message": "기존의 DEN 가져오기" }, "imported": { - "message": "가져오기 완료", - "description": "status showing that an account has been fully loaded into the keyring" + "message": "가져온 계정", + "description": "이 상태는 해당 계정이 keyring으로 완전히 적재된 상태임을 표시합니다" + }, + "importUsingSeed": { + "message": "계정 시드 구문으로 가져오기" }, "infoHelp": { "message": "정보 및 도움말" }, + "initialTransactionConfirmed": { + "message": "초기 트랜잭션이 네트워크를 통해 확정되었습니다. 확인을 누르고 이전으로 돌아갑니다." + }, + "insufficientFunds": { + "message": "충분하지 않은 자금." + }, + "insufficientTokens": { + "message": "충분하지 않은 토큰." + }, "invalidAddress": { - "message": "유효하지 않은 주소" + "message": "올바르지 않은 주소" + }, + "invalidAddressRecipient": { + "message": "수신 주소가 올바르지 않습니다" }, "invalidGasParams": { - "message": "유효하지 않은 가스 입력값" + "message": "올바르지 않은 가스 입력값" }, "invalidInput": { - "message": "유효하지 않은 입력값" + "message": "올바르지 않은 입력값" }, "invalidRequest": { "message": "유효하지 않은 요청" }, + "invalidRPC": { + "message": "올바르지 않은 RPC URI" + }, + "jsonFail": { + "message": "이상이 있습니다. JSON 파일이 올바른 파일인지 확인해주세요." + }, "jsonFile": { "message": "JSON 파일", - "description": "format for importing an account" + "description": "계정을 가져오기 위한 형식" + }, + "keepTrackTokens": { + "message": "메타마스크 계정을 통해 구입한 토큰 기록을 추적 및 보관합니다." }, "kovan": { "message": "Kovan 테스트넷" }, + "knowledgeDataBase": { + "message": "지식 베이스 방문" + }, + "max": { + "message": "최대" + }, + "learnMore": { + "message": "더 배우기." + }, "lessThanMax": { "message": "$1 이하여야합니다.", - "description": "helper for inputting hex as decimal input" + "description": "10진수 입력으로 hex값 입력을 도와줍니다" + }, + "likeToAddTokens": { + "message": "토큰을 추가하시겠습니까?" + }, + "links": { + "message": "링크" }, "limit": { - "message": "리밋" + "message": "한도" }, "loading": { "message": "로딩중..." @@ -335,11 +479,17 @@ "localhost": { "message": "로컬호스트 8545" }, + "login": { + "message": "로그인" + }, "logout": { "message": "로그아웃" }, "loose": { - "message": "외부 비밀키" + "message": "느슨함" + }, + "loweCaseWords": { + "message": "시드 단어는 소문자만 가능합니다" }, "mainnet": { "message": "이더리움 메인넷" @@ -347,94 +497,127 @@ "message": { "message": "메시지" }, + "metamaskDescription": { + "message": "메타마스크는 이더리움을 위한 안전한 신분 저장소입니다." + }, + "metamaskSeedWords": { + "message": "메타마스크 시드 단어" + }, "min": { "message": "최소" }, "myAccounts": { - "message": "내 계좌" + "message": "내 계정" + }, + "mustSelectOne": { + "message": "적어도 하나의 토큰을 선택하세요." }, "needEtherInWallet": { - "message": "dApp을 이용하기 위해서는 지갑에 이더가 있어야 합니다." + "message": "메타마스크를 통한 dApp을 이용하기 위해서는 지갑에 이더가 있어야 합니다." }, "needImportFile": { "message": "가져올 파일을 선택해주세요.", - "description": "User is important an account and needs to add a file to continue" + "description": "사용자는 계정을 가져오기 위해서 파일을 추가후 계속 진행해야 합니다" }, "needImportPassword": { - "message": "선택 된 파일에 패스워드를 입력해주세요.", - "description": "Password and file needed to import an account" + "message": "선택 된 파일에 대한 비밀번호를 입력해주세요.", + "description": "계정을 가져오기 위해서 비밀번호와 파일이 필요합니다." + }, + "negativeETH": { + "message": "음수값의 이더를 보낼 수 없습니다." }, "networks": { "message": "네트워크" }, + "nevermind": { + "message": "상관안함" + }, "newAccount": { - "message": "새 계좌" + "message": "새 계정" }, "newAccountNumberName": { - "message": "새 계좌 $1", - "description": "Default name of next account to be created on create account screen" + "message": "새 계정 $1", + "description": "계정 생성시 볼 수 있는 새 계정의 기본 이름" }, "newContract": { "message": "새 컨트랙트" }, "newPassword": { - "message": "새 패스워드 (최소 8자 이상)" + "message": "새 비밀번호 (최소 8자 이상)" }, "newRecipient": { "message": "받는 사람" }, + "newRPC": { + "message": "새로운 RPC URL" + }, "next": { "message": "다음" }, "noAddressForName": { - "message": "이 이름에는 주소가 설정되어 있지 않습니다." + "message": "이 이름에 대해 주소가 설정되어 있지 않습니다." }, "noDeposits": { - "message": "입금이 없습니다." + "message": "입금 내역이 없습니다." }, "noTransactionHistory": { "message": "트랜잭션 기록이 없습니다." }, "noTransactions": { - "message": "트랜잭션이 없습니다." + "message": "트랜잭션이 없습니다" }, "notStarted": { - "message": "시작되지 않음." + "message": "시작 안됨" }, "oldUI": { - "message": "구버전의 UI" + "message": "구버전 UI" }, "oldUIMessage": { - "message": "구버전 UI로 변경하셨습니다. 우 상단 드랍다운 메뉴에서 새 UI로 변경하실 수 있습니다." + "message": "구버전 UI로 변경하셨습니다. 우 상단 드롭다운 메뉴에서 새 UI로 변경하실 수 있습니다." }, "or": { "message": "또는", - "description": "choice between creating or importing a new account" + "description": "새 계정을 만들거나 가져오기중에 선택하기" + }, + "password": { + "message": "비밀번호" + }, + "passwordCorrect": { + "message": "비밀번호가 맞는지 확인해주세요." }, "passwordMismatch": { - "message": "패스워드가 일치하지 않습니다.", - "description": "in password creation process, the two new password fields did not match" + "message": "비밀번호가 일치하지 않습니다.", + "description": "비밀번호를 생성하는 과정에서 두개의 새 비밀번호 입력란이 일치하지 않습니다" }, "passwordShort": { - "message": "패스워드가 너무 짧습니다.", - "description": "in password creation process, the password is not long enough to be secure" + "message": "비밀번호가 짧습니다.", + "description": "비밀번호를 생성하는 과정에서 비밀번호가 짧으면 안전하지 않습니다" }, "pastePrivateKey": { - "message": "비밀키를 입력해주세요.", - "description": "For importing an account from a private key" + "message": "개인키를 입력해주세요:", + "description": "개인키로부터 계정을 가져오는 것입니다" }, "pasteSeed": { - "message": "시드 문장들을 붙여넣어주세요." + "message": "시드 구문을 이곳에 붙여넣어주세요!" + }, + "personalAddressDetected": { + "message": "개인 주소가 탐지됨. 토큰 컨트랙트 주소를 입력하세요." }, "pleaseReviewTransaction": { "message": "트랜잭션을 검토해주세요." }, + "popularTokens": { + "message": "인기있는 토큰" + }, + "privacyMsg": { + "message": "개인정보 보호 정책" + }, "privateKey": { - "message": "비밀키", - "description": "select this type of file to use to import an account" + "message": "개인키", + "description": "이 형식의 파일을 선택하여 계정을 가져올 수 있습니다" }, "privateKeyWarning": { - "message": " 절대 이 키를 노출하지 마십시오. 비밀키가 노출되면 누구나 당신의 계좌에서 자산을 빼갈 수 있습니다." + "message": " 절대 이 키를 노출하지 마십시오. 개인키가 노출되면 누구나 당신의 계정에서 자산을 빼갈 수 있습니다." }, "privateNetwork": { "message": "프라이빗 네트워크" @@ -446,28 +629,64 @@ "message": "옵션 메뉴에서 “토큰 추가”를 눌러서 추후에 다시 이 토큰을 추가하실 수 있습니다." }, "readMore": { - "message": "더 읽기." + "message": "여기서 더 보기." + }, + "readMore2": { + "message": "더 보기." }, "receive": { "message": "받기" }, "recipientAddress": { - "message": "받는 사람 주소" + "message": "받는 주소" }, "refundAddress": { "message": "환불받을 주소" }, "rejected": { - "message": "거부되었음." + "message": "거부됨" + }, + "reset": { + "message": "초기화" + }, + "resetAccount": { + "message": "계정 초기화" + }, + "resetAccountDescription": { + "message": "계정을 초기화 하는 경우에 트랜잭션 기록이 삭제됩니다." + }, + "restoreFromSeed": { + "message": "계정을 복구하시겠습니까?" + }, + "restoreVault": { + "message": "저장소 복구" }, "required": { - "message": "필요함." + "message": "필요함" }, "retryWithMoreGas": { - "message": "더 높은 가스 가격으로 다시 시도해주세요." + "message": "더 높은 가스 가격으로 다시 시도해주세요" + }, + "walletSeed": { + "message": "지갑 시드값" + }, + "revealSeedWords": { + "message": "시드 단어 보이기" + }, + "revealSeedWordsTitle": { + "message": "시드 단어" + }, + "revealSeedWordsDescription": { + "message": "브라우저를 바꾸거나 컴퓨터를 옮기는 경우 이 시드 구문이 필요하며 이를 통해 계정에 접근할 수 있습니다. 시드 구문을 안전한 곳에 보관하세요." + }, + "revealSeedWordsWarningTitle": { + "message": "이 구문을 다른사람과 절대로 공유하지 마세요!" + }, + "revealSeedWordsWarning": { + "message": "이 단어모음은 당신의 모든 계정을 훔치는데 사용할 수 있습니다." }, "revert": { - "message": "취소" + "message": "되돌림" }, "rinkeby": { "message": "Rinkeby 테스트넷" @@ -475,46 +694,122 @@ "ropsten": { "message": "Ropsten 테스트넷" }, + "rpc": { + "message": "사용자 정의 RPC" + }, + "currentRpc": { + "message": "현재 RPC" + }, + "connectingToMainnet": { + "message": "이더리움 메인넷 접속중" + }, + "connectingToRopsten": { + "message": "Ropsten 테스트넷 접속중" + }, + "connectingToKovan": { + "message": "Kovan 테스트넷 접속중" + }, + "connectingToRinkeby": { + "message": "Rinkeby 테스트넷 접속중" + }, + "connectingToUnknown": { + "message": "알려지지 않은 네트워크 접속중" + }, "sampleAccountName": { - "message": "예) 나의 새 계좌", - "description": "Help user understand concept of adding a human-readable name to their account" + "message": "예) 나의 새 계정", + "description": "각 계정에 대해서 구별하기 쉬운 이름을 지정하여 사용자가 쉽게 이해할 수 있게 합니다" }, "save": { "message": "저장" }, + "speedUpTitle": { + "message": "트랜잭션 속도 향상하기" + }, + "speedUpSubtitle": { + "message": "트랜잭션 가스 가격을 늘려서 해당 트랙잭션에 덮어쓰기 하여 속도를 빠르게 합니다" + }, + "saveAsCsvFile": { + "message": "CSV 파일로 저장" + }, "saveAsFile": { "message": "파일로 저장", - "description": "Account export process" + "description": "계정 내보내기 절차" + }, + "saveSeedAsFile": { + "message": "시드 단어를 파일로 저장하기" + }, + "search": { + "message": "검색" + }, + "searchResults": { + "message": "검색 결과" + }, + "secretPhrase": { + "message": "12개 단어로 구성된 비밀 구문을 입력하여 저장소를 복구하세요." + }, + "newPassword8Chars": { + "message": "새 비밀번호 (최소 8문자)" + }, + "seedPhraseReq": { + "message": "시드 구문은 12개의 단어입니다" + }, + "select": { + "message": "선택" + }, + "selectCurrency": { + "message": "통화 선택" }, "selectService": { "message": "서비스 선택" }, + "selectType": { + "message": "형식 선택" + }, "send": { "message": "전송" }, + "sendETH": { + "message": "ETH 보내기" + }, "sendTokens": { "message": "토큰 전송" }, + "onlySendToEtherAddress": { + "message": "이더리움 주소로 ETH만 송금하세요." + }, + "onlySendTokensToAccountAddress": { + "message": "이더리움계정 주소로 $1 만 보내기.", + "description": "토큰 심볼 보이기" + }, + "searchTokens": { + "message": "토큰 검색" + }, "sendTokensAnywhere": { - "message": "이더 계좌로 토큰 전송" + "message": "이더 계정로 토큰 전송" }, "settings": { "message": "설정" }, + "info": { + "message": "정보" + }, "shapeshiftBuy": { "message": "Shapeshift를 통해서 구매하기" }, "showPrivateKeys": { - "message": "비밀키 보기" + "message": "개인키 보기" }, "showQRCode": { - "message": "QR코드 보기" + "message": "QR 코드 보기" }, "sign": { "message": "서명" }, + "signed": { + "message": "서명됨" + }, "signMessage": { - "message": "서명 메시지" + "message": "메시지 서명" }, "signNotice": { "message": "이 메시지에 대한 서명은 위험할 수 있습니다.\n 완전히 신뢰할 수 있는 사이트에서만 서명해주세요.\n 안전을 위해 추후의 버전에서는 삭제될 기능입니다. " @@ -523,33 +818,81 @@ "message": "서명 요청" }, "sigRequested": { - "message": "서명이 요청되었습니다." + "message": "서명이 요청됨" + }, + "spaceBetween": { + "message": "단어 사이에는 공백만 올 수 있습니다" }, "status": { "message": "상태" }, + "stateLogs": { + "message": "상태 로그" + }, + "stateLogsDescription": { + "message": "상태 로그에는 공개 계정 주소와 송신 트랜잭션 정보가 들어있습니다." + }, + "stateLogError": { + "message": "상태 로그 받기 실패." + }, "submit": { "message": "제출" }, + "submitted": { + "message": "제출됨" + }, + "supportCenter": { + "message": "지원 센터에 방문하기" + }, + "symbolBetweenZeroTen": { + "message": "심볼은 0에서 10개 사이의 문자여야 합니다." + }, "takesTooLong": { "message": "너무 오래걸리나요?" }, + "terms": { + "message": "사용 지침" + }, "testFaucet": { - "message": "Faucet 테스트" + "message": "파우셋 테스트" }, "to": { - "message": "대상" + "message": "받는이: " }, "toETHviaShapeShift": { "message": "ShapeShift를 통해 $1를 ETH로 바꾸기", - "description": "system will fill in deposit type in start of message" + "description": "시스템이 시작할 때에 입금 유형을 입력해줍니다" + }, + "token": { + "message": "토큰" + }, + "tokenAddress": { + "message": "토큰 주소" + }, + "tokenAlreadyAdded": { + "message": "토큰이 이미 추가되어있습니다." }, "tokenBalance": { - "message": "현재 토큰 잔액: " + "message": "현재 토큰 잔액:" + }, + "tokenSelection": { + "message": "토큰을 검색하거나 유명한 토큰 리스트에서 선택하시기 바랍니다." + }, + "tokenSymbol": { + "message": "토큰 기호" + }, + "tokenWarning1": { + "message": "메타마스크 계좌를 통해 구입한 토큰을 추적합니다. 다른 계정으로 토큰을 구입한 경우 이곳에 나타나지 않습니다." }, "total": { "message": "합계" }, + "transactions": { + "message": "트랜잭션" + }, + "transactionError": { + "message": "트랜잭션 오류. 컨트랙트 코드에서 예외 발생(Exception thrown)." + }, "transactionMemo": { "message": "트랜잭션 메모 (선택사항)" }, @@ -560,23 +903,29 @@ "message": "전송" }, "troubleTokenBalances": { - "message": "토큰 잔액을 가져오는데에 문제가 생겼습니다. (여기)서 상세내용을 볼 수 있습니다.", - "description": "Followed by a link (here) to view token balances" + "message": "토큰 잔액을 가져오는데에 문제가 생겼습니다. 링크에서 상세내용을 볼 수 있습니다.", + "description": "토큰 잔액을 보려면 (here) 링크를 따라가세요" + }, + "twelveWords": { + "message": "12개의 단어는 메타마스크 계정을 복구하기 위한 유일한 방법입니다.\n안전한 장소에 보관하시기 바랍니다." }, "typePassword": { - "message": "패스워드를 입력하세요." + "message": "비밀번호를 입력하세요" }, "uiWelcome": { - "message": "새 UI에 오신 것을 환영합니다. (베타)" + "message": "새로운 UI에 오신 것을 환영합니다. (Beta)" }, "uiWelcomeMessage": { - "message": "새 메타마스크 UI를 사용하고 계십니다. 토큰 전송과 같은 새 기능들을 사용해보시면서 문제가 있다면 알려주세요." + "message": "새로운 메타마스크 UI를 사용하고 계십니다. 토큰 전송과 같은 새 기능들을 사용해보시면서 문제가 있다면 알려주세요." + }, + "unapproved": { + "message": "허가안됨" }, "unavailable": { - "message": "유효하지 않은" + "message": "유효하지 않음" }, "unknown": { - "message": "알려지지 않은" + "message": "알려지지 않음" }, "unknownNetwork": { "message": "알려지지 않은 프라이빗 네트워크" @@ -584,19 +933,46 @@ "unknownNetworkId": { "message": "알려지지 않은 네트워크 ID" }, + "unlockMessage": { + "message": "우리가 기다리던 분권형 웹입니다" + }, + "uriErrorMsg": { + "message": "URI는 HTTP/HTTPS로 시작해야 합니다." + }, "usaOnly": { "message": "USA 거주자 한정", - "description": "Using this exchange is limited to people inside the USA" + "description": "해당 거래소는 USA거주자에 한해서만 사용가능합니다" }, "usedByClients": { - "message": "다양한 클라이언트에서 사용되고 있습니다." + "message": "다양한 클라이언트에서 사용되고 있습니다" + }, + "useOldUI": { + "message": "예전 UI 사용" + }, + "validFileImport": { + "message": "가져오기 위해 유효한 파일을 선택해야 합니다." + }, + "vaultCreated": { + "message": "저장소가 생성됨" }, "viewAccount": { - "message": "계좌 보기" + "message": "계정 보기" + }, + "viewOnEtherscan": { + "message": "이터스캔에서 보기" + }, + "visitWebSite": { + "message": "웹사이트 방문" }, "warning": { "message": "경고" }, + "welcomeBack": { + "message": "환영합니다!" + }, + "welcomeBeta": { + "message": "메타마스크 Beta에 오신 것을 환영합니다" + }, "whatsThis": { "message": "이것은 무엇인가요?" }, @@ -604,6 +980,204 @@ "message": "서명이 요청되고 있습니다." }, "youSign": { - "message": "서명 중입니다." + "message": "서명 중입니다" + }, + "yourPrivateSeedPhrase": { + "message": "개인 시드 구문" + }, + "accessingYourCamera": { + "message": "카메라 접근중..." + }, + "accountSelectionRequired": { + "message": "계정을 선택하셔야 합니다!" + }, + "approve": { + "message": "수락" + }, + "browserNotSupported": { + "message": "브라우저가 지원하지 않습니다..." + }, + "bytes": { + "message": "바이트" + }, + "chromeRequiredForHardwareWallets": { + "message": "하드웨어 지갑을 연결하기 위해서는 구글 크롬에서 메타마스크를 사용하셔야 합니다." + }, + "connectHardwareWallet": { + "message": "하드웨어 지갑 연결" + }, + "connect": { + "message": "연결" + }, + "connecting": { + "message": "연결중..." + }, + "connectToLedger": { + "message": "Ledger 연결" + }, + "connectToTrezor": { + "message": "Trezor 연결" + }, + "copyAddress": { + "message": "클립보드로 주소 복사" + }, + "downloadGoogleChrome": { + "message": "구글 크롬 다운로드" + }, + "dontHaveAHardwareWallet": { + "message": "하드웨어 지갑이 없나요?" + }, + "ensNameNotFound": { + "message": "ENS 이름을 찾을 수 없습니다" + }, + "parameters": { + "message": "매개변수" + }, + "forgetDevice": { + "message": "장치 연결 해제" + }, + "functionType": { + "message": "함수 유형" + }, + "getHelp": { + "message": "도움말" + }, + "hardware": { + "message": "하드웨어" + }, + "hardwareWalletConnected": { + "message": "하드웨어 지갑이 연결됨" + }, + "hardwareWallets": { + "message": "하드웨어 지갑 연결" + }, + "hardwareWalletsMsg": { + "message": "메타마스크에서 사용할 하드웨어 지갑을 선택해주세요" + }, + "havingTroubleConnecting": { + "message": "연결에 문제가 있나요?" + }, + "hexData": { + "message": "Hex 데이터" + }, + "invalidSeedPhrase": { + "message": "잘못된 시드 구문" + }, + "ledgerAccountRestriction": { + "message": "새 계정을 추가하려면 최소 마지막 계정을 사용해야 합니다." + }, + "menu": { + "message": "메뉴" + }, + "noConversionRateAvailable": { + "message": "변환 비율을 찾을 수 없습니다" + }, + "notFound": { + "message": "찾을 수 없음" + }, + "noWebcamFoundTitle": { + "message": "웹캠이 없습니다" + }, + "noWebcamFound": { + "message": "컴퓨터의 웹캠을 찾을 수 없습니다. 다시 시도해보세요." + }, + "openInTab": { + "message": "탭으로 열기" + }, + "origin": { + "message": "Origin" + }, + "prev": { + "message": "이전" + }, + "restoreAccountWithSeed": { + "message": "시드 구문으로 계정 복구하기" + }, + "restore": { + "message": "복구" + }, + "remove": { + "message": "제거" + }, + "removeAccount": { + "message": "계정 제거" + }, + "removeAccountDescription": { + "message": "이 계정한 지갑에서 삭제될 것입니다. 지우기 전에 이 계정에 대한 개인 키 혹은 시드 구문을 가지고 있는지 확인하세요. 계정 드랍다운 메뉴를 통해서 계정을 가져오거나 생성할 수 있습니다." + }, + "readyToConnect": { + "message": "접속 준비되었나요?" + }, + "separateEachWord": { + "message": "각 단어는 공백 한칸으로 분리합니다" + }, + "orderOneHere": { + "message": "Trezor 혹은 Ledger를 구입하고 자금을 콜드 스토리지에 저장합니다" + }, + "selectAnAddress": { + "message": "주소 선택" + }, + "selectAnAccount": { + "message": "계정 선택" + }, + "selectAnAccountHelp": { + "message": "메타마스크에서 보기위한 계정 선택" + }, + "selectHdPath": { + "message": "HD 경로 지정" + }, + "selectPathHelp": { + "message": "하단에서 Ledger지갑 계정을 찾지 못하겠으면 \"Legacy (MEW / MyCrypto)\" 경로로 바꿔보세요" + }, + "step1HardwareWallet": { + "message": "1. 하드웨어 지갑 연결" + }, + "step1HardwareWalletMsg": { + "message": "하드웨어 지갑을 컴퓨터에 연결해주세요." + }, + "step2HardwareWallet": { + "message": "2. 계정 선택" + }, + "step2HardwareWalletMsg": { + "message": "보고 싶은 계정을 선택합니다. 한번에 하나의 계정만 선택할 수 있습니다." + }, + "step3HardwareWallet": { + "message": "3. dApps을 사용하거나 다른 것을 합니다!" + }, + "step3HardwareWalletMsg": { + "message": "다른 이더리움 계정을 사용하듯 하드웨어 계정을 사용합니다. dApps을 로그인하거나, 이더를 보내거나, ERC20토큰 혹은 대체가능하지 않은 토큰 (예를 들어 CryptoKitties)을 사거나 저장하거나 합니다." + }, + "scanInstructions": { + "message": "QR 코드를 카메라 앞에 가져다 놓아주세요" + }, + "scanQrCode": { + "message": "QR 코드 스캔" + }, + "transfer": { + "message": "전송" + }, + "trezorHardwareWallet": { + "message": "TREZOR 하드웨어 지갑" + }, + "tryAgain": { + "message": "다시 시도하세요" + }, + "unknownFunction": { + "message": "알 수 없는 함수" + }, + "unknownQrCode": { + "message": "오류: QR 코드를 확인할 수 없습니다" + }, + "unknownCameraErrorTitle": { + "message": "이런! 뭔가 잘못되었습니다...." + }, + "unknownCameraError": { + "message": "카메라를 접근하는 중 오류가 발생했습니다. 다시 시도해 주세요..." + }, + "unlock": { + "message": "잠금 해제" + }, + "youNeedToAllowCameraAccess": { + "message": "이 기능을 사용하려면 카메라 접근을 허용해야 합니다." } } diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index bb722735d..6344e1beb 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -784,7 +784,7 @@ "message": "Тестовый кран" }, "to": { - "message": "Получатель: " + "message": "Получатель" }, "toETHviaShapeShift": { "message": "$1 в ETH через ShapeShift", diff --git a/app/_locales/tml/messages.json b/app/_locales/tml/messages.json index fcc418bac..4f733458e 100644 --- a/app/_locales/tml/messages.json +++ b/app/_locales/tml/messages.json @@ -796,7 +796,7 @@ "message": "சோதனை குழாய்" }, "to": { - "message": "பெறுநர்: " + "message": "பெறுநர்" }, "toETHviaShapeShift": { "message": "$ 1 முதல் ETH வரை வடிவம்", diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index 08ba6cde8..8be695108 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -796,7 +796,7 @@ "message": "Test Musluğu" }, "to": { - "message": "Kime: " + "message": "Kime" }, "toETHviaShapeShift": { "message": "ShapeShift üstünden $1'dan ETH'e", diff --git a/app/manifest.json b/app/manifest.json index 1e12f35b4..7793c6e38 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -71,7 +71,6 @@ "activeTab", "webRequest", "*://*.eth/", - "*://*.test/", "notifications" ], "web_accessible_resources": [ diff --git a/app/scripts/background.js b/app/scripts/background.js index 1599b8f99..e13d2842a 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -256,6 +256,7 @@ function setupController (initState, initLangCode) { showUnconfirmedMessage: triggerUi, unlockAccountMessage: triggerUi, showUnapprovedTx: triggerUi, + showWatchAssetUi: showWatchAssetUi, // initial state initState, // initial locale code @@ -451,9 +452,28 @@ function triggerUi () { }) } +/** + * Opens the browser popup for user confirmation of watchAsset + * then it waits until user interact with the UI + */ +function showWatchAssetUi () { + triggerUi() + return new Promise( + (resolve) => { + var interval = setInterval(() => { + if (!notificationIsOpen) { + clearInterval(interval) + resolve() + } + }, 1000) + } + ) +} + // On first install, open a window to MetaMask website to how-it-works. extension.runtime.onInstalled.addListener(function (details) { if ((details.reason === 'install') && (!METAMASK_DEBUG)) { extension.tabs.create({url: 'https://metamask.io/#how-it-works'}) } }) + diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 707fd7de9..464a37017 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -1,5 +1,6 @@ const ObservableStore = require('obs-store') const normalizeAddress = require('eth-sig-util').normalize +const { isValidAddress } = require('ethereumjs-util') const extend = require('xtend') @@ -14,6 +15,7 @@ class PreferencesController { * @property {string} store.currentAccountTab Indicates the selected tab in the ui * @property {array} store.tokens The tokens the user wants display in their token lists * @property {object} store.accountTokens The tokens stored per account and then per network type + * @property {object} store.assetImages Contains assets objects related to assets added * @property {boolean} store.useBlockie The users preference for blockie identicons within the UI * @property {object} store.featureFlags A key-boolean map, where keys refer to features and booleans to whether the * user wishes to see that feature @@ -26,7 +28,9 @@ class PreferencesController { frequentRpcList: [], currentAccountTab: 'history', accountTokens: {}, + assetImages: {}, tokens: [], + suggestedTokens: {}, useBlockie: false, featureFlags: {}, currentLocale: opts.initLangCode, @@ -37,6 +41,7 @@ class PreferencesController { this.diagnostics = opts.diagnostics this.network = opts.network this.store = new ObservableStore(initState) + this.showWatchAssetUi = opts.showWatchAssetUi this._subscribeProviderType() } // PUBLIC METHODS @@ -51,6 +56,53 @@ class PreferencesController { this.store.updateState({ useBlockie: val }) } + getSuggestedTokens () { + return this.store.getState().suggestedTokens + } + + getAssetImages () { + return this.store.getState().assetImages + } + + addSuggestedERC20Asset (tokenOpts) { + this._validateERC20AssetParams(tokenOpts) + const suggested = this.getSuggestedTokens() + const { rawAddress, symbol, decimals, image } = tokenOpts + const address = normalizeAddress(rawAddress) + const newEntry = { address, symbol, decimals, image } + suggested[address] = newEntry + this.store.updateState({ suggestedTokens: suggested }) + } + + /** + * RPC engine middleware for requesting new asset added + * + * @param req + * @param res + * @param {Function} - next + * @param {Function} - end + */ + async requestWatchAsset (req, res, next, end) { + if (req.method === 'metamask_watchAsset') { + const { type, options } = req.params + switch (type) { + case 'ERC20': + const result = await this._handleWatchAssetERC20(options) + if (result instanceof Error) { + end(result) + } else { + res.result = result + end() + } + break + default: + end(new Error(`Asset of type ${type} not supported`)) + } + } else { + next() + } + } + /** * Getter for the `useBlockie` property * @@ -186,6 +238,13 @@ class PreferencesController { return selected } + removeSuggestedTokens () { + return new Promise((resolve, reject) => { + this.store.updateState({ suggestedTokens: {} }) + resolve({}) + }) + } + /** * Setter for the `selectedAddress` property * @@ -232,11 +291,11 @@ class PreferencesController { * @returns {Promise<array>} Promises the new array of AddedToken objects. * */ - async addToken (rawAddress, symbol, decimals) { + async addToken (rawAddress, symbol, decimals, image) { const address = normalizeAddress(rawAddress) const newEntry = { address, symbol, decimals } - const tokens = this.store.getState().tokens + const assetImages = this.getAssetImages() const previousEntry = tokens.find((token, index) => { return token.address === address }) @@ -247,7 +306,8 @@ class PreferencesController { } else { tokens.push(newEntry) } - this._updateAccountTokens(tokens) + assetImages[address] = image + this._updateAccountTokens(tokens, assetImages) return Promise.resolve(tokens) } @@ -260,8 +320,10 @@ class PreferencesController { */ removeToken (rawAddress) { const tokens = this.store.getState().tokens + const assetImages = this.getAssetImages() const updatedTokens = tokens.filter(token => token.address !== rawAddress) - this._updateAccountTokens(updatedTokens) + delete assetImages[rawAddress] + this._updateAccountTokens(updatedTokens, assetImages) return Promise.resolve(updatedTokens) } @@ -322,7 +384,7 @@ class PreferencesController { /** * Returns an updated rpcList based on the passed url and the current list. - * The returned list will have a max length of 2. If the _url currently exists it the list, it will be moved to the + * The returned list will have a max length of 3. If the _url currently exists it the list, it will be moved to the * end of the list. The current list is modified and returned as a promise. * * @param {string} _url The rpc url to add to the frequentRpcList. @@ -338,7 +400,7 @@ class PreferencesController { if (_url !== 'http://localhost:8545') { rpcList.push(_url) } - if (rpcList.length > 2) { + if (rpcList.length > 3) { rpcList.shift() } return Promise.resolve(rpcList) @@ -387,6 +449,7 @@ class PreferencesController { // // PRIVATE METHODS // + /** * Subscription to network provider type. * @@ -405,10 +468,10 @@ class PreferencesController { * @param {array} tokens Array of tokens to be updated. * */ - _updateAccountTokens (tokens) { + _updateAccountTokens (tokens, assetImages) { const { accountTokens, providerType, selectedAddress } = this._getTokenRelatedStates() accountTokens[selectedAddress][providerType] = tokens - this.store.updateState({ accountTokens, tokens }) + this.store.updateState({ accountTokens, tokens, assetImages }) } /** @@ -438,6 +501,47 @@ class PreferencesController { const tokens = accountTokens[selectedAddress][providerType] return { tokens, accountTokens, providerType, selectedAddress } } + + /** + * Handle the suggestion of an ERC20 asset through `watchAsset` + * * + * @param {Promise} promise Promise according to addition of ERC20 token + * + */ + async _handleWatchAssetERC20 (options) { + const { address, symbol, decimals, image } = options + const rawAddress = address + try { + this._validateERC20AssetParams({ rawAddress, symbol, decimals }) + } catch (err) { + return err + } + const tokenOpts = { rawAddress, decimals, symbol, image } + this.addSuggestedERC20Asset(tokenOpts) + return this.showWatchAssetUi().then(() => { + const tokenAddresses = this.getTokens().filter(token => token.address === normalizeAddress(rawAddress)) + return tokenAddresses.length > 0 + }) + } + + /** + * Validates that the passed options for suggested token have all required properties. + * + * @param {Object} opts The options object to validate + * @throws {string} Throw a custom error indicating that address, symbol and/or decimals + * doesn't fulfill requirements + * + */ + _validateERC20AssetParams (opts) { + const { rawAddress, symbol, decimals } = opts + if (!rawAddress || !symbol || !decimals) throw new Error(`Cannot suggest token without address, symbol, and decimals`) + if (!(symbol.length < 6)) throw new Error(`Invalid symbol ${symbol} more than five characters`) + const numDecimals = parseInt(decimals, 10) + if (isNaN(numDecimals) || numDecimals > 36 || numDecimals < 0) { + throw new Error(`Invalid decimals ${decimals} must be at least 0, and not over 36`) + } + if (!isValidAddress(rawAddress)) throw new Error(`Invalid address ${rawAddress}`) + } } module.exports = PreferencesController diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js index b7e2c7cbe..3a52d5e8d 100644 --- a/app/scripts/lib/account-tracker.js +++ b/app/scripts/lib/account-tracker.js @@ -43,10 +43,24 @@ class AccountTracker { this._provider = opts.provider this._query = pify(new EthQuery(this._provider)) this._blockTracker = opts.blockTracker - // subscribe to latest block - this._blockTracker.on('latest', this._updateForBlock.bind(this)) // blockTracker.currentBlock may be null this._currentBlockNumber = this._blockTracker.getCurrentBlock() + // bind function for easier listener syntax + this._updateForBlock = this._updateForBlock.bind(this) + } + + start () { + // remove first to avoid double add + this._blockTracker.removeListener('latest', this._updateForBlock) + // add listener + this._blockTracker.addListener('latest', this._updateForBlock) + // fetch account balances + this._updateAccounts() + } + + stop () { + // remove listener + this._blockTracker.removeListener('latest', this._updateForBlock) } /** diff --git a/app/scripts/lib/ipfsContent.js b/app/scripts/lib/ipfsContent.js index 5db63f47d..38682b916 100644 --- a/app/scripts/lib/ipfsContent.js +++ b/app/scripts/lib/ipfsContent.js @@ -34,7 +34,7 @@ module.exports = function (provider) { return { cancel: true } } - extension.webRequest.onErrorOccurred.addListener(ipfsContent, {urls: ['*://*.eth/', '*://*.test/']}) + extension.webRequest.onErrorOccurred.addListener(ipfsContent, {urls: ['*://*.eth/']}) return { remove () { diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 29838ad2d..98cb62bfa 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -67,6 +67,10 @@ module.exports = class MetamaskController extends EventEmitter { const initState = opts.initState || {} this.recordFirstTimeInfo(initState) + // this keeps track of how many "controllerStream" connections are open + // the only thing that uses controller connections are open metamask UI instances + this.activeControllerConnections = 0 + // platform-specific api this.platform = opts.platform @@ -88,6 +92,7 @@ module.exports = class MetamaskController extends EventEmitter { this.preferencesController = new PreferencesController({ initState: initState.PreferencesController, initLangCode: opts.initLangCode, + showWatchAssetUi: opts.showWatchAssetUi, network: this.networkController, }) @@ -127,6 +132,14 @@ module.exports = class MetamaskController extends EventEmitter { provider: this.provider, blockTracker: this.blockTracker, }) + // start and stop polling for balances based on activeControllerConnections + this.on('controllerConnectionChanged', (activeControllerConnections) => { + if (activeControllerConnections > 0) { + this.accountTracker.start() + } else { + this.accountTracker.stop() + } + }) // key mgmt const additionalKeyrings = [TrezorKeyring, LedgerBridgeKeyring] @@ -137,19 +150,7 @@ module.exports = class MetamaskController extends EventEmitter { encryptor: opts.encryptor || undefined, }) - // If only one account exists, make sure it is selected. - this.keyringController.memStore.subscribe((state) => { - const addresses = state.keyrings.reduce((res, keyring) => { - return res.concat(keyring.accounts) - }, []) - if (addresses.length === 1) { - const address = addresses[0] - this.preferencesController.setSelectedAddress(address) - } - // ensure preferences + identities controller know about all addresses - this.preferencesController.addAddresses(addresses) - this.accountTracker.syncWithAddresses(addresses) - }) + this.keyringController.memStore.subscribe((s) => this._onKeyringControllerUpdate(s)) // detect tokens controller this.detectTokensController = new DetectTokensController({ @@ -386,6 +387,7 @@ module.exports = class MetamaskController extends EventEmitter { setSelectedAddress: nodeify(preferencesController.setSelectedAddress, preferencesController), addToken: nodeify(preferencesController.addToken, preferencesController), removeToken: nodeify(preferencesController.removeToken, preferencesController), + removeSuggestedTokens: nodeify(preferencesController.removeSuggestedTokens, preferencesController), setCurrentAccountTab: nodeify(preferencesController.setCurrentAccountTab, preferencesController), setAccountLabel: nodeify(preferencesController.setAccountLabel, preferencesController), setFeatureFlag: nodeify(preferencesController.setFeatureFlag, preferencesController), @@ -1209,11 +1211,19 @@ module.exports = class MetamaskController extends EventEmitter { setupControllerConnection (outStream) { const api = this.getApi() const dnode = Dnode(api) + // report new active controller connection + this.activeControllerConnections++ + this.emit('controllerConnectionChanged', this.activeControllerConnections) + // connect dnode api to remote connection pump( outStream, dnode, outStream, (err) => { + // report new active controller connection + this.activeControllerConnections-- + this.emit('controllerConnectionChanged', this.activeControllerConnections) + // report any error if (err) log.error(err) } ) @@ -1242,6 +1252,7 @@ module.exports = class MetamaskController extends EventEmitter { engine.push(createOriginMiddleware({ origin })) engine.push(createLoggerMiddleware({ origin })) engine.push(filterMiddleware) + engine.push(this.preferencesController.requestWatchAsset.bind(this.preferencesController)) engine.push(createProviderMiddleware({ provider: this.provider })) // setup connection @@ -1279,6 +1290,34 @@ module.exports = class MetamaskController extends EventEmitter { } /** + * Handle a KeyringController update + * @param {object} state the KC state + * @return {Promise<void>} + * @private + */ + async _onKeyringControllerUpdate (state) { + const {isUnlocked, keyrings} = state + const addresses = keyrings.reduce((acc, {accounts}) => acc.concat(accounts), []) + + if (!addresses.length) { + return + } + + // Ensure preferences + identities controller know about all addresses + this.preferencesController.addAddresses(addresses) + this.accountTracker.syncWithAddresses(addresses) + + const wasLocked = !isUnlocked + if (wasLocked) { + const oldSelectedAddress = this.preferencesController.getSelectedAddress() + if (!addresses.includes(oldSelectedAddress)) { + const address = addresses[0] + await this.preferencesController.setSelectedAddress(address) + } + } + } + + /** * A method for emitting the full MetaMask state to all registered listeners. * @private */ @@ -1424,6 +1463,7 @@ module.exports = class MetamaskController extends EventEmitter { } } + // TODO: Replace isClientOpen methods with `controllerConnectionChanged` events. /** * A method for recording whether the MetaMask user interface is open or not. * @private |