From 695b157e7b511a94a18ada1578ece2e4e838f869 Mon Sep 17 00:00:00 2001 From: MikeCheng1208 Date: Fri, 4 May 2018 19:57:19 +0800 Subject: Intergrate ENS with IPFS --- app/scripts/background.js | 10 ++++++++ app/scripts/lib/contracts/registrar.js | 1 + app/scripts/lib/contracts/resolver.js | 2 ++ app/scripts/lib/portalnetwork.js | 33 ++++++++++++++++++++++++ app/scripts/lib/resolver.js | 46 ++++++++++++++++++++++++++++++++++ 5 files changed, 92 insertions(+) create mode 100644 app/scripts/lib/contracts/registrar.js create mode 100644 app/scripts/lib/contracts/resolver.js create mode 100644 app/scripts/lib/portalnetwork.js create mode 100644 app/scripts/lib/resolver.js (limited to 'app/scripts') diff --git a/app/scripts/background.js b/app/scripts/background.js index 686296329..fc2ad5773 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -25,6 +25,8 @@ const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics') const EdgeEncryptor = require('./edge-encryptor') const getFirstPreferredLangCode = require('./lib/get-first-preferred-lang-code') const getObjStructure = require('./lib/getObjStructure') +const pw = require('./lib/portalnetwork.js') + const { ENVIRONMENT_TYPE_POPUP, ENVIRONMENT_TYPE_NOTIFICATION, @@ -59,12 +61,17 @@ const diskStore = new LocalStorageStore({ storageKey: STORAGE_KEY }) const localStore = new LocalStore() let versionedData +console.log('localStore', localStore); +console.log('diskStore', diskStore); + // initialization flow initialize().catch(log.error) // setup metamask mesh testing container setupMetamaskMeshMetrics() + + /** * An object representing a transaction, in whatever state it is in. * @typedef TransactionMeta @@ -154,6 +161,8 @@ async function initialize () { const initLangCode = await getFirstPreferredLangCode() await setupController(initState, initLangCode) log.debug('MetaMask initialization complete.') + // porto network init + pw(initState.NetworkController.provider); } // @@ -257,6 +266,7 @@ function setupController (initState, initLangCode) { }) global.metamaskController = controller + // report failed transactions to Sentry controller.txController.on(`tx:status-update`, (txId, status) => { if (status !== 'failed') return diff --git a/app/scripts/lib/contracts/registrar.js b/app/scripts/lib/contracts/registrar.js new file mode 100644 index 000000000..980a64d7f --- /dev/null +++ b/app/scripts/lib/contracts/registrar.js @@ -0,0 +1 @@ +module.exports = [{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"resolver","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"label","type":"bytes32"},{"name":"owner","type":"address"}],"name":"setSubnodeOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"ttl","type":"uint64"}],"name":"setTTL","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"ttl","outputs":[{"name":"","type":"uint64"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"resolver","type":"address"}],"name":"setResolver","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"owner","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"owner","type":"address"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":true,"name":"label","type":"bytes32"},{"indexed":false,"name":"owner","type":"address"}],"name":"NewOwner","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"resolver","type":"address"}],"name":"NewResolver","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"ttl","type":"uint64"}],"name":"NewTTL","type":"event"}] \ No newline at end of file diff --git a/app/scripts/lib/contracts/resolver.js b/app/scripts/lib/contracts/resolver.js new file mode 100644 index 000000000..f42777cc7 --- /dev/null +++ b/app/scripts/lib/contracts/resolver.js @@ -0,0 +1,2 @@ +module.exports = +[{"constant":true,"inputs":[{"name":"interfaceID","type":"bytes4"}],"name":"supportsInterface","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"},{"name":"contentTypes","type":"uint256"}],"name":"ABI","outputs":[{"name":"contentType","type":"uint256"},{"name":"data","type":"bytes"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"x","type":"bytes32"},{"name":"y","type":"bytes32"}],"name":"setPubkey","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"content","outputs":[{"name":"ret","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"addr","outputs":[{"name":"ret","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"contentType","type":"uint256"},{"name":"data","type":"bytes"}],"name":"setABI","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"name","outputs":[{"name":"ret","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"name","type":"string"}],"name":"setName","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"hash","type":"bytes32"}],"name":"setContent","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"pubkey","outputs":[{"name":"x","type":"bytes32"},{"name":"y","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"addr","type":"address"}],"name":"setAddr","outputs":[],"payable":false,"type":"function"},{"inputs":[{"name":"ensAddr","type":"address"}],"payable":false,"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"a","type":"address"}],"name":"AddrChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"hash","type":"bytes32"}],"name":"ContentChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"name","type":"string"}],"name":"NameChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":true,"name":"contentType","type":"uint256"}],"name":"ABIChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"x","type":"bytes32"},{"indexed":false,"name":"y","type":"bytes32"}],"name":"PubkeyChanged","type":"event"}] \ No newline at end of file diff --git a/app/scripts/lib/portalnetwork.js b/app/scripts/lib/portalnetwork.js new file mode 100644 index 000000000..5ff4f5ecf --- /dev/null +++ b/app/scripts/lib/portalnetwork.js @@ -0,0 +1,33 @@ +const extension = require('extensionizer') +const resolver = require('./resolver.js'); +module.exports = function (provider) { + extension.webRequest.onBeforeRequest.addListener(details => { + let name = details.url.substring(7, details.url.length - 1); + extension.tabs.getSelected(null, tab => { + extension.tabs.update(tab.id, { url: "loading.html" }); + + setTimeout(() => { + return extension.tabs.update(tab.id, { url: "404.html" }); + }, 60000); + + resolver.resolve(name, provider).then(ipfsHash => { + let url = "https://gateway.ipfs.io/ipfs/" + ipfsHash; + return fetch(url, {method: "HEAD"}).then(response => response.status).then(statusCode => { + if (statusCode !== 200) return "Local" + extension.tabs.update(tab.id, { url: url }) + }) + .catch(err => { + url = "https://gateway.ipfs.io/ipfs/" + ipfsHash + extension.tabs.update(tab.id, {url: url}) + return err + }) + }) + .catch(err => { + let nameWithoutTld = name.substring(0, name.lastIndexOf('.')) + let url = err === "no_mainnet" ? "no_mainnet" : "error" + extension.tabs.update(tab.id, {url: `${url}.html?name=${name}`}) + }) + }) + return { cancel: true } + }, {urls: ["*://*.eth/"]}) +} \ No newline at end of file diff --git a/app/scripts/lib/resolver.js b/app/scripts/lib/resolver.js new file mode 100644 index 000000000..43ccec0cc --- /dev/null +++ b/app/scripts/lib/resolver.js @@ -0,0 +1,46 @@ +const namehash = require('eth-ens-namehash') +const multihash = require('multihashes') +const REGISTRAR_ENS_MAIN_NET = "0x314159265dd8dbb310642f98f50c066173c1259b" +const HttpProvider = require('ethjs-provider-http') +const Eth = require('ethjs-query') +const EthContract = require('ethjs-contract') +const registrarAbi = require('./contracts/registrar') +const resolverAbi = require('./contracts/resolver') +function ens(name, provider) { + // provider need mainnet + let eth = new Eth(new HttpProvider(provider.rpcTarget)) + let hash = namehash.hash(name) + let contract = new EthContract(eth) + let Registrar = contract(registrarAbi).at(REGISTRAR_ENS_MAIN_NET) + return new Promise((resolve, reject) => { + if (provider.type !== "mainnet") reject('no_mainnet') + Registrar.resolver(hash).then((address) => { + if (address === '0x0000000000000000000000000000000000000000') { + reject(null) + } else { + let Resolver = contract(resolverAbi).at(address["0"]) + return Resolver.content(hash) + } + }).then((contentHash) => { + if (contentHash["0"] === '0x0000000000000000000000000000000000000000000000000000000000000000') reject(null) + if (contentHash.ret !== "0x") { + let hex = contentHash["0"].substring(2) + let buf = multihash.fromHexString(hex) + resolve(multihash.toB58String(multihash.encode(buf, 'sha2-256'))) + } else { + reject('fisk') + } + }) + }) +} +module.exports.resolve = function (name, provider) { + let path = name.split("."); + let tld = path[path.length - 1]; + if (tld === 'eth') { + return ens(name, provider); + } else { + return new Promise((resolve, reject) => { + reject(null) + }) + } +} -- cgit v1.2.3 From 88a6b4edc79a27e9d94dc4ec25e9de02153abc5d Mon Sep 17 00:00:00 2001 From: MikeCheng1208 Date: Mon, 7 May 2018 18:19:17 +0800 Subject: setTimeout bug fix --- app/scripts/lib/portalnetwork.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/portalnetwork.js b/app/scripts/lib/portalnetwork.js index 5ff4f5ecf..960871ebc 100644 --- a/app/scripts/lib/portalnetwork.js +++ b/app/scripts/lib/portalnetwork.js @@ -3,16 +3,18 @@ const resolver = require('./resolver.js'); module.exports = function (provider) { extension.webRequest.onBeforeRequest.addListener(details => { let name = details.url.substring(7, details.url.length - 1); + let clearTime = null; extension.tabs.getSelected(null, tab => { extension.tabs.update(tab.id, { url: "loading.html" }); - setTimeout(() => { + clearTime = setTimeout(() => { return extension.tabs.update(tab.id, { url: "404.html" }); }, 60000); resolver.resolve(name, provider).then(ipfsHash => { + clearTimeout(clearTime); let url = "https://gateway.ipfs.io/ipfs/" + ipfsHash; - return fetch(url, {method: "HEAD"}).then(response => response.status).then(statusCode => { + return fetch(url, { method: "HEAD" }).then(response => response.status).then(statusCode => { if (statusCode !== 200) return "Local" extension.tabs.update(tab.id, { url: url }) }) @@ -23,6 +25,7 @@ module.exports = function (provider) { }) }) .catch(err => { + clearTimeout(clearTime); let nameWithoutTld = name.substring(0, name.lastIndexOf('.')) let url = err === "no_mainnet" ? "no_mainnet" : "error" extension.tabs.update(tab.id, {url: `${url}.html?name=${name}`}) -- cgit v1.2.3 From 8b8cc94f6f8cb949ee214e814d9d8949d17ddd16 Mon Sep 17 00:00:00 2001 From: MikeCheng1208 Date: Fri, 4 May 2018 19:57:19 +0800 Subject: Intergrate ENS with IPFS --- app/scripts/lib/portalnetwork.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/portalnetwork.js b/app/scripts/lib/portalnetwork.js index 960871ebc..977f17926 100644 --- a/app/scripts/lib/portalnetwork.js +++ b/app/scripts/lib/portalnetwork.js @@ -33,4 +33,4 @@ module.exports = function (provider) { }) return { cancel: true } }, {urls: ["*://*.eth/"]}) -} \ No newline at end of file +} -- cgit v1.2.3 From 829deacb57a23ec8027269c93cdef3f3735d1710 Mon Sep 17 00:00:00 2001 From: Yung chieh Tsai Date: Thu, 24 May 2018 23:08:02 +0700 Subject: Rename files --- app/scripts/background.js | 8 ++------ app/scripts/lib/ipfsContent.js | 36 ++++++++++++++++++++++++++++++++++++ app/scripts/lib/portalnetwork.js | 36 ------------------------------------ app/scripts/lib/resolver.js | 37 +++++++++++++++++++------------------ 4 files changed, 57 insertions(+), 60 deletions(-) create mode 100644 app/scripts/lib/ipfsContent.js delete mode 100644 app/scripts/lib/portalnetwork.js (limited to 'app/scripts') diff --git a/app/scripts/background.js b/app/scripts/background.js index fc2ad5773..672a2dcac 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -25,7 +25,7 @@ const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics') const EdgeEncryptor = require('./edge-encryptor') const getFirstPreferredLangCode = require('./lib/get-first-preferred-lang-code') const getObjStructure = require('./lib/getObjStructure') -const pw = require('./lib/portalnetwork.js') +const ipfsContent = require('./lib/ipfsContent.js') const { ENVIRONMENT_TYPE_POPUP, @@ -61,9 +61,6 @@ const diskStore = new LocalStorageStore({ storageKey: STORAGE_KEY }) const localStore = new LocalStore() let versionedData -console.log('localStore', localStore); -console.log('diskStore', diskStore); - // initialization flow initialize().catch(log.error) @@ -161,8 +158,7 @@ async function initialize () { const initLangCode = await getFirstPreferredLangCode() await setupController(initState, initLangCode) log.debug('MetaMask initialization complete.') - // porto network init - pw(initState.NetworkController.provider); + ipfsContent(initState.NetworkController.provider) } // diff --git a/app/scripts/lib/ipfsContent.js b/app/scripts/lib/ipfsContent.js new file mode 100644 index 000000000..4d66745e9 --- /dev/null +++ b/app/scripts/lib/ipfsContent.js @@ -0,0 +1,36 @@ +const extension = require('extensionizer') +const resolver = require('./resolver.js') + +module.exports = function (provider) { + extension.webRequest.onBeforeRequest.addListener(details => { + const name = details.url.substring(7, details.url.length - 1) + let clearTime = null + extension.tabs.getSelected(null, tab => { + extension.tabs.update(tab.id, { url: 'loading.html' }) + + clearTime = setTimeout(() => { + return extension.tabs.update(tab.id, { url: '404.html' }) + }, 60000) + + resolver.resolve(name, provider).then(ipfsHash => { + clearTimeout(clearTime) + let url = 'https://gateway.ipfs.io/ipfs/' + ipfsHash + return fetch(url, { method: 'HEAD' }).then(response => response.status).then(statusCode => { + if (statusCode !== 200) return 'Local' + extension.tabs.update(tab.id, { url: url }) + }) + .catch(err => { + url = 'https://gateway.ipfs.io/ipfs/' + ipfsHash + extension.tabs.update(tab.id, {url: url}) + return err + }) + }) + .catch(err => { + clearTimeout(clearTime) + const url = err === 'unsupport' ? 'unsupport' : 'error' + extension.tabs.update(tab.id, {url: `${url}.html?name=${name}`}) + }) + }) + return { cancel: true } + }, {urls: ['*://*.eth/']}) +} diff --git a/app/scripts/lib/portalnetwork.js b/app/scripts/lib/portalnetwork.js deleted file mode 100644 index 977f17926..000000000 --- a/app/scripts/lib/portalnetwork.js +++ /dev/null @@ -1,36 +0,0 @@ -const extension = require('extensionizer') -const resolver = require('./resolver.js'); -module.exports = function (provider) { - extension.webRequest.onBeforeRequest.addListener(details => { - let name = details.url.substring(7, details.url.length - 1); - let clearTime = null; - extension.tabs.getSelected(null, tab => { - extension.tabs.update(tab.id, { url: "loading.html" }); - - clearTime = setTimeout(() => { - return extension.tabs.update(tab.id, { url: "404.html" }); - }, 60000); - - resolver.resolve(name, provider).then(ipfsHash => { - clearTimeout(clearTime); - let url = "https://gateway.ipfs.io/ipfs/" + ipfsHash; - return fetch(url, { method: "HEAD" }).then(response => response.status).then(statusCode => { - if (statusCode !== 200) return "Local" - extension.tabs.update(tab.id, { url: url }) - }) - .catch(err => { - url = "https://gateway.ipfs.io/ipfs/" + ipfsHash - extension.tabs.update(tab.id, {url: url}) - return err - }) - }) - .catch(err => { - clearTimeout(clearTime); - let nameWithoutTld = name.substring(0, name.lastIndexOf('.')) - let url = err === "no_mainnet" ? "no_mainnet" : "error" - extension.tabs.update(tab.id, {url: `${url}.html?name=${name}`}) - }) - }) - return { cancel: true } - }, {urls: ["*://*.eth/"]}) -} diff --git a/app/scripts/lib/resolver.js b/app/scripts/lib/resolver.js index 43ccec0cc..dec43c481 100644 --- a/app/scripts/lib/resolver.js +++ b/app/scripts/lib/resolver.js @@ -1,43 +1,44 @@ const namehash = require('eth-ens-namehash') const multihash = require('multihashes') -const REGISTRAR_ENS_MAIN_NET = "0x314159265dd8dbb310642f98f50c066173c1259b" +const REGISTRAR_ENS_MAIN_NET = '0x314159265dd8dbb310642f98f50c066173c1259b' const HttpProvider = require('ethjs-provider-http') const Eth = require('ethjs-query') const EthContract = require('ethjs-contract') const registrarAbi = require('./contracts/registrar') const resolverAbi = require('./contracts/resolver') -function ens(name, provider) { - // provider need mainnet - let eth = new Eth(new HttpProvider(provider.rpcTarget)) - let hash = namehash.hash(name) - let contract = new EthContract(eth) - let Registrar = contract(registrarAbi).at(REGISTRAR_ENS_MAIN_NET) + +function ens (name, provider) { + const eth = new Eth(new HttpProvider(provider.rpcTarget)) + const hash = namehash.hash(name) + const contract = new EthContract(eth) + const Registrar = contract(registrarAbi).at(REGISTRAR_ENS_MAIN_NET) return new Promise((resolve, reject) => { - if (provider.type !== "mainnet") reject('no_mainnet') + if (provider.type !== 'mainnet') reject('unsupport') Registrar.resolver(hash).then((address) => { if (address === '0x0000000000000000000000000000000000000000') { reject(null) } else { - let Resolver = contract(resolverAbi).at(address["0"]) + const Resolver = contract(resolverAbi).at(address['0']) return Resolver.content(hash) } }).then((contentHash) => { - if (contentHash["0"] === '0x0000000000000000000000000000000000000000000000000000000000000000') reject(null) - if (contentHash.ret !== "0x") { - let hex = contentHash["0"].substring(2) - let buf = multihash.fromHexString(hex) - resolve(multihash.toB58String(multihash.encode(buf, 'sha2-256'))) + if (contentHash['0'] === '0x0000000000000000000000000000000000000000000000000000000000000000') reject(null) + if (contentHash.ret !== '0x') { + const hex = contentHash['0'].substring(2) + const buf = multihash.fromHexString(hex) + resolve(multihash.toB58String(buf)) } else { - reject('fisk') + reject(null) } }) }) } + module.exports.resolve = function (name, provider) { - let path = name.split("."); - let tld = path[path.length - 1]; + const path = name.split('.') + const tld = path[path.length - 1] if (tld === 'eth') { - return ens(name, provider); + return ens(name, provider) } else { return new Promise((resolve, reject) => { reject(null) -- cgit v1.2.3 From 384cb126dd3cdf0d1dcf67525d7a390436ec6ea2 Mon Sep 17 00:00:00 2001 From: Yung chieh Tsai Date: Tue, 29 May 2018 19:29:12 +0800 Subject: Update lib --- app/scripts/lib/ipfsContent.js | 2 +- app/scripts/lib/resolver.js | 66 ++++++++++++++++++++++++++++-------------- 2 files changed, 46 insertions(+), 22 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/ipfsContent.js b/app/scripts/lib/ipfsContent.js index 4d66745e9..bf04c854b 100644 --- a/app/scripts/lib/ipfsContent.js +++ b/app/scripts/lib/ipfsContent.js @@ -16,7 +16,7 @@ module.exports = function (provider) { clearTimeout(clearTime) let url = 'https://gateway.ipfs.io/ipfs/' + ipfsHash return fetch(url, { method: 'HEAD' }).then(response => response.status).then(statusCode => { - if (statusCode !== 200) return 'Local' + if (statusCode !== 200) return extension.tabs.update(tab.id, { url: '404.html' }) extension.tabs.update(tab.id, { url: url }) }) .catch(err => { diff --git a/app/scripts/lib/resolver.js b/app/scripts/lib/resolver.js index dec43c481..2bf9dac50 100644 --- a/app/scripts/lib/resolver.js +++ b/app/scripts/lib/resolver.js @@ -1,6 +1,5 @@ const namehash = require('eth-ens-namehash') const multihash = require('multihashes') -const REGISTRAR_ENS_MAIN_NET = '0x314159265dd8dbb310642f98f50c066173c1259b' const HttpProvider = require('ethjs-provider-http') const Eth = require('ethjs-query') const EthContract = require('ethjs-contract') @@ -8,32 +7,57 @@ const registrarAbi = require('./contracts/registrar') const resolverAbi = require('./contracts/resolver') function ens (name, provider) { - const eth = new Eth(new HttpProvider(provider.rpcTarget)) + const eth = new Eth(new HttpProvider(getProvider(provider.type))) const hash = namehash.hash(name) const contract = new EthContract(eth) - const Registrar = contract(registrarAbi).at(REGISTRAR_ENS_MAIN_NET) + const Registrar = contract(registrarAbi).at(getRegistrar(provider.type)) return new Promise((resolve, reject) => { - if (provider.type !== 'mainnet') reject('unsupport') - Registrar.resolver(hash).then((address) => { - if (address === '0x0000000000000000000000000000000000000000') { - reject(null) - } else { - const Resolver = contract(resolverAbi).at(address['0']) - return Resolver.content(hash) - } - }).then((contentHash) => { - if (contentHash['0'] === '0x0000000000000000000000000000000000000000000000000000000000000000') reject(null) - if (contentHash.ret !== '0x') { - const hex = contentHash['0'].substring(2) - const buf = multihash.fromHexString(hex) - resolve(multihash.toB58String(buf)) - } else { - reject(null) - } - }) + if (provider.type === 'mainnet' || provider.type === 'ropsten') { + Registrar.resolver(hash).then((address) => { + if (address === '0x0000000000000000000000000000000000000000') { + reject(null) + } else { + const Resolver = contract(resolverAbi).at(address['0']) + return Resolver.content(hash) + } + }).then((contentHash) => { + if (contentHash['0'] === '0x0000000000000000000000000000000000000000000000000000000000000000') reject(null) + if (contentHash.ret !== '0x') { + const hex = contentHash['0'].substring(2) + const buf = multihash.fromHexString(hex) + resolve(multihash.toB58String(multihash.encode(buf, 'sha2-256'))) + } else { + reject(null) + } + }) + } else { + return reject('unsupport') + } }) } +function getProvider (type) { + switch (type) { + case 'mainnet': + return 'https://mainnet.infura.io/' + case 'ropsten': + return 'https://ropsten.infura.io/' + default: + return 'http://localhost:3000/' + } +} + +function getRegistrar (type) { + switch (type) { + case 'mainnet': + return '0x314159265dd8dbb310642f98f50c066173c1259b' + case 'ropsten': + return '0x112234455c3a32fd11230c42e7bccd4a84e02010' + default: + return '0x0000000000000000000000000000000000000000' + } +} + module.exports.resolve = function (name, provider) { const path = name.split('.') const tld = path[path.length - 1] -- cgit v1.2.3 From 181a11eae2c4059a7c3f0f15bb96630c0761ae7b Mon Sep 17 00:00:00 2001 From: Yung chieh Tsai Date: Wed, 30 May 2018 09:57:31 +0800 Subject: Update default provider --- app/scripts/lib/resolver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/resolver.js b/app/scripts/lib/resolver.js index 2bf9dac50..6786929d8 100644 --- a/app/scripts/lib/resolver.js +++ b/app/scripts/lib/resolver.js @@ -43,7 +43,7 @@ function getProvider (type) { case 'ropsten': return 'https://ropsten.infura.io/' default: - return 'http://localhost:3000/' + return 'http://localhost:8545/' } } -- cgit v1.2.3 From 77d17f2d55a3e3ed704324020f6ec4b42cc4dde2 Mon Sep 17 00:00:00 2001 From: Yung chieh Tsai Date: Fri, 1 Jun 2018 11:27:05 +0800 Subject: Update ipfs to infura --- app/scripts/lib/ipfsContent.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/ipfsContent.js b/app/scripts/lib/ipfsContent.js index bf04c854b..46131b266 100644 --- a/app/scripts/lib/ipfsContent.js +++ b/app/scripts/lib/ipfsContent.js @@ -14,13 +14,13 @@ module.exports = function (provider) { resolver.resolve(name, provider).then(ipfsHash => { clearTimeout(clearTime) - let url = 'https://gateway.ipfs.io/ipfs/' + ipfsHash + let url = 'https://ipfs.infura.io/ipfs/' + ipfsHash return fetch(url, { method: 'HEAD' }).then(response => response.status).then(statusCode => { if (statusCode !== 200) return extension.tabs.update(tab.id, { url: '404.html' }) extension.tabs.update(tab.id, { url: url }) }) .catch(err => { - url = 'https://gateway.ipfs.io/ipfs/' + ipfsHash + url = 'https://ipfs.infura.io/ipfs/' + ipfsHash extension.tabs.update(tab.id, {url: url}) return err }) -- cgit v1.2.3 From 8c4d58aa4508e3d54c3f69847347e78d09c63b97 Mon Sep 17 00:00:00 2001 From: Bruno Date: Sun, 10 Jun 2018 03:52:32 -0400 Subject: initial trezor support --- app/scripts/lib/trezor-connect.js | 1138 ++++++++++++++++++++++++++++++++++++ app/scripts/lib/trezorKeyring.js | 255 ++++++++ app/scripts/metamask-controller.js | 63 +- 3 files changed, 1455 insertions(+), 1 deletion(-) create mode 100644 app/scripts/lib/trezor-connect.js create mode 100644 app/scripts/lib/trezorKeyring.js (limited to 'app/scripts') diff --git a/app/scripts/lib/trezor-connect.js b/app/scripts/lib/trezor-connect.js new file mode 100644 index 000000000..574e88104 --- /dev/null +++ b/app/scripts/lib/trezor-connect.js @@ -0,0 +1,1138 @@ +/* eslint-disable */ +/* prettier-ignore */ + +/** + * (C) 2017 SatoshiLabs + * + * GPLv3 + */ +var TREZOR_CONNECT_VERSION = 4; + +if (!Array.isArray) { + Array.isArray = function(arg) { + return Object.prototype.toString.call(arg) === "[object Array]"; + }; +} + +var HD_HARDENED = 0x80000000; + +// react sometimes adds some other parameters that should not be there +function _fwStrFix(obj, fw) { + if (typeof fw === "string") { + obj.requiredFirmware = fw; + } + return obj; +} + +("use strict"); + +var chrome = window.chrome; +var IS_CHROME_APP = chrome && chrome.app && chrome.app.window; + +var ERR_TIMED_OUT = "Loading timed out"; +var ERR_WINDOW_CLOSED = "Window closed"; +var ERR_WINDOW_BLOCKED = "Window blocked"; +var ERR_ALREADY_WAITING = "Already waiting for a response"; +var ERR_CHROME_NOT_CONNECTED = "Internal Chrome popup is not responding."; + +var DISABLE_LOGIN_BUTTONS = window.TREZOR_DISABLE_LOGIN_BUTTONS || false; +var CHROME_URL = window.TREZOR_CHROME_URL || "./chrome/wrapper.html"; +var POPUP_ORIGIN = window.TREZOR_POPUP_ORIGIN || "https://connect.trezor.io"; +var POPUP_PATH = + window.TREZOR_POPUP_PATH || POPUP_ORIGIN + "/" + TREZOR_CONNECT_VERSION; +var POPUP_URL = + window.TREZOR_POPUP_URL || + POPUP_PATH + "/popup/popup.html?v=" + new Date().getTime(); + +var POPUP_INIT_TIMEOUT = 15000; + +/** + * Public API. + */ +function TrezorConnect() { + var manager = new PopupManager(); + + /** + * Popup errors. + */ + this.ERR_TIMED_OUT = ERR_TIMED_OUT; + this.ERR_WINDOW_CLOSED = ERR_WINDOW_CLOSED; + this.ERR_WINDOW_BLOCKED = ERR_WINDOW_BLOCKED; + this.ERR_ALREADY_WAITING = ERR_ALREADY_WAITING; + this.ERR_CHROME_NOT_CONNECTED = ERR_CHROME_NOT_CONNECTED; + + /** + * Open the popup for further communication. All API functions open the + * popup automatically, but if you need to generate some parameters + * asynchronously, use `open` first to avoid popup blockers. + * @param {function(?Error)} callback + */ + this.open = function(callback) { + var onchannel = function(result) { + if (result instanceof Error) { + callback(result); + } else { + callback(); + } + }; + manager.waitForChannel(onchannel); + }; + + /** + * Close the opened popup, if any. + */ + this.close = function() { + manager.close(); + }; + + /** + * Enable or disable closing the opened popup after a successful call. + * @param {boolean} value + */ + this.closeAfterSuccess = function(value) { + manager.closeAfterSuccess = value; + }; + + /** + * Enable or disable closing the opened popup after a failed call. + * @param {boolean} value + */ + this.closeAfterFailure = function(value) { + manager.closeAfterFailure = value; + }; + + /** + * Set bitcore server + * @param {string|Array} value + */ + this.setBitcoreURLS = function(value) { + if (typeof value === "string") { + manager.bitcoreURLS = [value]; + } else if (value instanceof Array) { + manager.bitcoreURLS = value; + } + }; + + /** + * Set currency. Human readable coin name + * @param {string|Array} value + */ + this.setCurrency = function(value) { + if (typeof value === "string") { + manager.currency = value; + } + }; + + /** + * Set currency units (mBTC, BTC) + * @param {string|Array} value + */ + this.setCurrencyUnits = function(value) { + if (typeof value === "string") { + manager.currencyUnits = value; + } + }; + + /** + * Set coin info json url + * @param {string|Array} value + */ + this.setCoinInfoURL = function(value) { + if (typeof value === "string") { + manager.coinInfoURL = value; + } + }; + + /** + * Set max. limit for account discovery + * @param {number} value + */ + this.setAccountDiscoveryLimit = function(value) { + if (!isNaN(value)) manager.accountDiscoveryLimit = value; + }; + + /** + * Set max. gap for account discovery + * @param {number} value + */ + this.setAccountDiscoveryGapLength = function(value) { + if (!isNaN(value)) manager.accountDiscoveryGapLength = value; + }; + + /** + * Set discovery BIP44 coin type + * @param {number} value + */ + this.setAccountDiscoveryBip44CoinType = function(value) { + if (!isNaN(value)) manager.accountDiscoveryBip44CoinType = value; + }; + + /** + * @typedef XPubKeyResult + * @param {boolean} success + * @param {?string} error + * @param {?string} xpubkey serialized extended public key + * @param {?string} path BIP32 serializd path of the key + */ + + /** + * Load BIP32 extended public key by path. + * + * Path can be specified either in the string form ("m/44'/1/0") or as + * raw integer array. In case you omit the path, user is asked to select + * a BIP32 account to export, and the result contains m/44'/0'/x' node + * of the account. + * + * @param {?(string|array)} path + * @param {function(XPubKeyResult)} callback + * @param {?(string|array)} requiredFirmware + */ + this.getXPubKey = function(path, callback, requiredFirmware) { + if (typeof path === "string") { + path = parseHDPath(path); + } + manager.sendWithChannel( + _fwStrFix( + { + type: "xpubkey", + path: path + }, + requiredFirmware + ), + callback + ); + }; + + this.getFreshAddress = function(callback, requiredFirmware) { + var wrapperCallback = function(result) { + if (result.success) { + callback({ success: true, address: result.freshAddress }); + } else { + callback(result); + } + }; + + manager.sendWithChannel( + _fwStrFix( + { + type: "accountinfo" + }, + requiredFirmware + ), + wrapperCallback + ); + }; + + this.getAccountInfo = function(input, callback, requiredFirmware) { + try { + manager.sendWithChannel( + _fwStrFix( + { + type: "accountinfo", + description: input + }, + requiredFirmware + ), + callback + ); + } catch (e) { + callback({ success: false, error: e }); + } + }; + + this.getAllAccountsInfo = function(callback, requiredFirmware) { + try { + manager.sendWithChannel( + _fwStrFix( + { + type: "allaccountsinfo", + description: "all" + }, + requiredFirmware + ), + callback + ); + } catch (e) { + callback({ success: false, error: e }); + } + }; + + this.getBalance = function(callback, requiredFirmware) { + manager.sendWithChannel( + _fwStrFix( + { + type: "accountinfo" + }, + requiredFirmware + ), + callback + ); + }; + + /** + * @typedef SignTxResult + * @param {boolean} success + * @param {?string} error + * @param {?string} serialized_tx serialized tx, in hex, including signatures + * @param {?array} signatures array of input signatures, in hex + */ + + /** + * Sign a transaction in the device and return both serialized + * transaction and the signatures. + * + * @param {array} inputs + * @param {array} outputs + * @param {function(SignTxResult)} callback + * @param {?(string|array)} requiredFirmware + * + * @see https://github.com/trezor/trezor-common/blob/master/protob/types.proto + */ + this.signTx = function(inputs, outputs, callback, requiredFirmware, coin) { + manager.sendWithChannel( + _fwStrFix( + { + type: "signtx", + inputs: inputs, + outputs: outputs, + coin: coin + }, + requiredFirmware + ), + callback + ); + }; + + // new implementation with ethereum at beginnig + this.ethereumSignTx = function() { + this.signEthereumTx.apply(this, arguments); + }; + + // old fallback + this.signEthereumTx = function( + address_n, + nonce, + gas_price, + gas_limit, + to, + value, + data, + chain_id, + callback, + requiredFirmware + ) { + if (requiredFirmware == null) { + requiredFirmware = "1.4.0"; // first firmware that supports ethereum + } + if (typeof address_n === "string") { + address_n = parseHDPath(address_n); + } + manager.sendWithChannel( + _fwStrFix( + { + type: "signethtx", + address_n: address_n, + nonce: nonce, + gas_price: gas_price, + gas_limit: gas_limit, + to: to, + value: value, + data: data, + chain_id: chain_id + }, + requiredFirmware + ), + callback + ); + }; + + /** + * @typedef TxRecipient + * @param {number} amount the amount to send, in satoshis + * @param {string} address the address of the recipient + */ + + /** + * Compose a transaction by doing BIP-0044 discovery, letting the user + * select an account, and picking UTXO by internal preferences. + * Transaction is then signed and returned in the same format as + * `signTx`. Only supports BIP-0044 accounts (single-signature). + * + * @param {array} recipients + * @param {function(SignTxResult)} callback + * @param {?(string|array)} requiredFirmware + */ + this.composeAndSignTx = function(recipients, callback, requiredFirmware) { + manager.sendWithChannel( + _fwStrFix( + { + type: "composetx", + recipients: recipients + }, + requiredFirmware + ), + callback + ); + }; + + /** + * @typedef RequestLoginResult + * @param {boolean} success + * @param {?string} error + * @param {?string} public_key public key used for signing, in hex + * @param {?string} signature signature, in hex + */ + + /** + * Sign a login challenge for active origin. + * + * @param {?string} hosticon + * @param {string} challenge_hidden + * @param {string} challenge_visual + * @param {string|function(RequestLoginResult)} callback + * @param {?(string|array)} requiredFirmware + * + * @see https://github.com/trezor/trezor-common/blob/master/protob/messages.proto + */ + this.requestLogin = function( + hosticon, + challenge_hidden, + challenge_visual, + callback, + requiredFirmware + ) { + if (typeof callback === "string") { + // special case for a login through button. + // `callback` is name of global var + callback = window[callback]; + } + if (!callback) { + throw new TypeError("TrezorConnect: login callback not found"); + } + manager.sendWithChannel( + _fwStrFix( + { + type: "login", + icon: hosticon, + challenge_hidden: challenge_hidden, + challenge_visual: challenge_visual + }, + requiredFirmware + ), + callback + ); + }; + + /** + * @typedef SignMessageResult + * @param {boolean} success + * @param {?string} error + * @param {?string} address address (in base58check) + * @param {?string} signature signature, in base64 + */ + + /** + * Sign a message + * + * @param {string|array} path + * @param {string} message to sign (ascii) + * @param {string|function(SignMessageResult)} callback + * @param {?string} opt_coin - (optional) name of coin (default Bitcoin) + * @param {?(string|array)} requiredFirmware + * + */ + this.signMessage = function( + path, + message, + callback, + opt_coin, + requiredFirmware + ) { + if (typeof path === "string") { + path = parseHDPath(path); + } + if (!opt_coin) { + opt_coin = "Bitcoin"; + } + if (!callback) { + throw new TypeError("TrezorConnect: callback not found"); + } + manager.sendWithChannel( + _fwStrFix( + { + type: "signmsg", + path: path, + message: message, + coin: opt_coin + }, + requiredFirmware + ), + callback + ); + }; + + /** + * Sign an Ethereum message + * + * @param {string|array} path + * @param {string} message to sign (ascii) + * @param {string|function(SignMessageResult)} callback + * @param {?(string|array)} requiredFirmware + * + */ + this.ethereumSignMessage = function( + path, + message, + callback, + requiredFirmware + ) { + if (typeof path === "string") { + path = parseHDPath(path); + } + if (!callback) { + throw new TypeError("TrezorConnect: callback not found"); + } + manager.sendWithChannel( + _fwStrFix( + { + type: "signethmsg", + path: path, + message: message + }, + requiredFirmware + ), + callback + ); + }; + + /** + * Verify message + * + * @param {string} address + * @param {string} signature (base64) + * @param {string} message (string) + * @param {string|function()} callback + * @param {?string} opt_coin - (optional) name of coin (default Bitcoin) + * @param {?(string|array)} requiredFirmware + * + */ + this.verifyMessage = function( + address, + signature, + message, + callback, + opt_coin, + requiredFirmware + ) { + if (!opt_coin) { + opt_coin = "Bitcoin"; + } + if (!callback) { + throw new TypeError("TrezorConnect: callback not found"); + } + manager.sendWithChannel( + _fwStrFix( + { + type: "verifymsg", + address: address, + signature: signature, + message: message, + coin: { coin_name: opt_coin } + }, + requiredFirmware + ), + callback + ); + }; + + /** + * Verify ethereum message + * + * @param {string} address + * @param {string} signature (base64) + * @param {string} message (string) + * @param {string|function()} callback + * @param {?(string|array)} requiredFirmware + * + */ + this.ethereumVerifyMessage = function( + address, + signature, + message, + callback, + requiredFirmware + ) { + if (!callback) { + throw new TypeError("TrezorConnect: callback not found"); + } + manager.sendWithChannel( + _fwStrFix( + { + type: "verifyethmsg", + address: address, + signature: signature, + message: message + }, + requiredFirmware + ), + callback + ); + }; + + /** + * Symmetric key-value encryption + * + * @param {string|array} path + * @param {string} key to show on device display + * @param {string} value hexadecimal value, length a multiple of 16 bytes + * @param {boolean} encrypt / decrypt direction + * @param {boolean} ask_on_encrypt (should user confirm on encrypt?) + * @param {boolean} ask_on_decrypt (should user confirm on decrypt?) + * @param {string|function()} callback + * @param {?(string|array)} requiredFirmware + * + */ + this.cipherKeyValue = function( + path, + key, + value, + encrypt, + ask_on_encrypt, + ask_on_decrypt, + callback, + requiredFirmware + ) { + if (typeof path === "string") { + path = parseHDPath(path); + } + if (typeof value !== "string") { + throw new TypeError("TrezorConnect: Value must be a string"); + } + if (!/^[0-9A-Fa-f]*$/.test(value)) { + throw new TypeError("TrezorConnect: Value must be hexadecimal"); + } + if (value.length % 32 !== 0) { + // 1 byte == 2 hex strings + throw new TypeError( + "TrezorConnect: Value length must be multiple of 16 bytes" + ); + } + if (!callback) { + throw new TypeError("TrezorConnect: callback not found"); + } + manager.sendWithChannel( + _fwStrFix( + { + type: "cipherkeyvalue", + path: path, + key: key, + value: value, + encrypt: !!encrypt, + ask_on_encrypt: !!ask_on_encrypt, + ask_on_decrypt: !!ask_on_decrypt + }, + requiredFirmware + ), + callback + ); + }; + + this.nemGetAddress = function( + address_n, + network, + callback, + requiredFirmware + ) { + if (requiredFirmware == null) { + requiredFirmware = "1.6.0"; // first firmware that supports NEM + } + if (typeof address_n === "string") { + address_n = parseHDPath(address_n); + } + manager.sendWithChannel( + _fwStrFix( + { + type: "nemGetAddress", + address_n: address_n, + network: network + }, + requiredFirmware + ), + callback + ); + }; + + this.nemSignTx = function( + address_n, + transaction, + callback, + requiredFirmware + ) { + if (requiredFirmware == null) { + requiredFirmware = "1.6.0"; // first firmware that supports NEM + } + if (typeof address_n === "string") { + address_n = parseHDPath(address_n); + } + manager.sendWithChannel( + _fwStrFix( + { + type: "nemSignTx", + address_n: address_n, + transaction: transaction + }, + requiredFirmware + ), + callback + ); + }; + + this.pushTransaction = function(rawTx, callback) { + if (!/^[0-9A-Fa-f]*$/.test(rawTx)) { + throw new TypeError("TrezorConnect: Transaction must be hexadecimal"); + } + if (!callback) { + throw new TypeError("TrezorConnect: callback not found"); + } + + manager.sendWithChannel( + { + type: "pushtx", + rawTx: rawTx + }, + callback + ); + }; + + /** + * Display address on device + * + * @param {array} address + * @param {string} coin + * @param {boolean} segwit + * @param {?(string|array)} requiredFirmware + * + */ + this.getAddress = function( + address, + coin, + segwit, + callback, + requiredFirmware + ) { + if (typeof address === "string") { + address = parseHDPath(address); + } + + manager.sendWithChannel( + _fwStrFix( + { + type: "getaddress", + address_n: address, + coin: coin, + segwit: segwit + }, + requiredFirmware + ), + callback + ); + }; + + /** + * Display ethereum address on device + * + * @param {array} address + * @param {?(string|array)} requiredFirmware + * + */ + this.ethereumGetAddress = function(address, callback, requiredFirmware) { + if (typeof address === "string") { + address = parseHDPath(address); + } + + manager.sendWithChannel( + _fwStrFix( + { + type: "ethgetaddress", + address_n: address + }, + requiredFirmware + ), + callback + ); + }; + + var LOGIN_CSS = + ''; + + var LOGIN_ONCLICK = + "TrezorConnect.requestLogin(" + + "'@hosticon@','@challenge_hidden@','@challenge_visual@','@callback@'" + + ")"; + + var LOGIN_HTML = + '
' + + ' ' + + ' ' + + ' @text@' + + " " + + ' ' + + ' What is TREZOR?' + + " " + + "
"; + + /** + * Find elements and replace them with login buttons. + * It's not required to use these special elements, feel free to call + * `TrezorConnect.requestLogin` directly. + */ + this.renderLoginButtons = function() { + var elements = document.getElementsByTagName("trezor:login"); + + for (var i = 0; i < elements.length; i++) { + var e = elements[i]; + var text = e.getAttribute("text") || "Sign in with TREZOR"; + var callback = e.getAttribute("callback") || ""; + var hosticon = e.getAttribute("icon") || ""; + var challenge_hidden = e.getAttribute("challenge_hidden") || ""; + var challenge_visual = e.getAttribute("challenge_visual") || ""; + + // it's not valid to put markup into attributes, so let users + // supply a raw text and make TREZOR bold + text = text.replace("TREZOR", "TREZOR"); + e.outerHTML = (LOGIN_CSS + LOGIN_HTML) + .replace("@text@", text) + .replace("@callback@", callback) + .replace("@hosticon@", hosticon) + .replace("@challenge_hidden@", challenge_hidden) + .replace("@challenge_visual@", challenge_visual) + .replace("@connect_path@", POPUP_PATH); + } + }; +} + +/* + * `getXPubKey()` + */ + +function parseHDPath(string) { + return string + .toLowerCase() + .split("/") + .filter(function(p) { + return p !== "m"; + }) + .map(function(p) { + var hardened = false; + if (p[p.length - 1] === "'") { + hardened = true; + p = p.substr(0, p.length - 1); + } + if (isNaN(p)) { + throw new Error("Not a valid path."); + } + var n = parseInt(p); + if (hardened) { + // hardened index + n = (n | 0x80000000) >>> 0; + } + return n; + }); +} + +/* + * Popup management + */ + +function ChromePopup(url, name, width, height) { + var left = (screen.width - width) / 2; + var top = (screen.height - height) / 2; + var opts = { + id: name, + innerBounds: { + width: width, + height: height, + left: left, + top: top + } + }; + + var closed = function() { + if (this.onclose) { + this.onclose(false); // never report as blocked + } + }.bind(this); + + var opened = function(w) { + this.window = w; + this.window.onClosed.addListener(closed); + }.bind(this); + + chrome.app.window.create(url, opts, opened); + + this.name = name; + this.window = null; + this.onclose = null; +} + +function ChromeChannel(popup, waiting) { + var port = null; + + var respond = function(data) { + if (waiting) { + var w = waiting; + waiting = null; + w(data); + } + }; + + var setup = function(p) { + if (p.name === popup.name) { + port = p; + port.onMessage.addListener(respond); + chrome.runtime.onConnect.removeListener(setup); + } + }; + + chrome.runtime.onConnect.addListener(setup); + + this.respond = respond; + + this.close = function() { + chrome.runtime.onConnect.removeListener(setup); + port.onMessage.removeListener(respond); + port.disconnect(); + port = null; + }; + + this.send = function(value, callback) { + if (waiting === null) { + waiting = callback; + + if (port) { + port.postMessage(value); + } else { + throw new Error(ERR_CHROME_NOT_CONNECTED); + } + } else { + throw new Error(ERR_ALREADY_WAITING); + } + }; +} + +function Popup(url, origin, name, width, height) { + var left = (screen.width - width) / 2; + var top = (screen.height - height) / 2; + var opts = + "width=" + + width + + ",height=" + + height + + ",left=" + + left + + ",top=" + + top + + ",menubar=no" + + ",toolbar=no" + + ",location=no" + + ",personalbar=no" + + ",status=no"; + var w = window.open(url, name, opts); + + var interval; + var blocked = w.closed; + var iterate = function() { + if (w.closed) { + clearInterval(interval); + if (this.onclose) { + this.onclose(blocked); + } + } + }.bind(this); + interval = setInterval(iterate, 100); + + this.window = w; + this.origin = origin; + this.onclose = null; +} + +function Channel(popup, waiting) { + var respond = function(data) { + if (waiting) { + var w = waiting; + waiting = null; + w(data); + } + }; + + var receive = function(event) { + var org1 = event.origin.match(/^.+\:\/\/[^\‌​/]+/)[0]; + var org2 = popup.origin.match(/^.+\:\/\/[^\‌​/]+/)[0]; + //if (event.source === popup.window && event.origin === popup.origin) { + if (event.source === popup.window && org1 === org2) { + respond(event.data); + } + }; + + window.addEventListener("message", receive); + + this.respond = respond; + + this.close = function() { + window.removeEventListener("message", receive); + }; + + this.send = function(value, callback) { + if (waiting === null) { + waiting = callback; + popup.window.postMessage(value, popup.origin); + } else { + throw new Error(ERR_ALREADY_WAITING); + } + }; +} + +function ConnectedChannel(p) { + var ready = function() { + clearTimeout(this.timeout); + this.popup.onclose = null; + this.ready = true; + this.onready(); + }.bind(this); + + var closed = function(blocked) { + clearTimeout(this.timeout); + this.channel.close(); + if (blocked) { + this.onerror(new Error(ERR_WINDOW_BLOCKED)); + } else { + this.onerror(new Error(ERR_WINDOW_CLOSED)); + } + }.bind(this); + + var timedout = function() { + this.popup.onclose = null; + if (this.popup.window) { + this.popup.window.close(); + } + this.channel.close(); + this.onerror(new Error(ERR_TIMED_OUT)); + }.bind(this); + + if (IS_CHROME_APP) { + this.popup = new ChromePopup(p.chromeUrl, p.name, p.width, p.height); + this.channel = new ChromeChannel(this.popup, ready); + } else { + this.popup = new Popup(p.url, p.origin, p.name, p.width, p.height); + this.channel = new Channel(this.popup, ready); + } + + this.timeout = setTimeout(timedout, POPUP_INIT_TIMEOUT); + + this.popup.onclose = closed; + + this.ready = false; + this.onready = null; + this.onerror = null; +} + +function PopupManager() { + var cc = null; + + var closed = function() { + cc.channel.respond(new Error(ERR_WINDOW_CLOSED)); + cc.channel.close(); + cc = null; + }; + + var open = function(callback) { + cc = new ConnectedChannel({ + name: "trezor-connect", + width: 600, + height: 500, + origin: POPUP_ORIGIN, + path: POPUP_PATH, + url: POPUP_URL, + chromeUrl: CHROME_URL + }); + cc.onready = function() { + cc.popup.onclose = closed; + callback(cc.channel); + }; + cc.onerror = function(error) { + cc = null; + callback(error); + }; + }.bind(this); + + this.closeAfterSuccess = true; + this.closeAfterFailure = true; + + this.close = function() { + if (cc && cc.popup.window) { + cc.popup.window.close(); + } + }; + + this.waitForChannel = function(callback) { + if (cc) { + if (cc.ready) { + callback(cc.channel); + } else { + callback(new Error(ERR_ALREADY_WAITING)); + } + } else { + try { + open(callback); + } catch (e) { + callback(new Error(ERR_WINDOW_BLOCKED)); + } + } + }; + + this.sendWithChannel = function(message, callback) { + message.bitcoreURLS = this.bitcoreURLS || null; + message.currency = this.currency || null; + message.currencyUnits = this.currencyUnits || null; + message.coinInfoURL = this.coinInfoURL || null; + message.accountDiscoveryLimit = this.accountDiscoveryLimit || null; + message.accountDiscoveryGapLength = this.accountDiscoveryGapLength || null; + message.accountDiscoveryBip44CoinType = + this.accountDiscoveryBip44CoinType || null; + + var respond = function(response) { + var succ = response.success && this.closeAfterSuccess; + var fail = !response.success && this.closeAfterFailure; + if (succ || fail) { + this.close(); + } + callback(response); + }.bind(this); + + var onresponse = function(response) { + if (response instanceof Error) { + var error = response; + respond({ success: false, error: error.message }); + } else { + respond(response); + } + }; + + var onchannel = function(channel) { + if (channel instanceof Error) { + var error = channel; + respond({ success: false, error: error.message }); + } else { + channel.send(message, onresponse); + } + }; + + this.waitForChannel(onchannel); + }; +} + +const connect = new TrezorConnect(); +module.exports = connect; \ No newline at end of file diff --git a/app/scripts/lib/trezorKeyring.js b/app/scripts/lib/trezorKeyring.js new file mode 100644 index 000000000..d99f384f2 --- /dev/null +++ b/app/scripts/lib/trezorKeyring.js @@ -0,0 +1,255 @@ +const { EventEmitter } = require('events') +const ethUtil = require('ethereumjs-util') +// const sigUtil = require('eth-sig-util') +//const { Lock } = require('semaphore-async-await') + +const hdPathString = `m/44'/60'/0'/0` +const keyringType = 'Trezor Hardware Keyring' + +const TrezorConnect = require('./trezor-connect.js') +const HDKey = require('hdkey') +const TREZOR_FIRMWARE_VERSION = '1.4.0' +const log = require('loglevel') + +class TrezorKeyring extends EventEmitter { + constructor (opts = {}) { + super() + this.type = keyringType + //this.lock = new Lock() + this.accounts = [] + this.hdk = new HDKey() + this.deserialize(opts) + this.page = 0 + this.perPage = 5 + } + + serialize () { + return Promise.resolve({ hdPath: this.hdPath, accounts: this.accounts }) + } + + deserialize (opts = {}) { + this.hdPath = opts.hdPath || hdPathString + this.accounts = opts.accounts || [] + return Promise.resolve() + } + + unlock () { + if (this.hdk.publicKey) return Promise.resolve() + + return new Promise((resolve, reject) => { + TrezorConnect.getXPubKey( + this.hdPath, + response => { + log.debug('TREZOR CONNECT RESPONSE: ') + log.debug(response) + if (response.success) { + this.hdk.publicKey = new Buffer(response.publicKey, 'hex') + this.hdk.chainCode = new Buffer(response.chainCode, 'hex') + resolve() + } else { + reject(response.error || 'Unknown error') + } + }, + TREZOR_FIRMWARE_VERSION + ) + }) + } + + addAccounts (n = 1) { + return new Promise((resolve, reject) => { + return this.unlock() + .then(_ => { + const pathBase = 'm' + const from = n + const to = n + 1 + + this.accounts = [] + + for (let i = from; i < to; i++) { + const dkey = this.hdk.derive(`${pathBase}/${i}`) + const address = ethUtil + .publicToAddress(dkey.publicKey, true) + .toString('hex') + this.accounts.push(ethUtil.toChecksumAddress(address)) + this.page = 0 + } + resolve(this.accounts) + }) + .catch(e => { + reject(e) + }) + }) + } + + async getPage () { + return new Promise((resolve, reject) => { + return this.unlock() + .then(_ => { + const pathBase = 'm' + const from = this.page === 0 ? 0 : (this.page - 1) * this.perPage + const to = from + this.perPage + + const accounts = [] + + for (let i = from; i < to; i++) { + const dkey = this.hdk.derive(`${pathBase}/${i}`) + const address = ethUtil + .publicToAddress(dkey.publicKey, true) + .toString('hex') + accounts.push({ + address: ethUtil.toChecksumAddress(address), + balance: 0, + index: i, + }) + } + resolve(accounts) + }) + .catch(e => { + reject(e) + }) + }) + } + + async getPrevAccountSet () { + this.page-- + return await this.getPage() + } + + async getNextAccountSet () { + this.page++ + return await this.getPage() + } + + getAccounts () { + return Promise.resolve(this.accounts.slice()) + } + + // tx is an instance of the ethereumjs-transaction class. + async signTransaction (address, tx) { + throw new Error('Not supported on this device') + /* + await this.lock.acquire() + try { + + // Look before we leap + await this._checkCorrectTrezorAttached() + + let accountId = await this._findAddressId(address) + let eth = await this._getEth() + tx.v = tx._chainId + let TrezorSig = await eth.signTransaction( + this._derivePath(accountId), + tx.serialize().toString('hex') + ) + tx.v = parseInt(TrezorSig.v, 16) + tx.r = '0x' + TrezorSig.r + tx.s = '0x' + TrezorSig.s + + // Since look before we leap check is racy, also check that signature is for account expected + let addressSignedWith = ethUtil.bufferToHex(tx.getSenderAddress()) + if (addressSignedWith.toLowerCase() !== address.toLowerCase()) { + throw new Error( + `Signature is for ${addressSignedWith} but expected ${address} - is the correct Trezor device attached?` + ) + } + + return tx + + } finally { + await this.lock.release() + }*/ + } + + async signMessage (withAccount, data) { + throw new Error('Not supported on this device') + } + + // For personal_sign, we need to prefix the message: + async signPersonalMessage (withAccount, message) { + throw new Error('Not supported on this device') + /* + await this.lock.acquire() + try { + // Look before we leap + await this._checkCorrectTrezorAttached() + + let accountId = await this._findAddressId(withAccount) + let eth = await this._getEth() + let msgHex = ethUtil.stripHexPrefix(message) + let TrezorSig = await eth.signPersonalMessage( + this._derivePath(accountId), + msgHex + ) + let signature = this._personalToRawSig(TrezorSig) + + // Since look before we leap check is racy, also check that signature is for account expected + let addressSignedWith = sigUtil.recoverPersonalSignature({ + data: message, + sig: signature, + }) + if (addressSignedWith.toLowerCase() !== withAccount.toLowerCase()) { + throw new Error( + `Signature is for ${addressSignedWith} but expected ${withAccount} - is the correct Trezor device attached?` + ) + } + + return signature + + } finally { + await this.lock.release() + } */ + } + + async signTypedData (withAccount, typedData) { + throw new Error('Not supported on this device') + } + + async exportAccount (address) { + throw new Error('Not supported on this device') + } + + async _findAddressId (addr) { + const result = this.accounts.indexOf(addr) + if (result === -1) throw new Error('Unknown address') + else return result + } + + async _addressFromId (i) { + /* Must be called with lock acquired + const eth = await this._getEth() + return (await eth.getAddress(this._derivePath(i))).address*/ + const result = this.accounts[i] + if (!result) throw new Error('Unknown address') + else return result + } + + async _checkCorrectTrezorAttached () { + return true + /* Must be called with lock acquired + if (this.accounts.length > 0) { + const expectedFirstAccount = this.accounts[0] + let actualFirstAccount = await this._addressFromId(0) + if (expectedFirstAccount !== actualFirstAccount) { + throw new Error( + `Incorrect Trezor device attached - expected device containg account ${expectedFirstAccount}, but found ${actualFirstAccount}` + ) + } + }*/ + } + + _derivePath (i) { + return this.hdPath + '/' + i + } + + _personalToRawSig (TrezorSig) { + var v = TrezorSig['v'] - 27 + v = v.toString(16) + if (v.length < 2) { + v = '0' + v + } + return '0x' + TrezorSig['r'] + TrezorSig['s'] + v + } +} + +TrezorKeyring.type = keyringType +module.exports = TrezorKeyring \ No newline at end of file diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index a362e3826..dd5a5616f 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -48,6 +48,7 @@ const seedPhraseVerifier = require('./lib/seed-phrase-verifier') const cleanErrorStack = require('./lib/cleanErrorStack') const DiagnosticsReporter = require('./lib/diagnostics-reporter') const log = require('loglevel') +const TrezorKeyring = require("./lib/trezorKeyring"); module.exports = class MetamaskController extends EventEmitter { @@ -130,9 +131,11 @@ module.exports = class MetamaskController extends EventEmitter { provider: this.provider, blockTracker: this.blockTracker, }) - + // key mgmt + const additionalKeyrings = [TrezorKeyring] this.keyringController = new KeyringController({ + keyringTypes: additionalKeyrings, initState: initState.KeyringController, getNetwork: this.networkController.getNetworkState.bind(this.networkController), encryptor: opts.encryptor || undefined, @@ -363,6 +366,10 @@ module.exports = class MetamaskController extends EventEmitter { resetAccount: nodeify(this.resetAccount, this), importAccountWithStrategy: nodeify(this.importAccountWithStrategy, this), + // trezor + connectHardware: nodeify(this.connectHardware, this), + unlockTrezorAccount: nodeify(this.unlockTrezorAccount, this), + // vault management submitPassword: nodeify(this.submitPassword, this), @@ -523,6 +530,60 @@ module.exports = class MetamaskController extends EventEmitter { this.preferencesController.setSelectedAddress(address) } + // + // Hardware + // + + /** + * Fetch account list from a trezor device. + * + * @returns [] accounts + */ + async connectHardware (deviceName, page) { + const keyringController = this.keyringController + const keyring = await keyringController.getKeyringsByType( + 'Trezor Hardware Keyring' + )[0] + if (!keyring) { + throw new Error('MetamaskController - No Trezor Hardware Keyring found') + } + + const accounts = page === -1 ? await keyring.getPrevAccountSet() : await keyring.getNextAccountSet() + + return accounts + + } + + /** + * Imports an account from a trezor device. + * + * @returns {} keyState + */ + async unlockTrezorAccount (index) { + const keyringController = this.keyringController + const keyring = await keyringController.getKeyringsByType( + 'Trezor Hardware Keyring' + )[0] + if (!keyring) { + throw new Error('MetamaskController - No Trezor Hardware Keyring found') + } + + const oldAccounts = await keyringController.getAccounts() + const keyState = await keyringController.addNewAccount(keyring) + const newAccounts = await keyringController.getAccounts() + + this.preferencesController.setAddresses(newAccounts) + newAccounts.forEach(address => { + if (!oldAccounts.includes(address)) { + this.preferencesController.setSelectedAddress(address) + } + }) + + const { identities } = this.preferencesController.store.getState() + return { ...keyState, identities } + } + + // // Account Management // -- cgit v1.2.3 From f5f66f59d7d215adf402a1e580c452e634480f69 Mon Sep 17 00:00:00 2001 From: Bruno Date: Sun, 10 Jun 2018 18:48:42 -0400 Subject: clean up --- app/scripts/lib/trezorKeyring.js | 16 +++++++++++----- app/scripts/metamask-controller.js | 7 ++++--- 2 files changed, 15 insertions(+), 8 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/trezorKeyring.js b/app/scripts/lib/trezorKeyring.js index d99f384f2..83787f3d2 100644 --- a/app/scripts/lib/trezorKeyring.js +++ b/app/scripts/lib/trezorKeyring.js @@ -1,10 +1,9 @@ const { EventEmitter } = require('events') const ethUtil = require('ethereumjs-util') // const sigUtil = require('eth-sig-util') -//const { Lock } = require('semaphore-async-await') const hdPathString = `m/44'/60'/0'/0` -const keyringType = 'Trezor Hardware Keyring' +const keyringType = 'Trezor Hardware' const TrezorConnect = require('./trezor-connect.js') const HDKey = require('hdkey') @@ -15,7 +14,6 @@ class TrezorKeyring extends EventEmitter { constructor (opts = {}) { super() this.type = keyringType - //this.lock = new Lock() this.accounts = [] this.hdk = new HDKey() this.deserialize(opts) @@ -24,16 +22,22 @@ class TrezorKeyring extends EventEmitter { } serialize () { - return Promise.resolve({ hdPath: this.hdPath, accounts: this.accounts }) + return Promise.resolve({ + hdPath: this.hdPath, + accounts: this.accounts, + page: this.page, + }) } deserialize (opts = {}) { this.hdPath = opts.hdPath || hdPathString this.accounts = opts.accounts || [] + this.page = opts.page || 0 return Promise.resolve() } unlock () { + if (this.hdk.publicKey) return Promise.resolve() return new Promise((resolve, reject) => { @@ -56,6 +60,7 @@ class TrezorKeyring extends EventEmitter { } addAccounts (n = 1) { + return new Promise((resolve, reject) => { return this.unlock() .then(_ => { @@ -82,6 +87,7 @@ class TrezorKeyring extends EventEmitter { } async getPage () { + return new Promise((resolve, reject) => { return this.unlock() .then(_ => { @@ -252,4 +258,4 @@ class TrezorKeyring extends EventEmitter { } TrezorKeyring.type = keyringType -module.exports = TrezorKeyring \ No newline at end of file +module.exports = TrezorKeyring diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index dd5a5616f..081c2e2db 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -48,7 +48,7 @@ const seedPhraseVerifier = require('./lib/seed-phrase-verifier') const cleanErrorStack = require('./lib/cleanErrorStack') const DiagnosticsReporter = require('./lib/diagnostics-reporter') const log = require('loglevel') -const TrezorKeyring = require("./lib/trezorKeyring"); +const TrezorKeyring = require('./lib/trezorKeyring') module.exports = class MetamaskController extends EventEmitter { @@ -540,9 +540,10 @@ module.exports = class MetamaskController extends EventEmitter { * @returns [] accounts */ async connectHardware (deviceName, page) { + const keyringController = this.keyringController const keyring = await keyringController.getKeyringsByType( - 'Trezor Hardware Keyring' + 'Trezor Hardware' )[0] if (!keyring) { throw new Error('MetamaskController - No Trezor Hardware Keyring found') @@ -562,7 +563,7 @@ module.exports = class MetamaskController extends EventEmitter { async unlockTrezorAccount (index) { const keyringController = this.keyringController const keyring = await keyringController.getKeyringsByType( - 'Trezor Hardware Keyring' + 'Trezor Hardware' )[0] if (!keyring) { throw new Error('MetamaskController - No Trezor Hardware Keyring found') -- cgit v1.2.3 From f6b27fa9eb542c1ac3fabdad9285e1a50baee7dc Mon Sep 17 00:00:00 2001 From: Bruno Date: Sun, 10 Jun 2018 19:02:54 -0400 Subject: add account working --- app/scripts/lib/trezorKeyring.js | 19 +++++++++++-------- app/scripts/metamask-controller.js | 2 ++ 2 files changed, 13 insertions(+), 8 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/trezorKeyring.js b/app/scripts/lib/trezorKeyring.js index 83787f3d2..cf7436a44 100644 --- a/app/scripts/lib/trezorKeyring.js +++ b/app/scripts/lib/trezorKeyring.js @@ -8,7 +8,7 @@ const keyringType = 'Trezor Hardware' const TrezorConnect = require('./trezor-connect.js') const HDKey = require('hdkey') const TREZOR_FIRMWARE_VERSION = '1.4.0' -const log = require('loglevel') +//const log = require('loglevel') class TrezorKeyring extends EventEmitter { constructor (opts = {}) { @@ -19,10 +19,11 @@ class TrezorKeyring extends EventEmitter { this.deserialize(opts) this.page = 0 this.perPage = 5 + this.accountToUnlock = 0 } serialize () { - return Promise.resolve({ + return Promise.resolve({ hdPath: this.hdPath, accounts: this.accounts, page: this.page, @@ -44,8 +45,6 @@ class TrezorKeyring extends EventEmitter { TrezorConnect.getXPubKey( this.hdPath, response => { - log.debug('TREZOR CONNECT RESPONSE: ') - log.debug(response) if (response.success) { this.hdk.publicKey = new Buffer(response.publicKey, 'hex') this.hdk.chainCode = new Buffer(response.chainCode, 'hex') @@ -59,14 +58,18 @@ class TrezorKeyring extends EventEmitter { }) } + setAccountToUnlock (index) { + this.accountToUnlock = parseInt(index, 10) + } + addAccounts (n = 1) { return new Promise((resolve, reject) => { return this.unlock() .then(_ => { const pathBase = 'm' - const from = n - const to = n + 1 + const from = this.accountToUnlock + const to = from + 1 this.accounts = [] @@ -133,7 +136,7 @@ class TrezorKeyring extends EventEmitter { // tx is an instance of the ethereumjs-transaction class. async signTransaction (address, tx) { throw new Error('Not supported on this device') - /* + /* await this.lock.acquire() try { @@ -200,7 +203,7 @@ class TrezorKeyring extends EventEmitter { } return signature - + } finally { await this.lock.release() } */ diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 081c2e2db..3cb77b35a 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -569,6 +569,8 @@ module.exports = class MetamaskController extends EventEmitter { throw new Error('MetamaskController - No Trezor Hardware Keyring found') } + keyring.setAccountToUnlock(index) + const oldAccounts = await keyringController.getAccounts() const keyState = await keyringController.addNewAccount(keyring) const newAccounts = await keyringController.getAccounts() -- cgit v1.2.3 From d1880073f678dbdc52e07e62ec66c39eea5062a6 Mon Sep 17 00:00:00 2001 From: Bruno Date: Sun, 10 Jun 2018 21:10:22 -0400 Subject: balances working --- app/scripts/lib/trezorKeyring.js | 3 ++- app/scripts/metamask-controller.js | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/trezorKeyring.js b/app/scripts/lib/trezorKeyring.js index cf7436a44..fb029f82a 100644 --- a/app/scripts/lib/trezorKeyring.js +++ b/app/scripts/lib/trezorKeyring.js @@ -8,7 +8,7 @@ const keyringType = 'Trezor Hardware' const TrezorConnect = require('./trezor-connect.js') const HDKey = require('hdkey') const TREZOR_FIRMWARE_VERSION = '1.4.0' -//const log = require('loglevel') +const log = require('loglevel') class TrezorKeyring extends EventEmitter { constructor (opts = {}) { @@ -111,6 +111,7 @@ class TrezorKeyring extends EventEmitter { index: i, }) } + log.debug(accounts) resolve(accounts) }) .catch(e => { diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 3cb77b35a..daab5baa5 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -549,10 +549,10 @@ module.exports = class MetamaskController extends EventEmitter { throw new Error('MetamaskController - No Trezor Hardware Keyring found') } - const accounts = page === -1 ? await keyring.getPrevAccountSet() : await keyring.getNextAccountSet() + const accounts = page === -1 ? await keyring.getPrevAccountSet(this.provider) : await keyring.getNextAccountSet(this.provider) + this.accountTracker.syncWithAddresses(accounts.map(a => a.address)) return accounts - } /** @@ -570,7 +570,6 @@ module.exports = class MetamaskController extends EventEmitter { } keyring.setAccountToUnlock(index) - const oldAccounts = await keyringController.getAccounts() const keyState = await keyringController.addNewAccount(keyring) const newAccounts = await keyringController.getAccounts() -- cgit v1.2.3 From 68d97211ff468b137965df2a30c6b295a3ab5679 Mon Sep 17 00:00:00 2001 From: Bruno Date: Mon, 11 Jun 2018 01:52:41 -0400 Subject: sign transactions is pretty close --- app/scripts/lib/trezorKeyring.js | 126 ++++++++++++++++++++++++--------------- 1 file changed, 77 insertions(+), 49 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/trezorKeyring.js b/app/scripts/lib/trezorKeyring.js index fb029f82a..fa5d6070c 100644 --- a/app/scripts/lib/trezorKeyring.js +++ b/app/scripts/lib/trezorKeyring.js @@ -4,10 +4,11 @@ const ethUtil = require('ethereumjs-util') const hdPathString = `m/44'/60'/0'/0` const keyringType = 'Trezor Hardware' - +const Transaction = require('ethereumjs-tx') +const pathBase = 'm' const TrezorConnect = require('./trezor-connect.js') const HDKey = require('hdkey') -const TREZOR_FIRMWARE_VERSION = '1.4.0' +const TREZOR_MIN_FIRMWARE_VERSION = '1.5.2' const log = require('loglevel') class TrezorKeyring extends EventEmitter { @@ -19,7 +20,7 @@ class TrezorKeyring extends EventEmitter { this.deserialize(opts) this.page = 0 this.perPage = 5 - this.accountToUnlock = 0 + this.unlockedAccount = 0 } serialize () { @@ -53,13 +54,13 @@ class TrezorKeyring extends EventEmitter { reject(response.error || 'Unknown error') } }, - TREZOR_FIRMWARE_VERSION + TREZOR_MIN_FIRMWARE_VERSION ) }) } setAccountToUnlock (index) { - this.accountToUnlock = parseInt(index, 10) + this.unlockedAccount = parseInt(index, 10) } addAccounts (n = 1) { @@ -67,18 +68,13 @@ class TrezorKeyring extends EventEmitter { return new Promise((resolve, reject) => { return this.unlock() .then(_ => { - const pathBase = 'm' - const from = this.accountToUnlock + const from = this.unlockedAccount const to = from + 1 - this.accounts = [] for (let i = from; i < to; i++) { - const dkey = this.hdk.derive(`${pathBase}/${i}`) - const address = ethUtil - .publicToAddress(dkey.publicKey, true) - .toString('hex') - this.accounts.push(ethUtil.toChecksumAddress(address)) + + this.accounts.push(this.getEthAddress(pathBase, i)) this.page = 0 } resolve(this.accounts) @@ -94,19 +90,16 @@ class TrezorKeyring extends EventEmitter { return new Promise((resolve, reject) => { return this.unlock() .then(_ => { - const pathBase = 'm' + const from = this.page === 0 ? 0 : (this.page - 1) * this.perPage const to = from + this.perPage const accounts = [] for (let i = from; i < to; i++) { - const dkey = this.hdk.derive(`${pathBase}/${i}`) - const address = ethUtil - .publicToAddress(dkey.publicKey, true) - .toString('hex') + accounts.push({ - address: ethUtil.toChecksumAddress(address), + address: this.getEthAddress(pathBase, i), balance: 0, index: i, }) @@ -134,40 +127,75 @@ class TrezorKeyring extends EventEmitter { return Promise.resolve(this.accounts.slice()) } - // tx is an instance of the ethereumjs-transaction class. - async signTransaction (address, tx) { - throw new Error('Not supported on this device') - /* - await this.lock.acquire() - try { - - // Look before we leap - await this._checkCorrectTrezorAttached() + padLeftEven (hex) { + return hex.length % 2 !== 0 ? `0${hex}` : hex + } - let accountId = await this._findAddressId(address) - let eth = await this._getEth() - tx.v = tx._chainId - let TrezorSig = await eth.signTransaction( - this._derivePath(accountId), - tx.serialize().toString('hex') - ) - tx.v = parseInt(TrezorSig.v, 16) - tx.r = '0x' + TrezorSig.r - tx.s = '0x' + TrezorSig.s + cleanData (buf) { + return this.padLeftEven(ethUtil.bufferToHex(buf).substring(2).toLowerCase()) + } - // Since look before we leap check is racy, also check that signature is for account expected - let addressSignedWith = ethUtil.bufferToHex(tx.getSenderAddress()) - if (addressSignedWith.toLowerCase() !== address.toLowerCase()) { - throw new Error( - `Signature is for ${addressSignedWith} but expected ${address} - is the correct Trezor device attached?` - ) - } + getEthAddress (pathBase, i) { + const dkey = this.hdk.derive(`${pathBase}/${i}`) + const address = ethUtil + .publicToAddress(dkey.publicKey, true) + .toString('hex') + return ethUtil.toChecksumAddress(address) + } - return tx + // tx is an instance of the ethereumjs-transaction class. + async signTransaction (address, tx) { - } finally { - await this.lock.release() - }*/ + return new Promise((resolve, reject) => { + log.debug('sign transaction ', address, tx) + const account = `m/44'/60'/0'/${this.unlockedAccount}` + + const txData = { + account, + nonce: this.cleanData(tx.nonce), + gasPrice: this.cleanData(tx.gasPrice), + gasLimit: this.cleanData(tx.gasLimit), + to: this.cleanData(tx.to), + value: this.cleanData(tx.value), + data: this.cleanData(tx.data), + chainId: tx._chainId, + } + + TrezorConnect.ethereumSignTx( + txData.account, + txData.nonce, + txData.gasPrice, + txData.gasLimit, + txData.to, + txData.value, + txData.data === '' ? null : txData.data, + txData.chainId, + response => { + if (response.success) { + tx.v = `0x${response.v.toString(16)}` + tx.r = `0x${response.r}` + tx.s = `0x${response.s}` + log.debug('about to create new tx with data', tx) + + const signedTx = new Transaction(tx) + + log.debug('signature is valid?', signedTx.verifySignature()) + + const addressSignedWith = ethUtil.toChecksumAddress(`0x${signedTx.from.toString('hex')}`) + const correctAddress = ethUtil.toChecksumAddress(address) + if (addressSignedWith !== correctAddress) { + // throw new Error('signature doesnt match the right address') + log.error('signature doesnt match the right address', addressSignedWith, correctAddress) + } + + resolve(signedTx) + + } else { + throw new Error(response.error || 'Unknown error') + } + }, + TREZOR_MIN_FIRMWARE_VERSION) + }) } async signMessage (withAccount, data) { -- cgit v1.2.3 From 999b6bd24a85068209a3213a4c9a83bf67854456 Mon Sep 17 00:00:00 2001 From: Bruno Date: Mon, 11 Jun 2018 01:58:19 -0400 Subject: clean up --- app/scripts/lib/trezorKeyring.js | 113 ++++++++------------------------------- 1 file changed, 21 insertions(+), 92 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/trezorKeyring.js b/app/scripts/lib/trezorKeyring.js index fa5d6070c..b0e5d31da 100644 --- a/app/scripts/lib/trezorKeyring.js +++ b/app/scripts/lib/trezorKeyring.js @@ -74,7 +74,7 @@ class TrezorKeyring extends EventEmitter { for (let i = from; i < to; i++) { - this.accounts.push(this.getEthAddress(pathBase, i)) + this.accounts.push(this._addressFromId(pathBase, i)) this.page = 0 } resolve(this.accounts) @@ -85,7 +85,7 @@ class TrezorKeyring extends EventEmitter { }) } - async getPage () { + getPage () { return new Promise((resolve, reject) => { return this.unlock() @@ -99,7 +99,7 @@ class TrezorKeyring extends EventEmitter { for (let i = from; i < to; i++) { accounts.push({ - address: this.getEthAddress(pathBase, i), + address: this._addressFromId(pathBase, i), balance: 0, index: i, }) @@ -127,22 +127,6 @@ class TrezorKeyring extends EventEmitter { return Promise.resolve(this.accounts.slice()) } - padLeftEven (hex) { - return hex.length % 2 !== 0 ? `0${hex}` : hex - } - - cleanData (buf) { - return this.padLeftEven(ethUtil.bufferToHex(buf).substring(2).toLowerCase()) - } - - getEthAddress (pathBase, i) { - const dkey = this.hdk.derive(`${pathBase}/${i}`) - const address = ethUtil - .publicToAddress(dkey.publicKey, true) - .toString('hex') - return ethUtil.toChecksumAddress(address) - } - // tx is an instance of the ethereumjs-transaction class. async signTransaction (address, tx) { @@ -152,12 +136,12 @@ class TrezorKeyring extends EventEmitter { const txData = { account, - nonce: this.cleanData(tx.nonce), - gasPrice: this.cleanData(tx.gasPrice), - gasLimit: this.cleanData(tx.gasLimit), - to: this.cleanData(tx.to), - value: this.cleanData(tx.value), - data: this.cleanData(tx.data), + nonce: this._cleanData(tx.nonce), + gasPrice: this._cleanData(tx.gasPrice), + gasLimit: this._cleanData(tx.gasLimit), + to: this._cleanData(tx.to), + value: this._cleanData(tx.value), + data: this._cleanData(tx.data), chainId: tx._chainId, } @@ -204,41 +188,12 @@ class TrezorKeyring extends EventEmitter { // For personal_sign, we need to prefix the message: async signPersonalMessage (withAccount, message) { + // Waiting on trezor to enable this throw new Error('Not supported on this device') - /* - await this.lock.acquire() - try { - // Look before we leap - await this._checkCorrectTrezorAttached() - - let accountId = await this._findAddressId(withAccount) - let eth = await this._getEth() - let msgHex = ethUtil.stripHexPrefix(message) - let TrezorSig = await eth.signPersonalMessage( - this._derivePath(accountId), - msgHex - ) - let signature = this._personalToRawSig(TrezorSig) - - // Since look before we leap check is racy, also check that signature is for account expected - let addressSignedWith = sigUtil.recoverPersonalSignature({ - data: message, - sig: signature, - }) - if (addressSignedWith.toLowerCase() !== withAccount.toLowerCase()) { - throw new Error( - `Signature is for ${addressSignedWith} but expected ${withAccount} - is the correct Trezor device attached?` - ) - } - - return signature - - } finally { - await this.lock.release() - } */ } async signTypedData (withAccount, typedData) { + // Waiting on trezor to enable this throw new Error('Not supported on this device') } @@ -246,46 +201,20 @@ class TrezorKeyring extends EventEmitter { throw new Error('Not supported on this device') } - async _findAddressId (addr) { - const result = this.accounts.indexOf(addr) - if (result === -1) throw new Error('Unknown address') - else return result - } - - async _addressFromId (i) { - /* Must be called with lock acquired - const eth = await this._getEth() - return (await eth.getAddress(this._derivePath(i))).address*/ - const result = this.accounts[i] - if (!result) throw new Error('Unknown address') - else return result - } - - async _checkCorrectTrezorAttached () { - return true - /* Must be called with lock acquired - if (this.accounts.length > 0) { - const expectedFirstAccount = this.accounts[0] - let actualFirstAccount = await this._addressFromId(0) - if (expectedFirstAccount !== actualFirstAccount) { - throw new Error( - `Incorrect Trezor device attached - expected device containg account ${expectedFirstAccount}, but found ${actualFirstAccount}` - ) - } - }*/ + _padLeftEven (hex) { + return hex.length % 2 !== 0 ? `0${hex}` : hex } - _derivePath (i) { - return this.hdPath + '/' + i + _cleanData (buf) { + return this._padLeftEven(ethUtil.bufferToHex(buf).substring(2).toLowerCase()) } - _personalToRawSig (TrezorSig) { - var v = TrezorSig['v'] - 27 - v = v.toString(16) - if (v.length < 2) { - v = '0' + v - } - return '0x' + TrezorSig['r'] + TrezorSig['s'] + v + _addressFromId(pathBase, i) { + const dkey = this.hdk.derive(`${pathBase}/${i}`) + const address = ethUtil + .publicToAddress(dkey.publicKey, true) + .toString('hex') + return ethUtil.toChecksumAddress(address) } } -- cgit v1.2.3 From d4201ae1cc990ba6b69e84586caabc0848c2c38e Mon Sep 17 00:00:00 2001 From: Bruno Date: Wed, 13 Jun 2018 00:22:04 -0400 Subject: added support for signPersonalMessage --- app/scripts/lib/trezorKeyring.js | 73 +++++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 28 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/trezorKeyring.js b/app/scripts/lib/trezorKeyring.js index b0e5d31da..6a483fdcd 100644 --- a/app/scripts/lib/trezorKeyring.js +++ b/app/scripts/lib/trezorKeyring.js @@ -1,6 +1,6 @@ const { EventEmitter } = require('events') const ethUtil = require('ethereumjs-util') -// const sigUtil = require('eth-sig-util') +const sigUtil = require('eth-sig-util') const hdPathString = `m/44'/60'/0'/0` const keyringType = 'Trezor Hardware' @@ -131,31 +131,21 @@ class TrezorKeyring extends EventEmitter { async signTransaction (address, tx) { return new Promise((resolve, reject) => { + log.debug('sign transaction ', address, tx) - const account = `m/44'/60'/0'/${this.unlockedAccount}` - - const txData = { - account, - nonce: this._cleanData(tx.nonce), - gasPrice: this._cleanData(tx.gasPrice), - gasLimit: this._cleanData(tx.gasLimit), - to: this._cleanData(tx.to), - value: this._cleanData(tx.value), - data: this._cleanData(tx.data), - chainId: tx._chainId, - } TrezorConnect.ethereumSignTx( - txData.account, - txData.nonce, - txData.gasPrice, - txData.gasLimit, - txData.to, - txData.value, - txData.data === '' ? null : txData.data, - txData.chainId, + this._getUnlockedAccount(), + this._normalize(tx.nonce), + this._normalize(tx.gasPrice), + this._normalize(tx.gasLimit), + this._normalize(tx.to), + this._normalize(tx.value), + this._normalize(tx.data), + tx._chainId, response => { if (response.success) { + tx.v = `0x${response.v.toString(16)}` tx.r = `0x${response.r}` tx.s = `0x${response.s}` @@ -163,13 +153,11 @@ class TrezorKeyring extends EventEmitter { const signedTx = new Transaction(tx) - log.debug('signature is valid?', signedTx.verifySignature()) - const addressSignedWith = ethUtil.toChecksumAddress(`0x${signedTx.from.toString('hex')}`) const correctAddress = ethUtil.toChecksumAddress(address) if (addressSignedWith !== correctAddress) { - // throw new Error('signature doesnt match the right address') log.error('signature doesnt match the right address', addressSignedWith, correctAddress) + throw new Error('signature doesnt match the right address') } resolve(signedTx) @@ -188,8 +176,24 @@ class TrezorKeyring extends EventEmitter { // For personal_sign, we need to prefix the message: async signPersonalMessage (withAccount, message) { - // Waiting on trezor to enable this - throw new Error('Not supported on this device') + + TrezorConnect.ethereumSignMessage(this._getUnlockedAccount(), message, response => { + if (response.success) { + + const signature = this._personalToRawSig(response.signature) + const addressSignedWith = sigUtil.recoverPersonalSignature({data: message, sig: signature}) + const correctAddress = ethUtil.toChecksumAddress(withAccount) + if (addressSignedWith !== correctAddress) { + log.error('signature doesnt match the right address', addressSignedWith, correctAddress) + throw new Error('signature doesnt match the right address') + } + return signature + + } else { + throw new Error(response.error || 'Unknown error') + } + + }, TREZOR_MIN_FIRMWARE_VERSION) } async signTypedData (withAccount, typedData) { @@ -205,17 +209,30 @@ class TrezorKeyring extends EventEmitter { return hex.length % 2 !== 0 ? `0${hex}` : hex } - _cleanData (buf) { + _normalize (buf) { return this._padLeftEven(ethUtil.bufferToHex(buf).substring(2).toLowerCase()) } - _addressFromId(pathBase, i) { + _addressFromId (pathBase, i) { const dkey = this.hdk.derive(`${pathBase}/${i}`) const address = ethUtil .publicToAddress(dkey.publicKey, true) .toString('hex') return ethUtil.toChecksumAddress(address) } + + _getUnlockedAccount () { + return `${this.hdPath}/${this.unlockedAccount}` + } + + _personalToRawSig (signature) { + var v = signature['v'] - 27 + v = v.toString(16) + if (v.length < 2) { + v = '0' + v + } + return '0x' + signature['r'] + signature['s'] + v + } } TrezorKeyring.type = keyringType -- cgit v1.2.3 From 8763ea898e7838d08315063b0e2181405a2ae3d5 Mon Sep 17 00:00:00 2001 From: Bruno Date: Wed, 13 Jun 2018 01:32:13 -0400 Subject: move TrezorKeyring to its own package --- app/scripts/lib/trezor-connect.js | 1138 ------------------------------------ app/scripts/lib/trezorKeyring.js | 239 -------- app/scripts/metamask-controller.js | 5 +- 3 files changed, 3 insertions(+), 1379 deletions(-) delete mode 100644 app/scripts/lib/trezor-connect.js delete mode 100644 app/scripts/lib/trezorKeyring.js (limited to 'app/scripts') diff --git a/app/scripts/lib/trezor-connect.js b/app/scripts/lib/trezor-connect.js deleted file mode 100644 index 574e88104..000000000 --- a/app/scripts/lib/trezor-connect.js +++ /dev/null @@ -1,1138 +0,0 @@ -/* eslint-disable */ -/* prettier-ignore */ - -/** - * (C) 2017 SatoshiLabs - * - * GPLv3 - */ -var TREZOR_CONNECT_VERSION = 4; - -if (!Array.isArray) { - Array.isArray = function(arg) { - return Object.prototype.toString.call(arg) === "[object Array]"; - }; -} - -var HD_HARDENED = 0x80000000; - -// react sometimes adds some other parameters that should not be there -function _fwStrFix(obj, fw) { - if (typeof fw === "string") { - obj.requiredFirmware = fw; - } - return obj; -} - -("use strict"); - -var chrome = window.chrome; -var IS_CHROME_APP = chrome && chrome.app && chrome.app.window; - -var ERR_TIMED_OUT = "Loading timed out"; -var ERR_WINDOW_CLOSED = "Window closed"; -var ERR_WINDOW_BLOCKED = "Window blocked"; -var ERR_ALREADY_WAITING = "Already waiting for a response"; -var ERR_CHROME_NOT_CONNECTED = "Internal Chrome popup is not responding."; - -var DISABLE_LOGIN_BUTTONS = window.TREZOR_DISABLE_LOGIN_BUTTONS || false; -var CHROME_URL = window.TREZOR_CHROME_URL || "./chrome/wrapper.html"; -var POPUP_ORIGIN = window.TREZOR_POPUP_ORIGIN || "https://connect.trezor.io"; -var POPUP_PATH = - window.TREZOR_POPUP_PATH || POPUP_ORIGIN + "/" + TREZOR_CONNECT_VERSION; -var POPUP_URL = - window.TREZOR_POPUP_URL || - POPUP_PATH + "/popup/popup.html?v=" + new Date().getTime(); - -var POPUP_INIT_TIMEOUT = 15000; - -/** - * Public API. - */ -function TrezorConnect() { - var manager = new PopupManager(); - - /** - * Popup errors. - */ - this.ERR_TIMED_OUT = ERR_TIMED_OUT; - this.ERR_WINDOW_CLOSED = ERR_WINDOW_CLOSED; - this.ERR_WINDOW_BLOCKED = ERR_WINDOW_BLOCKED; - this.ERR_ALREADY_WAITING = ERR_ALREADY_WAITING; - this.ERR_CHROME_NOT_CONNECTED = ERR_CHROME_NOT_CONNECTED; - - /** - * Open the popup for further communication. All API functions open the - * popup automatically, but if you need to generate some parameters - * asynchronously, use `open` first to avoid popup blockers. - * @param {function(?Error)} callback - */ - this.open = function(callback) { - var onchannel = function(result) { - if (result instanceof Error) { - callback(result); - } else { - callback(); - } - }; - manager.waitForChannel(onchannel); - }; - - /** - * Close the opened popup, if any. - */ - this.close = function() { - manager.close(); - }; - - /** - * Enable or disable closing the opened popup after a successful call. - * @param {boolean} value - */ - this.closeAfterSuccess = function(value) { - manager.closeAfterSuccess = value; - }; - - /** - * Enable or disable closing the opened popup after a failed call. - * @param {boolean} value - */ - this.closeAfterFailure = function(value) { - manager.closeAfterFailure = value; - }; - - /** - * Set bitcore server - * @param {string|Array} value - */ - this.setBitcoreURLS = function(value) { - if (typeof value === "string") { - manager.bitcoreURLS = [value]; - } else if (value instanceof Array) { - manager.bitcoreURLS = value; - } - }; - - /** - * Set currency. Human readable coin name - * @param {string|Array} value - */ - this.setCurrency = function(value) { - if (typeof value === "string") { - manager.currency = value; - } - }; - - /** - * Set currency units (mBTC, BTC) - * @param {string|Array} value - */ - this.setCurrencyUnits = function(value) { - if (typeof value === "string") { - manager.currencyUnits = value; - } - }; - - /** - * Set coin info json url - * @param {string|Array} value - */ - this.setCoinInfoURL = function(value) { - if (typeof value === "string") { - manager.coinInfoURL = value; - } - }; - - /** - * Set max. limit for account discovery - * @param {number} value - */ - this.setAccountDiscoveryLimit = function(value) { - if (!isNaN(value)) manager.accountDiscoveryLimit = value; - }; - - /** - * Set max. gap for account discovery - * @param {number} value - */ - this.setAccountDiscoveryGapLength = function(value) { - if (!isNaN(value)) manager.accountDiscoveryGapLength = value; - }; - - /** - * Set discovery BIP44 coin type - * @param {number} value - */ - this.setAccountDiscoveryBip44CoinType = function(value) { - if (!isNaN(value)) manager.accountDiscoveryBip44CoinType = value; - }; - - /** - * @typedef XPubKeyResult - * @param {boolean} success - * @param {?string} error - * @param {?string} xpubkey serialized extended public key - * @param {?string} path BIP32 serializd path of the key - */ - - /** - * Load BIP32 extended public key by path. - * - * Path can be specified either in the string form ("m/44'/1/0") or as - * raw integer array. In case you omit the path, user is asked to select - * a BIP32 account to export, and the result contains m/44'/0'/x' node - * of the account. - * - * @param {?(string|array)} path - * @param {function(XPubKeyResult)} callback - * @param {?(string|array)} requiredFirmware - */ - this.getXPubKey = function(path, callback, requiredFirmware) { - if (typeof path === "string") { - path = parseHDPath(path); - } - manager.sendWithChannel( - _fwStrFix( - { - type: "xpubkey", - path: path - }, - requiredFirmware - ), - callback - ); - }; - - this.getFreshAddress = function(callback, requiredFirmware) { - var wrapperCallback = function(result) { - if (result.success) { - callback({ success: true, address: result.freshAddress }); - } else { - callback(result); - } - }; - - manager.sendWithChannel( - _fwStrFix( - { - type: "accountinfo" - }, - requiredFirmware - ), - wrapperCallback - ); - }; - - this.getAccountInfo = function(input, callback, requiredFirmware) { - try { - manager.sendWithChannel( - _fwStrFix( - { - type: "accountinfo", - description: input - }, - requiredFirmware - ), - callback - ); - } catch (e) { - callback({ success: false, error: e }); - } - }; - - this.getAllAccountsInfo = function(callback, requiredFirmware) { - try { - manager.sendWithChannel( - _fwStrFix( - { - type: "allaccountsinfo", - description: "all" - }, - requiredFirmware - ), - callback - ); - } catch (e) { - callback({ success: false, error: e }); - } - }; - - this.getBalance = function(callback, requiredFirmware) { - manager.sendWithChannel( - _fwStrFix( - { - type: "accountinfo" - }, - requiredFirmware - ), - callback - ); - }; - - /** - * @typedef SignTxResult - * @param {boolean} success - * @param {?string} error - * @param {?string} serialized_tx serialized tx, in hex, including signatures - * @param {?array} signatures array of input signatures, in hex - */ - - /** - * Sign a transaction in the device and return both serialized - * transaction and the signatures. - * - * @param {array} inputs - * @param {array} outputs - * @param {function(SignTxResult)} callback - * @param {?(string|array)} requiredFirmware - * - * @see https://github.com/trezor/trezor-common/blob/master/protob/types.proto - */ - this.signTx = function(inputs, outputs, callback, requiredFirmware, coin) { - manager.sendWithChannel( - _fwStrFix( - { - type: "signtx", - inputs: inputs, - outputs: outputs, - coin: coin - }, - requiredFirmware - ), - callback - ); - }; - - // new implementation with ethereum at beginnig - this.ethereumSignTx = function() { - this.signEthereumTx.apply(this, arguments); - }; - - // old fallback - this.signEthereumTx = function( - address_n, - nonce, - gas_price, - gas_limit, - to, - value, - data, - chain_id, - callback, - requiredFirmware - ) { - if (requiredFirmware == null) { - requiredFirmware = "1.4.0"; // first firmware that supports ethereum - } - if (typeof address_n === "string") { - address_n = parseHDPath(address_n); - } - manager.sendWithChannel( - _fwStrFix( - { - type: "signethtx", - address_n: address_n, - nonce: nonce, - gas_price: gas_price, - gas_limit: gas_limit, - to: to, - value: value, - data: data, - chain_id: chain_id - }, - requiredFirmware - ), - callback - ); - }; - - /** - * @typedef TxRecipient - * @param {number} amount the amount to send, in satoshis - * @param {string} address the address of the recipient - */ - - /** - * Compose a transaction by doing BIP-0044 discovery, letting the user - * select an account, and picking UTXO by internal preferences. - * Transaction is then signed and returned in the same format as - * `signTx`. Only supports BIP-0044 accounts (single-signature). - * - * @param {array} recipients - * @param {function(SignTxResult)} callback - * @param {?(string|array)} requiredFirmware - */ - this.composeAndSignTx = function(recipients, callback, requiredFirmware) { - manager.sendWithChannel( - _fwStrFix( - { - type: "composetx", - recipients: recipients - }, - requiredFirmware - ), - callback - ); - }; - - /** - * @typedef RequestLoginResult - * @param {boolean} success - * @param {?string} error - * @param {?string} public_key public key used for signing, in hex - * @param {?string} signature signature, in hex - */ - - /** - * Sign a login challenge for active origin. - * - * @param {?string} hosticon - * @param {string} challenge_hidden - * @param {string} challenge_visual - * @param {string|function(RequestLoginResult)} callback - * @param {?(string|array)} requiredFirmware - * - * @see https://github.com/trezor/trezor-common/blob/master/protob/messages.proto - */ - this.requestLogin = function( - hosticon, - challenge_hidden, - challenge_visual, - callback, - requiredFirmware - ) { - if (typeof callback === "string") { - // special case for a login through button. - // `callback` is name of global var - callback = window[callback]; - } - if (!callback) { - throw new TypeError("TrezorConnect: login callback not found"); - } - manager.sendWithChannel( - _fwStrFix( - { - type: "login", - icon: hosticon, - challenge_hidden: challenge_hidden, - challenge_visual: challenge_visual - }, - requiredFirmware - ), - callback - ); - }; - - /** - * @typedef SignMessageResult - * @param {boolean} success - * @param {?string} error - * @param {?string} address address (in base58check) - * @param {?string} signature signature, in base64 - */ - - /** - * Sign a message - * - * @param {string|array} path - * @param {string} message to sign (ascii) - * @param {string|function(SignMessageResult)} callback - * @param {?string} opt_coin - (optional) name of coin (default Bitcoin) - * @param {?(string|array)} requiredFirmware - * - */ - this.signMessage = function( - path, - message, - callback, - opt_coin, - requiredFirmware - ) { - if (typeof path === "string") { - path = parseHDPath(path); - } - if (!opt_coin) { - opt_coin = "Bitcoin"; - } - if (!callback) { - throw new TypeError("TrezorConnect: callback not found"); - } - manager.sendWithChannel( - _fwStrFix( - { - type: "signmsg", - path: path, - message: message, - coin: opt_coin - }, - requiredFirmware - ), - callback - ); - }; - - /** - * Sign an Ethereum message - * - * @param {string|array} path - * @param {string} message to sign (ascii) - * @param {string|function(SignMessageResult)} callback - * @param {?(string|array)} requiredFirmware - * - */ - this.ethereumSignMessage = function( - path, - message, - callback, - requiredFirmware - ) { - if (typeof path === "string") { - path = parseHDPath(path); - } - if (!callback) { - throw new TypeError("TrezorConnect: callback not found"); - } - manager.sendWithChannel( - _fwStrFix( - { - type: "signethmsg", - path: path, - message: message - }, - requiredFirmware - ), - callback - ); - }; - - /** - * Verify message - * - * @param {string} address - * @param {string} signature (base64) - * @param {string} message (string) - * @param {string|function()} callback - * @param {?string} opt_coin - (optional) name of coin (default Bitcoin) - * @param {?(string|array)} requiredFirmware - * - */ - this.verifyMessage = function( - address, - signature, - message, - callback, - opt_coin, - requiredFirmware - ) { - if (!opt_coin) { - opt_coin = "Bitcoin"; - } - if (!callback) { - throw new TypeError("TrezorConnect: callback not found"); - } - manager.sendWithChannel( - _fwStrFix( - { - type: "verifymsg", - address: address, - signature: signature, - message: message, - coin: { coin_name: opt_coin } - }, - requiredFirmware - ), - callback - ); - }; - - /** - * Verify ethereum message - * - * @param {string} address - * @param {string} signature (base64) - * @param {string} message (string) - * @param {string|function()} callback - * @param {?(string|array)} requiredFirmware - * - */ - this.ethereumVerifyMessage = function( - address, - signature, - message, - callback, - requiredFirmware - ) { - if (!callback) { - throw new TypeError("TrezorConnect: callback not found"); - } - manager.sendWithChannel( - _fwStrFix( - { - type: "verifyethmsg", - address: address, - signature: signature, - message: message - }, - requiredFirmware - ), - callback - ); - }; - - /** - * Symmetric key-value encryption - * - * @param {string|array} path - * @param {string} key to show on device display - * @param {string} value hexadecimal value, length a multiple of 16 bytes - * @param {boolean} encrypt / decrypt direction - * @param {boolean} ask_on_encrypt (should user confirm on encrypt?) - * @param {boolean} ask_on_decrypt (should user confirm on decrypt?) - * @param {string|function()} callback - * @param {?(string|array)} requiredFirmware - * - */ - this.cipherKeyValue = function( - path, - key, - value, - encrypt, - ask_on_encrypt, - ask_on_decrypt, - callback, - requiredFirmware - ) { - if (typeof path === "string") { - path = parseHDPath(path); - } - if (typeof value !== "string") { - throw new TypeError("TrezorConnect: Value must be a string"); - } - if (!/^[0-9A-Fa-f]*$/.test(value)) { - throw new TypeError("TrezorConnect: Value must be hexadecimal"); - } - if (value.length % 32 !== 0) { - // 1 byte == 2 hex strings - throw new TypeError( - "TrezorConnect: Value length must be multiple of 16 bytes" - ); - } - if (!callback) { - throw new TypeError("TrezorConnect: callback not found"); - } - manager.sendWithChannel( - _fwStrFix( - { - type: "cipherkeyvalue", - path: path, - key: key, - value: value, - encrypt: !!encrypt, - ask_on_encrypt: !!ask_on_encrypt, - ask_on_decrypt: !!ask_on_decrypt - }, - requiredFirmware - ), - callback - ); - }; - - this.nemGetAddress = function( - address_n, - network, - callback, - requiredFirmware - ) { - if (requiredFirmware == null) { - requiredFirmware = "1.6.0"; // first firmware that supports NEM - } - if (typeof address_n === "string") { - address_n = parseHDPath(address_n); - } - manager.sendWithChannel( - _fwStrFix( - { - type: "nemGetAddress", - address_n: address_n, - network: network - }, - requiredFirmware - ), - callback - ); - }; - - this.nemSignTx = function( - address_n, - transaction, - callback, - requiredFirmware - ) { - if (requiredFirmware == null) { - requiredFirmware = "1.6.0"; // first firmware that supports NEM - } - if (typeof address_n === "string") { - address_n = parseHDPath(address_n); - } - manager.sendWithChannel( - _fwStrFix( - { - type: "nemSignTx", - address_n: address_n, - transaction: transaction - }, - requiredFirmware - ), - callback - ); - }; - - this.pushTransaction = function(rawTx, callback) { - if (!/^[0-9A-Fa-f]*$/.test(rawTx)) { - throw new TypeError("TrezorConnect: Transaction must be hexadecimal"); - } - if (!callback) { - throw new TypeError("TrezorConnect: callback not found"); - } - - manager.sendWithChannel( - { - type: "pushtx", - rawTx: rawTx - }, - callback - ); - }; - - /** - * Display address on device - * - * @param {array} address - * @param {string} coin - * @param {boolean} segwit - * @param {?(string|array)} requiredFirmware - * - */ - this.getAddress = function( - address, - coin, - segwit, - callback, - requiredFirmware - ) { - if (typeof address === "string") { - address = parseHDPath(address); - } - - manager.sendWithChannel( - _fwStrFix( - { - type: "getaddress", - address_n: address, - coin: coin, - segwit: segwit - }, - requiredFirmware - ), - callback - ); - }; - - /** - * Display ethereum address on device - * - * @param {array} address - * @param {?(string|array)} requiredFirmware - * - */ - this.ethereumGetAddress = function(address, callback, requiredFirmware) { - if (typeof address === "string") { - address = parseHDPath(address); - } - - manager.sendWithChannel( - _fwStrFix( - { - type: "ethgetaddress", - address_n: address - }, - requiredFirmware - ), - callback - ); - }; - - var LOGIN_CSS = - ''; - - var LOGIN_ONCLICK = - "TrezorConnect.requestLogin(" + - "'@hosticon@','@challenge_hidden@','@challenge_visual@','@callback@'" + - ")"; - - var LOGIN_HTML = - '
' + - ' ' + - ' ' + - ' @text@' + - " " + - ' ' + - ' What is TREZOR?' + - " " + - "
"; - - /** - * Find elements and replace them with login buttons. - * It's not required to use these special elements, feel free to call - * `TrezorConnect.requestLogin` directly. - */ - this.renderLoginButtons = function() { - var elements = document.getElementsByTagName("trezor:login"); - - for (var i = 0; i < elements.length; i++) { - var e = elements[i]; - var text = e.getAttribute("text") || "Sign in with TREZOR"; - var callback = e.getAttribute("callback") || ""; - var hosticon = e.getAttribute("icon") || ""; - var challenge_hidden = e.getAttribute("challenge_hidden") || ""; - var challenge_visual = e.getAttribute("challenge_visual") || ""; - - // it's not valid to put markup into attributes, so let users - // supply a raw text and make TREZOR bold - text = text.replace("TREZOR", "TREZOR"); - e.outerHTML = (LOGIN_CSS + LOGIN_HTML) - .replace("@text@", text) - .replace("@callback@", callback) - .replace("@hosticon@", hosticon) - .replace("@challenge_hidden@", challenge_hidden) - .replace("@challenge_visual@", challenge_visual) - .replace("@connect_path@", POPUP_PATH); - } - }; -} - -/* - * `getXPubKey()` - */ - -function parseHDPath(string) { - return string - .toLowerCase() - .split("/") - .filter(function(p) { - return p !== "m"; - }) - .map(function(p) { - var hardened = false; - if (p[p.length - 1] === "'") { - hardened = true; - p = p.substr(0, p.length - 1); - } - if (isNaN(p)) { - throw new Error("Not a valid path."); - } - var n = parseInt(p); - if (hardened) { - // hardened index - n = (n | 0x80000000) >>> 0; - } - return n; - }); -} - -/* - * Popup management - */ - -function ChromePopup(url, name, width, height) { - var left = (screen.width - width) / 2; - var top = (screen.height - height) / 2; - var opts = { - id: name, - innerBounds: { - width: width, - height: height, - left: left, - top: top - } - }; - - var closed = function() { - if (this.onclose) { - this.onclose(false); // never report as blocked - } - }.bind(this); - - var opened = function(w) { - this.window = w; - this.window.onClosed.addListener(closed); - }.bind(this); - - chrome.app.window.create(url, opts, opened); - - this.name = name; - this.window = null; - this.onclose = null; -} - -function ChromeChannel(popup, waiting) { - var port = null; - - var respond = function(data) { - if (waiting) { - var w = waiting; - waiting = null; - w(data); - } - }; - - var setup = function(p) { - if (p.name === popup.name) { - port = p; - port.onMessage.addListener(respond); - chrome.runtime.onConnect.removeListener(setup); - } - }; - - chrome.runtime.onConnect.addListener(setup); - - this.respond = respond; - - this.close = function() { - chrome.runtime.onConnect.removeListener(setup); - port.onMessage.removeListener(respond); - port.disconnect(); - port = null; - }; - - this.send = function(value, callback) { - if (waiting === null) { - waiting = callback; - - if (port) { - port.postMessage(value); - } else { - throw new Error(ERR_CHROME_NOT_CONNECTED); - } - } else { - throw new Error(ERR_ALREADY_WAITING); - } - }; -} - -function Popup(url, origin, name, width, height) { - var left = (screen.width - width) / 2; - var top = (screen.height - height) / 2; - var opts = - "width=" + - width + - ",height=" + - height + - ",left=" + - left + - ",top=" + - top + - ",menubar=no" + - ",toolbar=no" + - ",location=no" + - ",personalbar=no" + - ",status=no"; - var w = window.open(url, name, opts); - - var interval; - var blocked = w.closed; - var iterate = function() { - if (w.closed) { - clearInterval(interval); - if (this.onclose) { - this.onclose(blocked); - } - } - }.bind(this); - interval = setInterval(iterate, 100); - - this.window = w; - this.origin = origin; - this.onclose = null; -} - -function Channel(popup, waiting) { - var respond = function(data) { - if (waiting) { - var w = waiting; - waiting = null; - w(data); - } - }; - - var receive = function(event) { - var org1 = event.origin.match(/^.+\:\/\/[^\‌​/]+/)[0]; - var org2 = popup.origin.match(/^.+\:\/\/[^\‌​/]+/)[0]; - //if (event.source === popup.window && event.origin === popup.origin) { - if (event.source === popup.window && org1 === org2) { - respond(event.data); - } - }; - - window.addEventListener("message", receive); - - this.respond = respond; - - this.close = function() { - window.removeEventListener("message", receive); - }; - - this.send = function(value, callback) { - if (waiting === null) { - waiting = callback; - popup.window.postMessage(value, popup.origin); - } else { - throw new Error(ERR_ALREADY_WAITING); - } - }; -} - -function ConnectedChannel(p) { - var ready = function() { - clearTimeout(this.timeout); - this.popup.onclose = null; - this.ready = true; - this.onready(); - }.bind(this); - - var closed = function(blocked) { - clearTimeout(this.timeout); - this.channel.close(); - if (blocked) { - this.onerror(new Error(ERR_WINDOW_BLOCKED)); - } else { - this.onerror(new Error(ERR_WINDOW_CLOSED)); - } - }.bind(this); - - var timedout = function() { - this.popup.onclose = null; - if (this.popup.window) { - this.popup.window.close(); - } - this.channel.close(); - this.onerror(new Error(ERR_TIMED_OUT)); - }.bind(this); - - if (IS_CHROME_APP) { - this.popup = new ChromePopup(p.chromeUrl, p.name, p.width, p.height); - this.channel = new ChromeChannel(this.popup, ready); - } else { - this.popup = new Popup(p.url, p.origin, p.name, p.width, p.height); - this.channel = new Channel(this.popup, ready); - } - - this.timeout = setTimeout(timedout, POPUP_INIT_TIMEOUT); - - this.popup.onclose = closed; - - this.ready = false; - this.onready = null; - this.onerror = null; -} - -function PopupManager() { - var cc = null; - - var closed = function() { - cc.channel.respond(new Error(ERR_WINDOW_CLOSED)); - cc.channel.close(); - cc = null; - }; - - var open = function(callback) { - cc = new ConnectedChannel({ - name: "trezor-connect", - width: 600, - height: 500, - origin: POPUP_ORIGIN, - path: POPUP_PATH, - url: POPUP_URL, - chromeUrl: CHROME_URL - }); - cc.onready = function() { - cc.popup.onclose = closed; - callback(cc.channel); - }; - cc.onerror = function(error) { - cc = null; - callback(error); - }; - }.bind(this); - - this.closeAfterSuccess = true; - this.closeAfterFailure = true; - - this.close = function() { - if (cc && cc.popup.window) { - cc.popup.window.close(); - } - }; - - this.waitForChannel = function(callback) { - if (cc) { - if (cc.ready) { - callback(cc.channel); - } else { - callback(new Error(ERR_ALREADY_WAITING)); - } - } else { - try { - open(callback); - } catch (e) { - callback(new Error(ERR_WINDOW_BLOCKED)); - } - } - }; - - this.sendWithChannel = function(message, callback) { - message.bitcoreURLS = this.bitcoreURLS || null; - message.currency = this.currency || null; - message.currencyUnits = this.currencyUnits || null; - message.coinInfoURL = this.coinInfoURL || null; - message.accountDiscoveryLimit = this.accountDiscoveryLimit || null; - message.accountDiscoveryGapLength = this.accountDiscoveryGapLength || null; - message.accountDiscoveryBip44CoinType = - this.accountDiscoveryBip44CoinType || null; - - var respond = function(response) { - var succ = response.success && this.closeAfterSuccess; - var fail = !response.success && this.closeAfterFailure; - if (succ || fail) { - this.close(); - } - callback(response); - }.bind(this); - - var onresponse = function(response) { - if (response instanceof Error) { - var error = response; - respond({ success: false, error: error.message }); - } else { - respond(response); - } - }; - - var onchannel = function(channel) { - if (channel instanceof Error) { - var error = channel; - respond({ success: false, error: error.message }); - } else { - channel.send(message, onresponse); - } - }; - - this.waitForChannel(onchannel); - }; -} - -const connect = new TrezorConnect(); -module.exports = connect; \ No newline at end of file diff --git a/app/scripts/lib/trezorKeyring.js b/app/scripts/lib/trezorKeyring.js deleted file mode 100644 index 6a483fdcd..000000000 --- a/app/scripts/lib/trezorKeyring.js +++ /dev/null @@ -1,239 +0,0 @@ -const { EventEmitter } = require('events') -const ethUtil = require('ethereumjs-util') -const sigUtil = require('eth-sig-util') - -const hdPathString = `m/44'/60'/0'/0` -const keyringType = 'Trezor Hardware' -const Transaction = require('ethereumjs-tx') -const pathBase = 'm' -const TrezorConnect = require('./trezor-connect.js') -const HDKey = require('hdkey') -const TREZOR_MIN_FIRMWARE_VERSION = '1.5.2' -const log = require('loglevel') - -class TrezorKeyring extends EventEmitter { - constructor (opts = {}) { - super() - this.type = keyringType - this.accounts = [] - this.hdk = new HDKey() - this.deserialize(opts) - this.page = 0 - this.perPage = 5 - this.unlockedAccount = 0 - } - - serialize () { - return Promise.resolve({ - hdPath: this.hdPath, - accounts: this.accounts, - page: this.page, - }) - } - - deserialize (opts = {}) { - this.hdPath = opts.hdPath || hdPathString - this.accounts = opts.accounts || [] - this.page = opts.page || 0 - return Promise.resolve() - } - - unlock () { - - if (this.hdk.publicKey) return Promise.resolve() - - return new Promise((resolve, reject) => { - TrezorConnect.getXPubKey( - this.hdPath, - response => { - if (response.success) { - this.hdk.publicKey = new Buffer(response.publicKey, 'hex') - this.hdk.chainCode = new Buffer(response.chainCode, 'hex') - resolve() - } else { - reject(response.error || 'Unknown error') - } - }, - TREZOR_MIN_FIRMWARE_VERSION - ) - }) - } - - setAccountToUnlock (index) { - this.unlockedAccount = parseInt(index, 10) - } - - addAccounts (n = 1) { - - return new Promise((resolve, reject) => { - return this.unlock() - .then(_ => { - const from = this.unlockedAccount - const to = from + 1 - this.accounts = [] - - for (let i = from; i < to; i++) { - - this.accounts.push(this._addressFromId(pathBase, i)) - this.page = 0 - } - resolve(this.accounts) - }) - .catch(e => { - reject(e) - }) - }) - } - - getPage () { - - return new Promise((resolve, reject) => { - return this.unlock() - .then(_ => { - - const from = this.page === 0 ? 0 : (this.page - 1) * this.perPage - const to = from + this.perPage - - const accounts = [] - - for (let i = from; i < to; i++) { - - accounts.push({ - address: this._addressFromId(pathBase, i), - balance: 0, - index: i, - }) - } - log.debug(accounts) - resolve(accounts) - }) - .catch(e => { - reject(e) - }) - }) - } - - async getPrevAccountSet () { - this.page-- - return await this.getPage() - } - - async getNextAccountSet () { - this.page++ - return await this.getPage() - } - - getAccounts () { - return Promise.resolve(this.accounts.slice()) - } - - // tx is an instance of the ethereumjs-transaction class. - async signTransaction (address, tx) { - - return new Promise((resolve, reject) => { - - log.debug('sign transaction ', address, tx) - - TrezorConnect.ethereumSignTx( - this._getUnlockedAccount(), - this._normalize(tx.nonce), - this._normalize(tx.gasPrice), - this._normalize(tx.gasLimit), - this._normalize(tx.to), - this._normalize(tx.value), - this._normalize(tx.data), - tx._chainId, - response => { - if (response.success) { - - tx.v = `0x${response.v.toString(16)}` - tx.r = `0x${response.r}` - tx.s = `0x${response.s}` - log.debug('about to create new tx with data', tx) - - const signedTx = new Transaction(tx) - - const addressSignedWith = ethUtil.toChecksumAddress(`0x${signedTx.from.toString('hex')}`) - const correctAddress = ethUtil.toChecksumAddress(address) - if (addressSignedWith !== correctAddress) { - log.error('signature doesnt match the right address', addressSignedWith, correctAddress) - throw new Error('signature doesnt match the right address') - } - - resolve(signedTx) - - } else { - throw new Error(response.error || 'Unknown error') - } - }, - TREZOR_MIN_FIRMWARE_VERSION) - }) - } - - async signMessage (withAccount, data) { - throw new Error('Not supported on this device') - } - - // For personal_sign, we need to prefix the message: - async signPersonalMessage (withAccount, message) { - - TrezorConnect.ethereumSignMessage(this._getUnlockedAccount(), message, response => { - if (response.success) { - - const signature = this._personalToRawSig(response.signature) - const addressSignedWith = sigUtil.recoverPersonalSignature({data: message, sig: signature}) - const correctAddress = ethUtil.toChecksumAddress(withAccount) - if (addressSignedWith !== correctAddress) { - log.error('signature doesnt match the right address', addressSignedWith, correctAddress) - throw new Error('signature doesnt match the right address') - } - return signature - - } else { - throw new Error(response.error || 'Unknown error') - } - - }, TREZOR_MIN_FIRMWARE_VERSION) - } - - async signTypedData (withAccount, typedData) { - // Waiting on trezor to enable this - throw new Error('Not supported on this device') - } - - async exportAccount (address) { - throw new Error('Not supported on this device') - } - - _padLeftEven (hex) { - return hex.length % 2 !== 0 ? `0${hex}` : hex - } - - _normalize (buf) { - return this._padLeftEven(ethUtil.bufferToHex(buf).substring(2).toLowerCase()) - } - - _addressFromId (pathBase, i) { - const dkey = this.hdk.derive(`${pathBase}/${i}`) - const address = ethUtil - .publicToAddress(dkey.publicKey, true) - .toString('hex') - return ethUtil.toChecksumAddress(address) - } - - _getUnlockedAccount () { - return `${this.hdPath}/${this.unlockedAccount}` - } - - _personalToRawSig (signature) { - var v = signature['v'] - 27 - v = v.toString(16) - if (v.length < 2) { - v = '0' + v - } - return '0x' + signature['r'] + signature['s'] + v - } -} - -TrezorKeyring.type = keyringType -module.exports = TrezorKeyring diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index daab5baa5..abe7ff8a2 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -48,7 +48,7 @@ const seedPhraseVerifier = require('./lib/seed-phrase-verifier') const cleanErrorStack = require('./lib/cleanErrorStack') const DiagnosticsReporter = require('./lib/diagnostics-reporter') const log = require('loglevel') -const TrezorKeyring = require('./lib/trezorKeyring') +const TrezorKeyring = require('eth-trezor-keyring') module.exports = class MetamaskController extends EventEmitter { @@ -549,7 +549,8 @@ module.exports = class MetamaskController extends EventEmitter { throw new Error('MetamaskController - No Trezor Hardware Keyring found') } - const accounts = page === -1 ? await keyring.getPrevAccountSet(this.provider) : await keyring.getNextAccountSet(this.provider) + const accounts = await keyring.getPage(page) + this.accountTracker.syncWithAddresses(accounts.map(a => a.address)) return accounts -- cgit v1.2.3 From 704e2a21f8a3fc5f3d6245c5a924cd2df0cfd36e Mon Sep 17 00:00:00 2001 From: Bruno Date: Wed, 13 Jun 2018 02:09:25 -0400 Subject: clean up --- app/scripts/metamask-controller.js | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 6c380fd71..c57b643bb 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -131,7 +131,7 @@ module.exports = class MetamaskController extends EventEmitter { provider: this.provider, blockTracker: this.blockTracker, }) - + // key mgmt const additionalKeyrings = [TrezorKeyring] this.keyringController = new KeyringController({ @@ -423,7 +423,6 @@ module.exports = class MetamaskController extends EventEmitter { } - //============================================================================= // VAULT / KEYRING RELATED METHODS //============================================================================= @@ -537,19 +536,23 @@ module.exports = class MetamaskController extends EventEmitter { */ async connectHardware (deviceName, page) { - const keyringController = this.keyringController - const keyring = await keyringController.getKeyringsByType( - 'Trezor Hardware' - )[0] - if (!keyring) { - throw new Error('MetamaskController - No Trezor Hardware Keyring found') - } - - const accounts = await keyring.getPage(page) + switch (deviceName) { + case 'trezor': + const keyringController = this.keyringController + const keyring = await keyringController.getKeyringsByType( + 'Trezor Hardware' + )[0] + if (!keyring) { + throw new Error('MetamaskController - No Trezor Hardware Keyring found') + } - this.accountTracker.syncWithAddresses(accounts.map(a => a.address)) + const accounts = await keyring.getPage(page) + this.accountTracker.syncWithAddresses(accounts.map(a => a.address)) + return accounts - return accounts + default: + throw new Error('MetamaskController - Unknown device') + } } /** @@ -581,7 +584,7 @@ module.exports = class MetamaskController extends EventEmitter { const { identities } = this.preferencesController.store.getState() return { ...keyState, identities } } - + // // Account Management @@ -1037,7 +1040,7 @@ module.exports = class MetamaskController extends EventEmitter { * Allows a user to begin the seed phrase recovery process. * @param {Function} cb - A callback function called when complete. */ - markPasswordForgotten(cb) { + markPasswordForgotten (cb) { this.configManager.setPasswordForgotten(true) this.sendUpdate() cb() @@ -1047,7 +1050,7 @@ module.exports = class MetamaskController extends EventEmitter { * Allows a user to end the seed phrase recovery process. * @param {Function} cb - A callback function called when complete. */ - unMarkPasswordForgotten(cb) { + unMarkPasswordForgotten (cb) { this.configManager.setPasswordForgotten(false) this.sendUpdate() cb() -- cgit v1.2.3 From 87dfca07676f7a4745f68d2331a78f3ae53c558f Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Sat, 23 Jun 2018 02:52:11 -0400 Subject: fixes --- app/scripts/metamask-controller.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index c57b643bb..943904e4c 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -539,14 +539,14 @@ module.exports = class MetamaskController extends EventEmitter { switch (deviceName) { case 'trezor': const keyringController = this.keyringController - const keyring = await keyringController.getKeyringsByType( + let keyring = await keyringController.getKeyringsByType( 'Trezor Hardware' )[0] if (!keyring) { - throw new Error('MetamaskController - No Trezor Hardware Keyring found') + keyring = await this.keyringController.addNewKeyring('Trezor Hardware') } - const accounts = await keyring.getPage(page) + const accounts = page === 1 ? await keyring.getNextPage() : await keyring.getPreviousPage() this.accountTracker.syncWithAddresses(accounts.map(a => a.address)) return accounts -- cgit v1.2.3 From 7d3da0ae96a761461ee12bf20d8386a5ba7aade2 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Mon, 25 Jun 2018 11:45:00 -0700 Subject: migration 27 - remove rejected transactions from state --- app/scripts/migrations/027.js | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 app/scripts/migrations/027.js (limited to 'app/scripts') diff --git a/app/scripts/migrations/027.js b/app/scripts/migrations/027.js new file mode 100644 index 000000000..d6ebef580 --- /dev/null +++ b/app/scripts/migrations/027.js @@ -0,0 +1,35 @@ +// next version number +const version = 27 + +/* + +normalizes txParams on unconfirmed txs + +*/ +const clone = require('clone') + +module.exports = { + version, + + migrate: async function (originalVersionedData) { + const versionedData = clone(originalVersionedData) + versionedData.meta.version = version + const state = versionedData.data + const newState = transformState(state) + versionedData.data = newState + return versionedData + }, +} + +function transformState (state) { + const newState = state + + if (newState.TransactionController) { + if (newState.TransactionController.transactions) { + const transactions = newState.TransactionController.transactions + newState.TransactionController.transactions = transactions.filter((txMeta) => txMeta.status !== 'rejected') + } + } + + return newState +} -- cgit v1.2.3 From dd18684a5d682f48528f278284619983f13bf82f Mon Sep 17 00:00:00 2001 From: tmashuang Date: Mon, 25 Jun 2018 15:01:14 -0700 Subject: Remove unsued nonce tracker function --- app/scripts/controllers/transactions/nonce-tracker.js | 13 ------------- 1 file changed, 13 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/transactions/nonce-tracker.js b/app/scripts/controllers/transactions/nonce-tracker.js index 35ca08d6c..06f336eaa 100644 --- a/app/scripts/controllers/transactions/nonce-tracker.js +++ b/app/scripts/controllers/transactions/nonce-tracker.js @@ -129,19 +129,6 @@ class NonceTracker { return Number.isInteger(highest) ? highest + 1 : 0 } - _reduceTxListToUniqueNonces (txList) { - const reducedTxList = txList.reduce((reducedList, txMeta, index) => { - if (!index) return [txMeta] - const nonceMatches = txList.filter((txData) => { - return txMeta.txParams.nonce === txData.txParams.nonce - }) - if (nonceMatches.length > 1) return reducedList - reducedList.push(txMeta) - return reducedList - }, []) - return reducedTxList - } - _getHighestNonce (txList) { const nonces = txList.map((txMeta) => { const nonce = txMeta.txParams.nonce -- cgit v1.2.3 From 9b92ba4c47d5489c5c0f822045360be708110cac Mon Sep 17 00:00:00 2001 From: frankiebee Date: Mon, 25 Jun 2018 15:07:54 -0700 Subject: trandsactions - remove rejected transactions from history --- app/scripts/controllers/transactions/tx-state-manager.js | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'app/scripts') diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js index 0aae4774b..28a18ca2e 100644 --- a/app/scripts/controllers/transactions/tx-state-manager.js +++ b/app/scripts/controllers/transactions/tx-state-manager.js @@ -288,6 +288,7 @@ class TransactionStateManager extends EventEmitter { */ setTxStatusRejected (txId) { this._setTxStatus(txId, 'rejected') + this._removeTx(txId) } /** @@ -422,6 +423,11 @@ class TransactionStateManager extends EventEmitter { _saveTxList (transactions) { this.store.updateState({ transactions }) } + + _removeTx (txId) { + const transactionList = this.getFullTxList() + this._saveTxList(transactionList.filter((txMeta) => txMeta.id !== txId)) + } } module.exports = TransactionStateManager -- cgit v1.2.3 From bfac9c2c2dfd0c5f55cb794214791cf78cca75c0 Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Wed, 27 Jun 2018 16:29:24 -0400 Subject: detect tokens polling --- app/scripts/controllers/detect-tokens.js | 97 ++++++++++++++++++++++++++++++++ app/scripts/metamask-controller.js | 8 +++ 2 files changed, 105 insertions(+) create mode 100644 app/scripts/controllers/detect-tokens.js (limited to 'app/scripts') diff --git a/app/scripts/controllers/detect-tokens.js b/app/scripts/controllers/detect-tokens.js new file mode 100644 index 000000000..a7ddace19 --- /dev/null +++ b/app/scripts/controllers/detect-tokens.js @@ -0,0 +1,97 @@ +const ObservableStore = require('obs-store') +const { warn } = require('loglevel') +const contracts = require('eth-contract-metadata') +const { + ROPSTEN, + RINKEBY, + KOVAN, + MAINNET, + OCALHOST, + } = require('./network/enums') + +// By default, poll every 3 minutes +const DEFAULT_INTERVAL = 180 * 1000 + +/** + * A controller that polls for token exchange + * rates based on a user's current token list + */ +class DetectTokensController { + /** + * Creates a DetectTokensController + * + * @param {Object} [config] - Options to configure controller + */ + constructor ({ interval = DEFAULT_INTERVAL, preferences, network } = {}) { + this.preferences = preferences + this.interval = interval + this.network = network + } + + /** + * For each token in eth-contract=metada, find check selectedAddress balance. + * + */ + async exploreNewTokens () { + if (!this.isActive) { return } + if (this._network.getProviderConfig().type !== MAINNET) { return } + var tokens = this._preferences.store.getState().tokens + let detectedTokenAddress, token + for (const address in contracts) { + const contract = contracts[address] + if (contract.erc20 && !(address in tokens)) { + detectedTokenAddress = await this.fetchContractAccountBalance(address) + if (detectedTokenAddress) { + token = contracts[detectedTokenAddress] + this._preferences.addToken(detectedTokenAddress, token['symbol'], token['decimals']) + } + } + // etherscan restriction, 5 request/second, lazy scan + setTimeout(() => {}, 200) + } + } + + /** + * Find if selectedAddress has tokens with contract in contractAddress. + * + * @param {string} contractAddress Hex address of the token contract to explore. + * @returns {string} Contract address to be added to tokens. + * + */ + async fetchContractAccountBalance (contractAddress) { + const address = this._preferences.store.getState().selectedAddress + const response = await fetch(`https://api.etherscan.io/api?module=account&action=tokenbalance&contractaddress=${contractAddress}&address=${address}&tag=latest&apikey=NCKS6GTY41KPHWRJB62ES1MDNRBIT174PV`) + const parsedResponse = await response.json() + if (parsedResponse.result !== '0') { + return contractAddress + } + return null + } + + /** + * @type {Number} + */ + set interval (interval) { + this._handle && clearInterval(this._handle) + if (!interval) { return } + this._handle = setInterval(() => { this.exploreNewTokens() }, interval) + } + + /** + * @type {Object} + */ + set preferences (preferences) { + if (!preferences) { return } + this._preferences = preferences + } + + /** + * @type {Object} + */ + set network (network) { + if (!network) { return } + this._network = network + } +} + +module.exports = DetectTokensController diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index d40a351a5..37a31a28c 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -35,6 +35,7 @@ const TypedMessageManager = require('./lib/typed-message-manager') const TransactionController = require('./controllers/transactions') const BalancesController = require('./controllers/computed-balances') const TokenRatesController = require('./controllers/token-rates') +const DetectTokensController = require('./controllers/detect-tokens') const ConfigManager = require('./lib/config-manager') const nodeify = require('./lib/nodeify') const accountImporter = require('./account-import-strategies') @@ -112,6 +113,12 @@ module.exports = class MetamaskController extends EventEmitter { preferences: this.preferencesController.store, }) + // detect tokens controller + this.detectTokensController = new DetectTokensController({ + preferences: this.preferencesController, + network: this.networkController, + }) + this.recentBlocksController = new RecentBlocksController({ blockTracker: this.blockTracker, provider: this.provider, @@ -1276,5 +1283,6 @@ module.exports = class MetamaskController extends EventEmitter { */ set isClientOpenAndUnlocked (active) { this.tokenRatesController.isActive = active + this.detectTokensController.isActive = active } } -- cgit v1.2.3 From 0e863d5fab00eb83c908fb49f2939534bdfe1162 Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Wed, 27 Jun 2018 19:54:43 -0400 Subject: network store to detect token --- app/scripts/controllers/detect-tokens.js | 18 ++++++++++-------- app/scripts/metamask-controller.js | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/detect-tokens.js b/app/scripts/controllers/detect-tokens.js index a7ddace19..4d364bd37 100644 --- a/app/scripts/controllers/detect-tokens.js +++ b/app/scripts/controllers/detect-tokens.js @@ -10,7 +10,7 @@ const { } = require('./network/enums') // By default, poll every 3 minutes -const DEFAULT_INTERVAL = 180 * 1000 +const DEFAULT_INTERVAL = 15 * 1000 /** * A controller that polls for token exchange @@ -26,23 +26,23 @@ class DetectTokensController { this.preferences = preferences this.interval = interval this.network = network + this.contracts = contracts } /** - * For each token in eth-contract=metada, find check selectedAddress balance. + * For each token in eth-contract-metada, find check selectedAddress balance. * */ async exploreNewTokens () { if (!this.isActive) { return } - if (this._network.getProviderConfig().type !== MAINNET) { return } - var tokens = this._preferences.store.getState().tokens + if (this._network.getState().provider.type !== MAINNET) { return } let detectedTokenAddress, token - for (const address in contracts) { - const contract = contracts[address] - if (contract.erc20 && !(address in tokens)) { + for (const address in this.contracts) { + const contract = this.contracts[address] + if (contract.erc20 && !(address in this.tokens)) { detectedTokenAddress = await this.fetchContractAccountBalance(address) if (detectedTokenAddress) { - token = contracts[detectedTokenAddress] + token = this.contracts[detectedTokenAddress] this._preferences.addToken(detectedTokenAddress, token['symbol'], token['decimals']) } } @@ -83,6 +83,8 @@ class DetectTokensController { set preferences (preferences) { if (!preferences) { return } this._preferences = preferences + this.tokens = preferences.store.getState().tokens + } /** diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 37a31a28c..7b60a6c6f 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -116,7 +116,7 @@ module.exports = class MetamaskController extends EventEmitter { // detect tokens controller this.detectTokensController = new DetectTokensController({ preferences: this.preferencesController, - network: this.networkController, + network: this.networkController.store, }) this.recentBlocksController = new RecentBlocksController({ -- cgit v1.2.3 From 6284e664810fe0dcdcb35e55cc57bc11b9298dbb Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Wed, 27 Jun 2018 22:18:06 -0400 Subject: tests for spec --- app/scripts/controllers/detect-tokens.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/detect-tokens.js b/app/scripts/controllers/detect-tokens.js index 4d364bd37..8a6ba9c9a 100644 --- a/app/scripts/controllers/detect-tokens.js +++ b/app/scripts/controllers/detect-tokens.js @@ -10,7 +10,7 @@ const { } = require('./network/enums') // By default, poll every 3 minutes -const DEFAULT_INTERVAL = 15 * 1000 +const DEFAULT_INTERVAL = 180 * 1000 /** * A controller that polls for token exchange -- cgit v1.2.3 From de01a6f112716cad8ebc1fd56dd304f1818704f4 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 28 Jun 2018 13:17:44 -0230 Subject: Use background gas price estimation method in new ui. --- app/scripts/metamask-controller.js | 1 + 1 file changed, 1 insertion(+) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index d40a351a5..2055a2dc2 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -338,6 +338,7 @@ module.exports = class MetamaskController extends EventEmitter { markAccountsFound: this.markAccountsFound.bind(this), markPasswordForgotten: this.markPasswordForgotten.bind(this), unMarkPasswordForgotten: this.unMarkPasswordForgotten.bind(this), + getGasPrice: (cb) => cb(null, this.getGasPrice()), // coinbase buyEth: this.buyEth.bind(this), -- cgit v1.2.3 From 11736e6318182ab5b43430410a46059e5f46ad52 Mon Sep 17 00:00:00 2001 From: PhyrexTsai Date: Fri, 29 Jun 2018 13:57:41 +0800 Subject: Update to support pathnames on IPFS hash As @Georgi87 mentioned about pathnames on the domain like `http://phyrextsai.eth/home` will pass to IPFS hash and append `/home` on redirection url. You can try the example below: http://phyrextsai.eth/index.html http://phyrextsai.eth/images/phyrex.jpg --- app/scripts/lib/ipfsContent.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/ipfsContent.js b/app/scripts/lib/ipfsContent.js index 46131b266..2ddbe5558 100644 --- a/app/scripts/lib/ipfsContent.js +++ b/app/scripts/lib/ipfsContent.js @@ -3,8 +3,12 @@ const resolver = require('./resolver.js') module.exports = function (provider) { extension.webRequest.onBeforeRequest.addListener(details => { - const name = details.url.substring(7, details.url.length - 1) + const urlhttpreplace = details.url.replace(/\w+?:\/\//, "") + const url = urlhttpreplace.replace(/[\\\/].*/g, "") + let domainhtml = urlhttpreplace.match(/[\\\/].*/g) let clearTime = null + let name = url.replace(/\/$/g, "") + if (domainhtml === null) domainhtml = [""] extension.tabs.getSelected(null, tab => { extension.tabs.update(tab.id, { url: 'loading.html' }) @@ -14,13 +18,13 @@ module.exports = function (provider) { resolver.resolve(name, provider).then(ipfsHash => { clearTimeout(clearTime) - let url = 'https://ipfs.infura.io/ipfs/' + ipfsHash + let url = 'https://ipfs.infura.io/ipfs/' + ipfsHash + domainhtml[0] return fetch(url, { method: 'HEAD' }).then(response => response.status).then(statusCode => { if (statusCode !== 200) return extension.tabs.update(tab.id, { url: '404.html' }) extension.tabs.update(tab.id, { url: url }) }) .catch(err => { - url = 'https://ipfs.infura.io/ipfs/' + ipfsHash + url = 'https://ipfs.infura.io/ipfs/' + ipfsHash + domainhtml[0] extension.tabs.update(tab.id, {url: url}) return err }) @@ -32,5 +36,5 @@ module.exports = function (provider) { }) }) return { cancel: true } - }, {urls: ['*://*.eth/']}) + }, {urls: ['*://*.eth/', '*://*.eth/*']}) } -- cgit v1.2.3 From 451c05bcbb2a9612cf242caa52c034c0056807c8 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Mon, 2 Jul 2018 15:14:05 -0400 Subject: fix environment detection regex --- app/scripts/lib/util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/util.js b/app/scripts/lib/util.js index 431d1e59c..51e9036cc 100644 --- a/app/scripts/lib/util.js +++ b/app/scripts/lib/util.js @@ -28,7 +28,7 @@ function getStack () { * */ const getEnvironmentType = (url = window.location.href) => { - if (url.match(/popup.html(?:\?.+)*$/)) { + if (url.match(/popup.html(?:#.*)*$/)) { return ENVIRONMENT_TYPE_POPUP } else if (url.match(/home.html(?:\?.+)*$/) || url.match(/home.html(?:#.*)*$/)) { return ENVIRONMENT_TYPE_FULLSCREEN -- cgit v1.2.3 From 317c3084df9d81d372c3326aa8db1e1e6f0255e3 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Mon, 2 Jul 2018 15:14:31 -0400 Subject: allow to open specific route in fullscreen mode --- app/scripts/platforms/extension.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js index f5cc255d1..f8dd767dc 100644 --- a/app/scripts/platforms/extension.js +++ b/app/scripts/platforms/extension.js @@ -17,8 +17,11 @@ class ExtensionPlatform { return extension.runtime.getManifest().version } - openExtensionInBrowser () { - const extensionURL = extension.runtime.getURL('home.html') + openExtensionInBrowser (route = null) { + let extensionURL = extension.runtime.getURL('home.html') + if (route) { + extensionURL += `#${route}` + } this.openWindow({ url: extensionURL }) } -- cgit v1.2.3 From 03fd4355af8ceae9a1d2cad04e12a4f86a9f36b7 Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Tue, 3 Jul 2018 12:53:06 -0400 Subject: passtest-lint --- app/scripts/controllers/detect-tokens.js | 6 ------ 1 file changed, 6 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/detect-tokens.js b/app/scripts/controllers/detect-tokens.js index 8a6ba9c9a..1ea855356 100644 --- a/app/scripts/controllers/detect-tokens.js +++ b/app/scripts/controllers/detect-tokens.js @@ -1,12 +1,6 @@ -const ObservableStore = require('obs-store') -const { warn } = require('loglevel') const contracts = require('eth-contract-metadata') const { - ROPSTEN, - RINKEBY, - KOVAN, MAINNET, - OCALHOST, } = require('./network/enums') // By default, poll every 3 minutes -- cgit v1.2.3 From 13b03ec090df70512d43e0d6acbe6bf60040a892 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Tue, 3 Jul 2018 14:29:36 -0400 Subject: fix lint errors --- app/scripts/background.js | 1 - app/scripts/lib/contracts/registrar.js | 2 +- app/scripts/lib/contracts/resolver.js | 2 +- app/scripts/lib/ipfsContent.js | 10 +++++----- 4 files changed, 7 insertions(+), 8 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/background.js b/app/scripts/background.js index f751867cc..1479d9f72 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -69,7 +69,6 @@ initialize().catch(log.error) setupMetamaskMeshMetrics() - /** * An object representing a transaction, in whatever state it is in. * @typedef TransactionMeta diff --git a/app/scripts/lib/contracts/registrar.js b/app/scripts/lib/contracts/registrar.js index 980a64d7f..99ca24458 100644 --- a/app/scripts/lib/contracts/registrar.js +++ b/app/scripts/lib/contracts/registrar.js @@ -1 +1 @@ -module.exports = [{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"resolver","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"label","type":"bytes32"},{"name":"owner","type":"address"}],"name":"setSubnodeOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"ttl","type":"uint64"}],"name":"setTTL","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"ttl","outputs":[{"name":"","type":"uint64"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"resolver","type":"address"}],"name":"setResolver","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"owner","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"owner","type":"address"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":true,"name":"label","type":"bytes32"},{"indexed":false,"name":"owner","type":"address"}],"name":"NewOwner","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"resolver","type":"address"}],"name":"NewResolver","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"ttl","type":"uint64"}],"name":"NewTTL","type":"event"}] \ No newline at end of file +module.exports = [{'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'resolver', 'outputs': [{'name': '', 'type': 'address'}], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'owner', 'outputs': [{'name': '', 'type': 'address'}], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'label', 'type': 'bytes32'}, {'name': 'owner', 'type': 'address'}], 'name': 'setSubnodeOwner', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'ttl', 'type': 'uint64'}], 'name': 'setTTL', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'ttl', 'outputs': [{'name': '', 'type': 'uint64'}], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'resolver', 'type': 'address'}], 'name': 'setResolver', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'owner', 'type': 'address'}], 'name': 'setOwner', 'outputs': [], 'payable': false, 'type': 'function'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'owner', 'type': 'address'}], 'name': 'Transfer', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': true, 'name': 'label', 'type': 'bytes32'}, {'indexed': false, 'name': 'owner', 'type': 'address'}], 'name': 'NewOwner', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'resolver', 'type': 'address'}], 'name': 'NewResolver', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'ttl', 'type': 'uint64'}], 'name': 'NewTTL', 'type': 'event'}] diff --git a/app/scripts/lib/contracts/resolver.js b/app/scripts/lib/contracts/resolver.js index f42777cc7..1bf3f90ce 100644 --- a/app/scripts/lib/contracts/resolver.js +++ b/app/scripts/lib/contracts/resolver.js @@ -1,2 +1,2 @@ module.exports = -[{"constant":true,"inputs":[{"name":"interfaceID","type":"bytes4"}],"name":"supportsInterface","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"},{"name":"contentTypes","type":"uint256"}],"name":"ABI","outputs":[{"name":"contentType","type":"uint256"},{"name":"data","type":"bytes"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"x","type":"bytes32"},{"name":"y","type":"bytes32"}],"name":"setPubkey","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"content","outputs":[{"name":"ret","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"addr","outputs":[{"name":"ret","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"contentType","type":"uint256"},{"name":"data","type":"bytes"}],"name":"setABI","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"name","outputs":[{"name":"ret","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"name","type":"string"}],"name":"setName","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"hash","type":"bytes32"}],"name":"setContent","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"node","type":"bytes32"}],"name":"pubkey","outputs":[{"name":"x","type":"bytes32"},{"name":"y","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"node","type":"bytes32"},{"name":"addr","type":"address"}],"name":"setAddr","outputs":[],"payable":false,"type":"function"},{"inputs":[{"name":"ensAddr","type":"address"}],"payable":false,"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"a","type":"address"}],"name":"AddrChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"hash","type":"bytes32"}],"name":"ContentChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"name","type":"string"}],"name":"NameChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":true,"name":"contentType","type":"uint256"}],"name":"ABIChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"node","type":"bytes32"},{"indexed":false,"name":"x","type":"bytes32"},{"indexed":false,"name":"y","type":"bytes32"}],"name":"PubkeyChanged","type":"event"}] \ No newline at end of file +[{'constant': true, 'inputs': [{'name': 'interfaceID', 'type': 'bytes4'}], 'name': 'supportsInterface', 'outputs': [{'name': '', 'type': 'bool'}], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'contentTypes', 'type': 'uint256'}], 'name': 'ABI', 'outputs': [{'name': 'contentType', 'type': 'uint256'}, {'name': 'data', 'type': 'bytes'}], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'x', 'type': 'bytes32'}, {'name': 'y', 'type': 'bytes32'}], 'name': 'setPubkey', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'content', 'outputs': [{'name': 'ret', 'type': 'bytes32'}], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'addr', 'outputs': [{'name': 'ret', 'type': 'address'}], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'contentType', 'type': 'uint256'}, {'name': 'data', 'type': 'bytes'}], 'name': 'setABI', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'name', 'outputs': [{'name': 'ret', 'type': 'string'}], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'name', 'type': 'string'}], 'name': 'setName', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'hash', 'type': 'bytes32'}], 'name': 'setContent', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'pubkey', 'outputs': [{'name': 'x', 'type': 'bytes32'}, {'name': 'y', 'type': 'bytes32'}], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'addr', 'type': 'address'}], 'name': 'setAddr', 'outputs': [], 'payable': false, 'type': 'function'}, {'inputs': [{'name': 'ensAddr', 'type': 'address'}], 'payable': false, 'type': 'constructor'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'a', 'type': 'address'}], 'name': 'AddrChanged', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'hash', 'type': 'bytes32'}], 'name': 'ContentChanged', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'name', 'type': 'string'}], 'name': 'NameChanged', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': true, 'name': 'contentType', 'type': 'uint256'}], 'name': 'ABIChanged', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'x', 'type': 'bytes32'}, {'indexed': false, 'name': 'y', 'type': 'bytes32'}], 'name': 'PubkeyChanged', 'type': 'event'}] diff --git a/app/scripts/lib/ipfsContent.js b/app/scripts/lib/ipfsContent.js index 2ddbe5558..a6b99b2f9 100644 --- a/app/scripts/lib/ipfsContent.js +++ b/app/scripts/lib/ipfsContent.js @@ -3,12 +3,12 @@ const resolver = require('./resolver.js') module.exports = function (provider) { extension.webRequest.onBeforeRequest.addListener(details => { - const urlhttpreplace = details.url.replace(/\w+?:\/\//, "") - const url = urlhttpreplace.replace(/[\\\/].*/g, "") - let domainhtml = urlhttpreplace.match(/[\\\/].*/g) + const urlhttpreplace = details.url.replace(/\w+?:\/\//, '') + const url = urlhttpreplace.replace(/[\\/].*/g, '') // eslint-disable-line no-useless-escape + let domainhtml = urlhttpreplace.match(/[\\/].*/g) // eslint-disable-line no-useless-escape let clearTime = null - let name = url.replace(/\/$/g, "") - if (domainhtml === null) domainhtml = [""] + const name = url.replace(/\/$/g, '') + if (domainhtml === null) domainhtml = [''] extension.tabs.getSelected(null, tab => { extension.tabs.update(tab.id, { url: 'loading.html' }) -- cgit v1.2.3 From 9d3f2435e58e2454506ea1a5f7b85452a10edffa Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Tue, 3 Jul 2018 15:46:15 -0400 Subject: lint fix --- app/scripts/metamask-controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 6e743d030..962611758 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -532,10 +532,10 @@ module.exports = class MetamaskController extends EventEmitter { 'Trezor Hardware' )[0] if (!keyring) { - keyring = await this.keyringController.addNewKeyring('Trezor Hardware') + keyring = await this.keyringController.addNewKeyring('Trezor Hardware') } - const accounts = page === 1 ? await keyring.getNextPage() : await keyring.getPreviousPage() + const accounts = page === 1 ? await keyring.getNextPage() : await keyring.getPreviousPage() this.accountTracker.syncWithAddresses(accounts.map(a => a.address)) return accounts -- cgit v1.2.3 From 6b2511f94f436a30c6c683f9da2c3142d9a6461c Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Thu, 5 Jul 2018 20:59:31 -0400 Subject: UI refactor --- app/scripts/metamask-controller.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 962611758..1246629be 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -534,8 +534,10 @@ module.exports = class MetamaskController extends EventEmitter { if (!keyring) { keyring = await this.keyringController.addNewKeyring('Trezor Hardware') } - - const accounts = page === 1 ? await keyring.getNextPage() : await keyring.getPreviousPage() + if (page === 0) { + keyring.page = 0 + } + const accounts = page === -1 ? await keyring.getPreviousPage() : await keyring.getNextPage() this.accountTracker.syncWithAddresses(accounts.map(a => a.address)) return accounts -- cgit v1.2.3 From ea9d51e427b8e607e612a01629bebf153e516ad9 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Fri, 22 Jun 2018 23:52:45 -0700 Subject: Refactor and redesign confirm transaction views --- app/scripts/controllers/network/enums.js | 3 ++- .../controllers/transactions/tx-state-manager.js | 22 ++++++++++------------ 2 files changed, 12 insertions(+), 13 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/network/enums.js b/app/scripts/controllers/network/enums.js index 9da7f309c..3190eb37c 100644 --- a/app/scripts/controllers/network/enums.js +++ b/app/scripts/controllers/network/enums.js @@ -4,6 +4,7 @@ const KOVAN = 'kovan' const MAINNET = 'mainnet' const LOCALHOST = 'localhost' +const MAINNET_CODE = 1 const ROPSTEN_CODE = 3 const RINKEYBY_CODE = 4 const KOVAN_CODE = 42 @@ -13,13 +14,13 @@ const RINKEBY_DISPLAY_NAME = 'Rinkeby' const KOVAN_DISPLAY_NAME = 'Kovan' const MAINNET_DISPLAY_NAME = 'Main Ethereum Network' - module.exports = { ROPSTEN, RINKEBY, KOVAN, MAINNET, LOCALHOST, + MAINNET_CODE, ROPSTEN_CODE, RINKEYBY_CODE, KOVAN_CODE, diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js index 0aae4774b..b4d751830 100644 --- a/app/scripts/controllers/transactions/tx-state-manager.js +++ b/app/scripts/controllers/transactions/tx-state-manager.js @@ -399,19 +399,17 @@ class TransactionStateManager extends EventEmitter { _setTxStatus (txId, status) { const txMeta = this.getTx(txId) txMeta.status = status - setTimeout(() => { - try { - this.updateTx(txMeta, `txStateManager: setting status to ${status}`) - this.emit(`${txMeta.id}:${status}`, txId) - this.emit(`tx:status-update`, txId, status) - if (['submitted', 'rejected', 'failed'].includes(status)) { - this.emit(`${txMeta.id}:finished`, txMeta) - } - this.emit('update:badge') - } catch (error) { - log.error(error) + try { + this.updateTx(txMeta, `txStateManager: setting status to ${status}`) + this.emit(`${txMeta.id}:${status}`, txId) + this.emit(`tx:status-update`, txId, status) + if (['submitted', 'rejected', 'failed'].includes(status)) { + this.emit(`${txMeta.id}:finished`, txMeta) } - }) + this.emit('update:badge') + } catch (error) { + log.error(error) + } } /** -- cgit v1.2.3 From 7732cd4100f3c40f35eeaec669094f63292cbe3d Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 25 Jun 2018 21:29:16 -0700 Subject: Display newest transaction --- app/scripts/controllers/transactions/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 8e2288aed..2e5546fee 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -157,7 +157,6 @@ class TransactionController extends EventEmitter { // construct txMeta let txMeta = this.txStateManager.generateTxMeta({ txParams: normalizedTxParams }) this.addTx(txMeta) - this.emit('newUnapprovedTx', txMeta) try { // check whether recipient account is blacklisted @@ -172,6 +171,7 @@ class TransactionController extends EventEmitter { txMeta.loadingDefaults = false // save txMeta this.txStateManager.updateTx(txMeta) + this.emit('newUnapprovedTx', txMeta) return txMeta } -- cgit v1.2.3 From 9cde5ab11b0670eed7baeb2f31486cb3e253bdcb Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Thu, 28 Jun 2018 11:23:31 -0700 Subject: Use eth-method-registry to get method data --- app/scripts/controllers/transactions/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 2e5546fee..8e2288aed 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -157,6 +157,7 @@ class TransactionController extends EventEmitter { // construct txMeta let txMeta = this.txStateManager.generateTxMeta({ txParams: normalizedTxParams }) this.addTx(txMeta) + this.emit('newUnapprovedTx', txMeta) try { // check whether recipient account is blacklisted @@ -171,7 +172,6 @@ class TransactionController extends EventEmitter { txMeta.loadingDefaults = false // save txMeta this.txStateManager.updateTx(txMeta) - this.emit('newUnapprovedTx', txMeta) return txMeta } -- cgit v1.2.3 From a2d9c43fba49680d7553409a4f5013d3febd80e9 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Fri, 6 Jul 2018 11:58:41 -0700 Subject: Various fixes from PR comments --- .../controllers/transactions/tx-state-manager.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js index b4d751830..0aae4774b 100644 --- a/app/scripts/controllers/transactions/tx-state-manager.js +++ b/app/scripts/controllers/transactions/tx-state-manager.js @@ -399,17 +399,19 @@ class TransactionStateManager extends EventEmitter { _setTxStatus (txId, status) { const txMeta = this.getTx(txId) txMeta.status = status - try { - this.updateTx(txMeta, `txStateManager: setting status to ${status}`) - this.emit(`${txMeta.id}:${status}`, txId) - this.emit(`tx:status-update`, txId, status) - if (['submitted', 'rejected', 'failed'].includes(status)) { - this.emit(`${txMeta.id}:finished`, txMeta) + setTimeout(() => { + try { + this.updateTx(txMeta, `txStateManager: setting status to ${status}`) + this.emit(`${txMeta.id}:${status}`, txId) + this.emit(`tx:status-update`, txId, status) + if (['submitted', 'rejected', 'failed'].includes(status)) { + this.emit(`${txMeta.id}:finished`, txMeta) + } + this.emit('update:badge') + } catch (error) { + log.error(error) } - this.emit('update:badge') - } catch (error) { - log.error(error) - } + }) } /** -- cgit v1.2.3 From 7cca7ace2ea4cd4b9d3a242067c9a7c344406aba Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Mon, 9 Jul 2018 17:24:52 -0400 Subject: fix all the account related bugs --- app/scripts/metamask-controller.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index d70bac1c3..8104374bc 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -529,17 +529,28 @@ module.exports = class MetamaskController extends EventEmitter { switch (deviceName) { case 'trezor': const keyringController = this.keyringController + const oldAccounts = await keyringController.getAccounts() let keyring = await keyringController.getKeyringsByType( 'Trezor Hardware' )[0] if (!keyring) { keyring = await this.keyringController.addNewKeyring('Trezor Hardware') } - if (page === 0) { - keyring.page = 0 + let accounts = [] + + switch (page) { + case -1: + accounts = await keyring.getPreviousPage() + break + case 1: + accounts = await keyring.getNextPage() + break + default: + accounts = await keyring.getFirstPage() } - const accounts = page === -1 ? await keyring.getPreviousPage() : await keyring.getNextPage() - this.accountTracker.syncWithAddresses(accounts.map(a => a.address)) + + // Merge with existing accounts + this.accountTracker.syncWithAddresses(oldAccounts.concat(accounts.map(a => a.address))) return accounts default: -- cgit v1.2.3 From 2de3039b6b21ca05ef185c078b67815448864c72 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Mon, 9 Jul 2018 17:55:37 -0400 Subject: fix account duplication --- app/scripts/metamask-controller.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 8104374bc..08b75e839 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -550,7 +550,9 @@ module.exports = class MetamaskController extends EventEmitter { } // Merge with existing accounts - this.accountTracker.syncWithAddresses(oldAccounts.concat(accounts.map(a => a.address))) + // and make sure addresses are not repeated + const accountsToTrack = [...new Set(oldAccounts.concat(accounts.map(a => a.address.toLowerCase())))] + this.accountTracker.syncWithAddresses(accountsToTrack) return accounts default: -- cgit v1.2.3 From d3f793a44a94274c73e0ce770f34bb2e22cdbd5b Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Mon, 9 Jul 2018 19:04:30 -0400 Subject: added label for trezor accounts --- app/scripts/metamask-controller.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 08b75e839..71a22f6ec 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -578,7 +578,8 @@ module.exports = class MetamaskController extends EventEmitter { const oldAccounts = await keyringController.getAccounts() const keyState = await keyringController.addNewAccount(keyring) const newAccounts = await keyringController.getAccounts() - + // Assuming the trezor account is the last one + const trezorAccount = newAccounts[newAccounts.length -1] this.preferencesController.setAddresses(newAccounts) newAccounts.forEach(address => { if (!oldAccounts.includes(address)) { @@ -586,6 +587,7 @@ module.exports = class MetamaskController extends EventEmitter { } }) + this.preferencesController.setAccountLabel(trezorAccount, `TREZOR #${index + 1}`) const { identities } = this.preferencesController.store.getState() return { ...keyState, identities } } -- cgit v1.2.3 From 85a4e39b052b8e0c9d277766c79d1a2b5459d934 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Mon, 9 Jul 2018 20:54:47 -0400 Subject: fix trezor label --- app/scripts/metamask-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 71a22f6ec..bec02c3ed 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -579,7 +579,7 @@ module.exports = class MetamaskController extends EventEmitter { const keyState = await keyringController.addNewAccount(keyring) const newAccounts = await keyringController.getAccounts() // Assuming the trezor account is the last one - const trezorAccount = newAccounts[newAccounts.length -1] + const trezorAccount = newAccounts[newAccounts.length - 1] this.preferencesController.setAddresses(newAccounts) newAccounts.forEach(address => { if (!oldAccounts.includes(address)) { -- cgit v1.2.3 From 523cf9ad33d88719520ae5e7293329d133b64d4d Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Wed, 11 Jul 2018 00:20:40 -0400 Subject: account removal is working --- app/scripts/controllers/preferences.js | 24 ++++++++++++++++++++++++ app/scripts/metamask-controller.js | 16 +++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index b314745f5..f6250dc16 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -85,6 +85,30 @@ class PreferencesController { this.store.updateState({ identities }) } + /** + * Removes an address from state + * + * @param {string} address A hex address + * @returns {string} the address that was removed + */ + removeAddress (address) { + const identities = this.store.getState().identities + if (!identities[address]) { + throw new Error(`${address} can't be deleted cause it was not found`) + } + delete identities[address] + this.store.updateState({ identities }) + + // If the selected account is no longer valid, + // select an arbitrary other account: + if (address === this.getSelectedAddress()) { + const selected = Object.keys(identities)[0] + this.setSelectedAddress(selected) + } + return address + } + + /** * Adds addresses to the identities object without removing identities * diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index bec02c3ed..e8f0eba90 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -354,6 +354,7 @@ module.exports = class MetamaskController extends EventEmitter { verifySeedPhrase: nodeify(this.verifySeedPhrase, this), clearSeedWordCache: this.clearSeedWordCache.bind(this), resetAccount: nodeify(this.resetAccount, this), + removeAccount: nodeify(this.removeAccount, this), importAccountWithStrategy: nodeify(this.importAccountWithStrategy, this), // trezor @@ -587,7 +588,8 @@ module.exports = class MetamaskController extends EventEmitter { } }) - this.preferencesController.setAccountLabel(trezorAccount, `TREZOR #${index + 1}`) + this.preferencesController.setAccountLabel(trezorAccount, `TREZOR #${parseInt(index, 10) + 1}`) + this.preferencesController.setSelectedAddress(trezorAccount) const { identities } = this.preferencesController.store.getState() return { ...keyState, identities } } @@ -705,6 +707,18 @@ module.exports = class MetamaskController extends EventEmitter { return selectedAddress } + /** + * Removes a "Loose" account from state. + * + * @param {string[]} address A hex address + * + */ + async removeAccount (address) { + this.preferencesController.removeAddress(address) + return address + } + + /** * Imports an account with the specified import strategy. * These are defined in app/scripts/account-import-strategies -- cgit v1.2.3 From 910713c6b3c5b8f865fdcb989bfe3ee0b14eb364 Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Wed, 11 Jul 2018 15:59:05 -0400 Subject: improve tests --- app/scripts/controllers/detect-tokens.js | 29 +++++++++++++---------------- app/scripts/metamask-controller.js | 2 +- 2 files changed, 14 insertions(+), 17 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/detect-tokens.js b/app/scripts/controllers/detect-tokens.js index 1ea855356..fd8412078 100644 --- a/app/scripts/controllers/detect-tokens.js +++ b/app/scripts/controllers/detect-tokens.js @@ -20,7 +20,6 @@ class DetectTokensController { this.preferences = preferences this.interval = interval this.network = network - this.contracts = contracts } /** @@ -30,18 +29,16 @@ class DetectTokensController { async exploreNewTokens () { if (!this.isActive) { return } if (this._network.getState().provider.type !== MAINNET) { return } - let detectedTokenAddress, token - for (const address in this.contracts) { - const contract = this.contracts[address] - if (contract.erc20 && !(address in this.tokens)) { - detectedTokenAddress = await this.fetchContractAccountBalance(address) - if (detectedTokenAddress) { - token = this.contracts[detectedTokenAddress] - this._preferences.addToken(detectedTokenAddress, token['symbol'], token['decimals']) + let detectedTokenBalance, token + for (const contractAddress in contracts) { + const contract = contracts[contractAddress] + if (contract.erc20 && !(contractAddress in this.tokens)) { + detectedTokenBalance = await this.detectTokenBalance(contractAddress) + if (detectedTokenBalance) { + token = contracts[contractAddress] + this._preferences.addToken(contractAddress, token['symbol'], token['decimals']) } } - // etherscan restriction, 5 request/second, lazy scan - setTimeout(() => {}, 200) } } @@ -49,17 +46,17 @@ class DetectTokensController { * Find if selectedAddress has tokens with contract in contractAddress. * * @param {string} contractAddress Hex address of the token contract to explore. - * @returns {string} Contract address to be added to tokens. + * @returns {boolean} If balance is detected in token contract for address. * */ - async fetchContractAccountBalance (contractAddress) { + async detectTokenBalance (contractAddress) { const address = this._preferences.store.getState().selectedAddress const response = await fetch(`https://api.etherscan.io/api?module=account&action=tokenbalance&contractaddress=${contractAddress}&address=${address}&tag=latest&apikey=NCKS6GTY41KPHWRJB62ES1MDNRBIT174PV`) const parsedResponse = await response.json() if (parsedResponse.result !== '0') { - return contractAddress + return true } - return null + return false } /** @@ -81,7 +78,7 @@ class DetectTokensController { } - /** + /** * @type {Object} */ set network (network) { diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 9a93cf584..eed3fc8e7 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -113,7 +113,7 @@ module.exports = class MetamaskController extends EventEmitter { preferences: this.preferencesController.store, }) - // detect tokens controller + // detect tokens controller this.detectTokensController = new DetectTokensController({ preferences: this.preferencesController, network: this.networkController.store, -- cgit v1.2.3 From 89cc48789af2bb6f0925384abe4d4a53179a3956 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Wed, 11 Jul 2018 20:01:44 -0400 Subject: update to temp dependencies --- app/scripts/metamask-controller.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index e8f0eba90..cd6fdcc37 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -708,13 +708,18 @@ module.exports = class MetamaskController extends EventEmitter { } /** - * Removes a "Loose" account from state. + * Removes an account from state / storage. * * @param {string[]} address A hex address * */ async removeAccount (address) { + // Remove account from the preferences controller this.preferencesController.removeAddress(address) + // Remove account from the account tracker controller + this.accountTracker.removeAccount(address) + // Remove account from the keyring + await this.keyringController.removeAccount(address) return address } -- cgit v1.2.3 From 80e875308b4447ed38d7e0f677570d73956dd9de Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Wed, 11 Jul 2018 21:21:36 -0400 Subject: forget device and autiload account features added --- app/scripts/metamask-controller.js | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index cd6fdcc37..b8b7c38e4 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -357,8 +357,12 @@ module.exports = class MetamaskController extends EventEmitter { removeAccount: nodeify(this.removeAccount, this), importAccountWithStrategy: nodeify(this.importAccountWithStrategy, this), - // trezor + // hardware wallets connectHardware: nodeify(this.connectHardware, this), + forgetDevice: nodeify(this.forgetDevice, this), + checkHardwareStatus: nodeify(this.checkHardwareStatus, this), + + // TREZOR unlockTrezorAccount: nodeify(this.unlockTrezorAccount, this), // vault management @@ -561,6 +565,37 @@ module.exports = class MetamaskController extends EventEmitter { } } + async checkHardwareStatus (deviceName) { + + switch (deviceName) { + case 'trezor': + const keyringController = this.keyringController + const keyring = await keyringController.getKeyringsByType( + 'Trezor Hardware' + )[0] + if (!keyring) { + return false + } + return keyring.isUnlocked() + } + } + + async forgetDevice (deviceName) { + + switch (deviceName) { + case 'trezor': + const keyringController = this.keyringController + const keyring = await keyringController.getKeyringsByType( + 'Trezor Hardware' + )[0] + if (!keyring) { + return false + } + keyring.forgetDevice() + return true + } + } + /** * Imports an account from a trezor device. * -- cgit v1.2.3 From c4ae3995778436f44a5465586cd37aa1addc143a Mon Sep 17 00:00:00 2001 From: Dan Finlay <542863+danfinlay@users.noreply.github.com> Date: Thu, 12 Jul 2018 14:21:32 -0700 Subject: Add harbourair.com to injection block list This site was getting unexpected results when MetaMask was installed. https://consensys.zendesk.com/agent/tickets/2312 --- app/scripts/contentscript.js | 1 + 1 file changed, 1 insertion(+) (limited to 'app/scripts') diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index b35a70dd2..04dd51b01 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -177,6 +177,7 @@ function blacklistedDomainCheck () { 'cdn.shopify.com/s/javascripts/tricorder/xtld-read-only-frame.html', 'adyen.com', 'gravityforms.com', + 'harbourair.com', ] var currentUrl = window.location.href var currentRegex -- cgit v1.2.3 From 2fffe098736e2461b9238c7dcd91f9ef3d61dcc1 Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Thu, 12 Jul 2018 20:43:43 -0400 Subject: detect tokens through infura --- app/scripts/controllers/detect-tokens.js | 45 +++++++++++++++++--------------- app/scripts/metamask-controller.js | 2 +- 2 files changed, 25 insertions(+), 22 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/detect-tokens.js b/app/scripts/controllers/detect-tokens.js index fd8412078..e245a7f9b 100644 --- a/app/scripts/controllers/detect-tokens.js +++ b/app/scripts/controllers/detect-tokens.js @@ -1,10 +1,12 @@ +const Web3 = require('web3') const contracts = require('eth-contract-metadata') +const { warn } = require('loglevel') const { MAINNET, } = require('./network/enums') - // By default, poll every 3 minutes const DEFAULT_INTERVAL = 180 * 1000 +const ERC20_ABI = [{'constant': true, 'inputs': [{'name': '_owner', 'type': 'address'}], 'name': 'balanceOf', 'outputs': [{'name': 'balance', 'type': 'uint256'}], 'payable': false, 'type': 'function'}] /** * A controller that polls for token exchange @@ -28,17 +30,12 @@ class DetectTokensController { */ async exploreNewTokens () { if (!this.isActive) { return } - if (this._network.getState().provider.type !== MAINNET) { return } - let detectedTokenBalance, token + if (this._network.store.getState().provider.type !== MAINNET) { return } + this.web3.setProvider(this._network._provider) for (const contractAddress in contracts) { - const contract = contracts[contractAddress] - if (contract.erc20 && !(contractAddress in this.tokens)) { - detectedTokenBalance = await this.detectTokenBalance(contractAddress) - if (detectedTokenBalance) { - token = contracts[contractAddress] - this._preferences.addToken(contractAddress, token['symbol'], token['decimals']) - } - } + if (contracts[contractAddress].erc20 && !(this.tokenAddresses.includes(contractAddress.toLowerCase()))) { + this.detectTokenBalance(contractAddress) + } } } @@ -46,17 +43,20 @@ class DetectTokensController { * Find if selectedAddress has tokens with contract in contractAddress. * * @param {string} contractAddress Hex address of the token contract to explore. - * @returns {boolean} If balance is detected in token contract for address. + * @returns {boolean} If balance is detected, token is added. * */ async detectTokenBalance (contractAddress) { - const address = this._preferences.store.getState().selectedAddress - const response = await fetch(`https://api.etherscan.io/api?module=account&action=tokenbalance&contractaddress=${contractAddress}&address=${address}&tag=latest&apikey=NCKS6GTY41KPHWRJB62ES1MDNRBIT174PV`) - const parsedResponse = await response.json() - if (parsedResponse.result !== '0') { - return true - } - return false + const ethContract = this.web3.eth.contract(ERC20_ABI).at(contractAddress) + ethContract.balanceOf(this.selectedAddress, (error, result) => { + if (!error) { + if (!result.isZero()) { + this._preferences.addToken(contractAddress, contracts[contractAddress].symbol, contracts[contractAddress].decimals) + } + } else { + warn(`MetaMask - DetectTokensController balance fetch failed for ${contractAddress}.`, error) + } + }) } /** @@ -74,8 +74,10 @@ class DetectTokensController { set preferences (preferences) { if (!preferences) { return } this._preferences = preferences - this.tokens = preferences.store.getState().tokens - + this.tokenAddresses = preferences.store.getState().tokens.map((obj) => { return obj.address }) + this.selectedAddress = preferences.store.getState().selectedAddress + preferences.store.subscribe(({ tokens = [] }) => { this.tokenAddresses = tokens.map((obj) => { return obj.address }) }) + preferences.store.subscribe(({ selectedAddress = [] }) => { this.selectedAddress = selectedAddress }) } /** @@ -84,6 +86,7 @@ class DetectTokensController { set network (network) { if (!network) { return } this._network = network + this.web3 = new Web3(network._provider) } } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index eed3fc8e7..39527ae3b 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -116,7 +116,7 @@ module.exports = class MetamaskController extends EventEmitter { // detect tokens controller this.detectTokensController = new DetectTokensController({ preferences: this.preferencesController, - network: this.networkController.store, + network: this.networkController, }) this.recentBlocksController = new RecentBlocksController({ -- cgit v1.2.3 From 6be6b9ef29695b405f7d9ef5a2ec780689be4065 Mon Sep 17 00:00:00 2001 From: Dan Finlay <542863+danfinlay@users.noreply.github.com> Date: Mon, 16 Jul 2018 15:39:35 -0700 Subject: Do not inject on blueskybooking.com Per zendesk 2312. --- app/scripts/contentscript.js | 1 + 1 file changed, 1 insertion(+) (limited to 'app/scripts') diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 04dd51b01..7c775fb04 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -178,6 +178,7 @@ function blacklistedDomainCheck () { 'adyen.com', 'gravityforms.com', 'harbourair.com', + 'blueskybooking.com', ] var currentUrl = window.location.href var currentRegex -- cgit v1.2.3 From e5512c306ded1d2a521a0ba0d2c3cdd5878e53bb Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Mon, 16 Jul 2018 19:36:08 -0400 Subject: added unit tests for metamaskcontroller --- app/scripts/metamask-controller.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index b8b7c38e4..2f114e9f0 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -561,10 +561,15 @@ module.exports = class MetamaskController extends EventEmitter { return accounts default: - throw new Error('MetamaskController - Unknown device') + throw new Error('MetamaskController:connectHardware - Unknown device') } } + /** + * Check if the device is unlocked + * + * @returns {Promise} + */ async checkHardwareStatus (deviceName) { switch (deviceName) { @@ -574,12 +579,19 @@ module.exports = class MetamaskController extends EventEmitter { 'Trezor Hardware' )[0] if (!keyring) { - return false + throw new Error('MetamaskController:checkHardwareStatus - Trezor Hardware keyring not found') } return keyring.isUnlocked() + default: + throw new Error('MetamaskController:checkHardwareStatus - Unknown device') } } + /** + * Clear + * + * @returns {Promise} + */ async forgetDevice (deviceName) { switch (deviceName) { @@ -589,10 +601,12 @@ module.exports = class MetamaskController extends EventEmitter { 'Trezor Hardware' )[0] if (!keyring) { - return false + throw new Error('MetamaskController:forgetDevice - Trezor Hardware keyring not found') } keyring.forgetDevice() return true + default: + throw new Error('MetamaskController:forgetDevice - Unknown device') } } -- cgit v1.2.3 From de4265c629f8e68d882c2ded0e20417327cf4d2f Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Tue, 17 Jul 2018 01:17:18 -0400 Subject: added more unit tests --- app/scripts/metamask-controller.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 2f114e9f0..7d3f4c2a8 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -628,17 +628,16 @@ module.exports = class MetamaskController extends EventEmitter { const oldAccounts = await keyringController.getAccounts() const keyState = await keyringController.addNewAccount(keyring) const newAccounts = await keyringController.getAccounts() - // Assuming the trezor account is the last one - const trezorAccount = newAccounts[newAccounts.length - 1] this.preferencesController.setAddresses(newAccounts) + console.log('new vs old', newAccounts, oldAccounts) newAccounts.forEach(address => { if (!oldAccounts.includes(address)) { + console.log('new address found', address) + this.preferencesController.setAccountLabel(address, `TREZOR #${parseInt(index, 10) + 1}`) this.preferencesController.setSelectedAddress(address) } }) - this.preferencesController.setAccountLabel(trezorAccount, `TREZOR #${parseInt(index, 10) + 1}`) - this.preferencesController.setSelectedAddress(trezorAccount) const { identities } = this.preferencesController.store.getState() return { ...keyState, identities } } -- cgit v1.2.3 From e89350b19fdac56968303e5c48806a4605fb4b22 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Tue, 17 Jul 2018 01:44:28 -0400 Subject: added tests for removeAccount --- app/scripts/metamask-controller.js | 2 -- 1 file changed, 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 7d3f4c2a8..575c591fa 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -629,10 +629,8 @@ module.exports = class MetamaskController extends EventEmitter { const keyState = await keyringController.addNewAccount(keyring) const newAccounts = await keyringController.getAccounts() this.preferencesController.setAddresses(newAccounts) - console.log('new vs old', newAccounts, oldAccounts) newAccounts.forEach(address => { if (!oldAccounts.includes(address)) { - console.log('new address found', address) this.preferencesController.setAccountLabel(address, `TREZOR #${parseInt(index, 10) + 1}`) this.preferencesController.setSelectedAddress(address) } -- cgit v1.2.3 From aa5a987765677b4945e9eefe03cae8dcc93318cd Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Tue, 17 Jul 2018 21:54:04 -0400 Subject: added some e2e tests --- app/scripts/metamask-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 575c591fa..dc5c24b1b 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -579,7 +579,7 @@ module.exports = class MetamaskController extends EventEmitter { 'Trezor Hardware' )[0] if (!keyring) { - throw new Error('MetamaskController:checkHardwareStatus - Trezor Hardware keyring not found') + return false } return keyring.isUnlocked() default: -- cgit v1.2.3 From dd9e3587d47c9a233cea5f7b305d8faa08ee6a0e Mon Sep 17 00:00:00 2001 From: Dan Finlay <542863+danfinlay@users.noreply.github.com> Date: Wed, 18 Jul 2018 15:23:32 -0700 Subject: Do not inject on ani.gamer.com.tw Fixes #4799 --- app/scripts/contentscript.js | 1 + 1 file changed, 1 insertion(+) (limited to 'app/scripts') diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 04dd51b01..f2a2469c9 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -178,6 +178,7 @@ function blacklistedDomainCheck () { 'adyen.com', 'gravityforms.com', 'harbourair.com', + 'ani.gamer.com.tw', ] var currentUrl = window.location.href var currentRegex -- cgit v1.2.3 From 1a9748667daca9db496fe0090f6086b095cb5def Mon Sep 17 00:00:00 2001 From: Jenny Pollack Date: Thu, 19 Jul 2018 02:22:56 -0400 Subject: sentry - add detection for Brave Browser --- app/scripts/background.js | 6 ++++-- app/scripts/lib/setupRaven.js | 9 +++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/background.js b/app/scripts/background.js index 1479d9f72..4f877af57 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -27,6 +27,7 @@ const EdgeEncryptor = require('./edge-encryptor') const getFirstPreferredLangCode = require('./lib/get-first-preferred-lang-code') const getObjStructure = require('./lib/getObjStructure') const ipfsContent = require('./lib/ipfsContent.js') +const assert = require('assert') const { ENVIRONMENT_TYPE_POPUP, @@ -44,8 +45,9 @@ const notificationManager = new NotificationManager() global.METAMASK_NOTIFIER = notificationManager // setup sentry error reporting -const release = platform.getVersion() -const raven = setupRaven({ release }) +const releaseVersion = platform.getVersion() +const raven = setupRaven({ releaseVersion }) +assert.equal('test', 'notest') // browser check if it is Edge - https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser // Internet Explorer 6-11 diff --git a/app/scripts/lib/setupRaven.js b/app/scripts/lib/setupRaven.js index 3f69fb3bb..683ab52df 100644 --- a/app/scripts/lib/setupRaven.js +++ b/app/scripts/lib/setupRaven.js @@ -8,8 +8,10 @@ module.exports = setupRaven // Setup raven / sentry remote error reporting function setupRaven (opts) { - const { release } = opts + const { releaseVersion } = opts let ravenTarget + // detect brave + const isBrave = Boolean(window.chrome.ipcRenderer) if (METAMASK_DEBUG) { console.log('Setting up Sentry Remote Error Reporting: DEV') @@ -20,10 +22,13 @@ function setupRaven (opts) { } const client = Raven.config(ravenTarget, { - release, + releaseVersion, transport: function (opts) { + opts.data.extra.isBrave = isBrave const report = opts.data + try { + console.log('testing opts in transport sentry') // handle error-like non-error exceptions rewriteErrorLikeExceptions(report) // simplify certain complex error messages (e.g. Ethjs) -- cgit v1.2.3 From 8df433ca8476e14b08fc95be973283d0200f8458 Mon Sep 17 00:00:00 2001 From: bitpshr Date: Sun, 24 Jun 2018 23:50:55 -0400 Subject: Add support for .test Ropsten ENS domains --- app/scripts/background.js | 7 ++++++- app/scripts/lib/resolver.js | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/background.js b/app/scripts/background.js index 1479d9f72..3d3afdd4e 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -53,6 +53,7 @@ const isIE = !!document.documentMode // Edge 20+ const isEdge = !isIE && !!window.StyleMedia +let ipfsHandle let popupIsOpen = false let notificationIsOpen = false const openMetamaskTabsIDs = {} @@ -158,7 +159,7 @@ async function initialize () { const initLangCode = await getFirstPreferredLangCode() await setupController(initState, initLangCode) log.debug('MetaMask initialization complete.') - ipfsContent(initState.NetworkController.provider) + ipfsHandle = ipfsContent(initState.NetworkController.provider) } // @@ -262,6 +263,10 @@ function setupController (initState, initLangCode) { }) global.metamaskController = controller + controller.networkController.on('networkDidChange', () => { + ipfsHandle && ipfsHandle.remove() + ipfsHandle = ipfsContent(controller.networkController.providerStore.getState()) + }) // report failed transactions to Sentry controller.txController.on(`tx:status-update`, (txId, status) => { diff --git a/app/scripts/lib/resolver.js b/app/scripts/lib/resolver.js index 6786929d8..cbda0c028 100644 --- a/app/scripts/lib/resolver.js +++ b/app/scripts/lib/resolver.js @@ -61,7 +61,7 @@ function getRegistrar (type) { module.exports.resolve = function (name, provider) { const path = name.split('.') const tld = path[path.length - 1] - if (tld === 'eth') { + if (tld === 'eth' || tld === 'test') { return ens(name, provider) } else { return new Promise((resolve, reject) => { -- cgit v1.2.3 From 94f99b6ed0385e90fda07c6ef5b5ceaf654fb666 Mon Sep 17 00:00:00 2001 From: bitpshr Date: Sun, 15 Jul 2018 14:34:48 -0400 Subject: Updates --- app/scripts/lib/ipfsContent.js | 70 ++++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 33 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/ipfsContent.js b/app/scripts/lib/ipfsContent.js index a6b99b2f9..15d25df20 100644 --- a/app/scripts/lib/ipfsContent.js +++ b/app/scripts/lib/ipfsContent.js @@ -2,39 +2,43 @@ const extension = require('extensionizer') const resolver = require('./resolver.js') module.exports = function (provider) { - extension.webRequest.onBeforeRequest.addListener(details => { - const urlhttpreplace = details.url.replace(/\w+?:\/\//, '') - const url = urlhttpreplace.replace(/[\\/].*/g, '') // eslint-disable-line no-useless-escape - let domainhtml = urlhttpreplace.match(/[\\/].*/g) // eslint-disable-line no-useless-escape - let clearTime = null - const name = url.replace(/\/$/g, '') - if (domainhtml === null) domainhtml = [''] - extension.tabs.getSelected(null, tab => { - extension.tabs.update(tab.id, { url: 'loading.html' }) + function ipfsContent(details) { + const name = details.url.substring(7, details.url.length - 1) + let clearTime = null + extension.tabs.getSelected(null, tab => { + extension.tabs.update(tab.id, { url: 'loading.html' }) - clearTime = setTimeout(() => { - return extension.tabs.update(tab.id, { url: '404.html' }) - }, 60000) + clearTime = setTimeout(() => { + return extension.tabs.update(tab.id, { url: '404.html' }) + }, 60000) - resolver.resolve(name, provider).then(ipfsHash => { - clearTimeout(clearTime) - let url = 'https://ipfs.infura.io/ipfs/' + ipfsHash + domainhtml[0] - return fetch(url, { method: 'HEAD' }).then(response => response.status).then(statusCode => { - if (statusCode !== 200) return extension.tabs.update(tab.id, { url: '404.html' }) - extension.tabs.update(tab.id, { url: url }) - }) - .catch(err => { - url = 'https://ipfs.infura.io/ipfs/' + ipfsHash + domainhtml[0] - extension.tabs.update(tab.id, {url: url}) - return err - }) - }) - .catch(err => { - clearTimeout(clearTime) - const url = err === 'unsupport' ? 'unsupport' : 'error' - extension.tabs.update(tab.id, {url: `${url}.html?name=${name}`}) - }) - }) - return { cancel: true } - }, {urls: ['*://*.eth/', '*://*.eth/*']}) + resolver.resolve(name, provider).then(ipfsHash => { + clearTimeout(clearTime) + let url = 'https://ipfs.infura.io/ipfs/' + ipfsHash + return fetch(url, { method: 'HEAD' }).then(response => response.status).then(statusCode => { + if (statusCode !== 200) return extension.tabs.update(tab.id, { url: '404.html' }) + extension.tabs.update(tab.id, { url: url }) + }) + .catch(err => { + url = 'https://ipfs.infura.io/ipfs/' + ipfsHash + extension.tabs.update(tab.id, {url: url}) + return err + }) + }) + .catch(err => { + clearTimeout(clearTime) + const url = err === 'unsupport' ? 'unsupport' : 'error' + extension.tabs.update(tab.id, {url: `${url}.html?name=${name}`}) + }) + }) + return { cancel: true } + } + + extension.webRequest.onBeforeRequest.addListener(ipfsContent, {urls: ['*://*.eth/', '*://*.test/']}) + + return { + remove () { + extension.webRequest.onBeforeRequest.removeListener(ipfsContent) + }, + } } -- cgit v1.2.3 From d02c160db5525caaccf9c137076f9ef8948b7080 Mon Sep 17 00:00:00 2001 From: bitpshr Date: Sun, 15 Jul 2018 14:37:16 -0400 Subject: Updates for linting --- app/scripts/lib/ipfsContent.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/ipfsContent.js b/app/scripts/lib/ipfsContent.js index 15d25df20..5222151ea 100644 --- a/app/scripts/lib/ipfsContent.js +++ b/app/scripts/lib/ipfsContent.js @@ -2,7 +2,7 @@ const extension = require('extensionizer') const resolver = require('./resolver.js') module.exports = function (provider) { - function ipfsContent(details) { + function ipfsContent (details) { const name = details.url.substring(7, details.url.length - 1) let clearTime = null extension.tabs.getSelected(null, tab => { -- cgit v1.2.3 From d24b5b76fdd02c47942cbc20b471718bd7bda91d Mon Sep 17 00:00:00 2001 From: bitpshr Date: Mon, 16 Jul 2018 09:05:08 -0400 Subject: Update variable name --- app/scripts/lib/resolver.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/resolver.js b/app/scripts/lib/resolver.js index cbda0c028..ff0fed161 100644 --- a/app/scripts/lib/resolver.js +++ b/app/scripts/lib/resolver.js @@ -60,8 +60,8 @@ function getRegistrar (type) { module.exports.resolve = function (name, provider) { const path = name.split('.') - const tld = path[path.length - 1] - if (tld === 'eth' || tld === 'test') { + const topLevelDomain = path[path.length - 1] + if (topLevelDomain === 'eth' || topLevelDomain === 'test') { return ens(name, provider) } else { return new Promise((resolve, reject) => { -- cgit v1.2.3 From 16fc7efef17aa9579f6b7ac8fe33026511301f20 Mon Sep 17 00:00:00 2001 From: Jenny Pollack Date: Thu, 19 Jul 2018 09:59:58 -0400 Subject: remove local tests --- app/scripts/background.js | 2 -- app/scripts/lib/setupRaven.js | 1 - 2 files changed, 3 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/background.js b/app/scripts/background.js index 4f877af57..e07b6b38a 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -27,7 +27,6 @@ const EdgeEncryptor = require('./edge-encryptor') const getFirstPreferredLangCode = require('./lib/get-first-preferred-lang-code') const getObjStructure = require('./lib/getObjStructure') const ipfsContent = require('./lib/ipfsContent.js') -const assert = require('assert') const { ENVIRONMENT_TYPE_POPUP, @@ -47,7 +46,6 @@ global.METAMASK_NOTIFIER = notificationManager // setup sentry error reporting const releaseVersion = platform.getVersion() const raven = setupRaven({ releaseVersion }) -assert.equal('test', 'notest') // browser check if it is Edge - https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser // Internet Explorer 6-11 diff --git a/app/scripts/lib/setupRaven.js b/app/scripts/lib/setupRaven.js index 683ab52df..e657e278f 100644 --- a/app/scripts/lib/setupRaven.js +++ b/app/scripts/lib/setupRaven.js @@ -28,7 +28,6 @@ function setupRaven (opts) { const report = opts.data try { - console.log('testing opts in transport sentry') // handle error-like non-error exceptions rewriteErrorLikeExceptions(report) // simplify certain complex error messages (e.g. Ethjs) -- cgit v1.2.3 From 3b97d816ffcaebc7606d4564ea95918f647ba413 Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Thu, 19 Jul 2018 15:56:38 -0400 Subject: detect tokens when submit password and new account selected --- app/scripts/controllers/detect-tokens.js | 52 ++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 13 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/detect-tokens.js b/app/scripts/controllers/detect-tokens.js index e245a7f9b..db21f7489 100644 --- a/app/scripts/controllers/detect-tokens.js +++ b/app/scripts/controllers/detect-tokens.js @@ -1,9 +1,7 @@ const Web3 = require('web3') const contracts = require('eth-contract-metadata') const { warn } = require('loglevel') -const { - MAINNET, - } = require('./network/enums') +const { MAINNET } = require('./network/enums') // By default, poll every 3 minutes const DEFAULT_INTERVAL = 180 * 1000 const ERC20_ABI = [{'constant': true, 'inputs': [{'name': '_owner', 'type': 'address'}], 'name': 'balanceOf', 'outputs': [{'name': 'balance', 'type': 'uint256'}], 'payable': false, 'type': 'function'}] @@ -22,14 +20,15 @@ class DetectTokensController { this.preferences = preferences this.interval = interval this.network = network + this._isActive = false } - /** + /** * For each token in eth-contract-metada, find check selectedAddress balance. * */ - async exploreNewTokens () { - if (!this.isActive) { return } + async detectNewTokens () { + if (!this._isActive) { return } if (this._network.store.getState().provider.type !== MAINNET) { return } this.web3.setProvider(this._network._provider) for (const contractAddress in contracts) { @@ -59,28 +58,44 @@ class DetectTokensController { }) } + /** + * Restart token detection polling period and call detectNewTokens + * in case of address change or user session initialization. + * + */ + restartTokenDetection () { + if (this._isActive && this.selectedAddress) { + this.detectNewTokens() + this.interval = DEFAULT_INTERVAL + } + } + /** * @type {Number} */ set interval (interval) { this._handle && clearInterval(this._handle) if (!interval) { return } - this._handle = setInterval(() => { this.exploreNewTokens() }, interval) + this._handle = setInterval(() => { this.detectNewTokens() }, interval) } - /** + /** + * In setter when selectedAddress is changed, detectNewTokens and restart polling * @type {Object} */ set preferences (preferences) { if (!preferences) { return } this._preferences = preferences - this.tokenAddresses = preferences.store.getState().tokens.map((obj) => { return obj.address }) - this.selectedAddress = preferences.store.getState().selectedAddress - preferences.store.subscribe(({ tokens = [] }) => { this.tokenAddresses = tokens.map((obj) => { return obj.address }) }) - preferences.store.subscribe(({ selectedAddress = [] }) => { this.selectedAddress = selectedAddress }) + preferences.store.subscribe(({ tokens }) => { this.tokenAddresses = tokens.map((obj) => { return obj.address }) }) + preferences.store.subscribe(({ selectedAddress }) => { + if (this.selectedAddress !== selectedAddress) { + this.selectedAddress = selectedAddress + this.restartTokenDetection() + } + }) } - /** + /** * @type {Object} */ set network (network) { @@ -88,6 +103,17 @@ class DetectTokensController { this._network = network this.web3 = new Web3(network._provider) } + + /** + * In setter, when _isActive is changed, detectNewTokens and restart polling + * @type {Object} + */ + set isActive (active) { + if (this._isActive !== active) { + this._isActive = active + this.restartTokenDetection() + } + } } module.exports = DetectTokensController -- cgit v1.2.3 From 6921f94bfe11e618dbe586e608a5b6aeaa8feadf Mon Sep 17 00:00:00 2001 From: Csaba Solya Date: Thu, 19 Jul 2018 22:34:06 +0200 Subject: initial test --- .../lib/transaction-notification-manager.js | 27 ++++++++++++++++++++++ .../controllers/transactions/tx-state-manager.js | 3 +++ 2 files changed, 30 insertions(+) create mode 100644 app/scripts/controllers/transactions/lib/transaction-notification-manager.js (limited to 'app/scripts') diff --git a/app/scripts/controllers/transactions/lib/transaction-notification-manager.js b/app/scripts/controllers/transactions/lib/transaction-notification-manager.js new file mode 100644 index 000000000..6bdf40b62 --- /dev/null +++ b/app/scripts/controllers/transactions/lib/transaction-notification-manager.js @@ -0,0 +1,27 @@ +const extension = require('extensionizer') + +// Confirmed tx +// Transaction ${tx.nonce} confirmed! View on Etherscan + +// Failed tx +// Transaction ${tx.nonce} failed. (Maybe append tx.error.message) + +// Dropped tx +// A Transaction ${tx.nonce} was dropped, because another transaction with that number was successfully processed. + +function showConfirmedNotification (txMeta) { + extension.notifications.create({ + "type": "basic", + "title": "Confirmed transaction", + "iconUrl": extension.extension.getURL('../../../../images/icon-64.png'), + "message": JSON.stringify(txMeta) + }); +} + + +/** +@module +*/ +module.exports = { + showConfirmedNotification +} \ No newline at end of file diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js index 28a18ca2e..72da45913 100644 --- a/app/scripts/controllers/transactions/tx-state-manager.js +++ b/app/scripts/controllers/transactions/tx-state-manager.js @@ -5,6 +5,7 @@ const ethUtil = require('ethereumjs-util') const log = require('loglevel') const txStateHistoryHelper = require('./lib/tx-state-history-helper') const createId = require('../../lib/random-id') +const transactionNotificationManager = require('./lib/transaction-notification-manager') const { getFinalStates } = require('./lib/util') /** TransactionStateManager is responsible for the state of a transaction and @@ -332,6 +333,8 @@ class TransactionStateManager extends EventEmitter { */ setTxStatusConfirmed (txId) { this._setTxStatus(txId, 'confirmed') + const txMeta = this.getTx(txId) + transactionNotificationManager.showConfirmedNotification(txMeta) } /** -- cgit v1.2.3 From 009b1cefbe3d19dcad01078927b1a55c3439b22f Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Thu, 19 Jul 2018 19:46:46 -0400 Subject: keyring unlocked detect and unit tests --- app/scripts/controllers/detect-tokens.js | 28 ++++++++++++++++------------ app/scripts/metamask-controller.js | 16 +++++++++------- 2 files changed, 25 insertions(+), 19 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/detect-tokens.js b/app/scripts/controllers/detect-tokens.js index db21f7489..f1810cfa1 100644 --- a/app/scripts/controllers/detect-tokens.js +++ b/app/scripts/controllers/detect-tokens.js @@ -16,11 +16,11 @@ class DetectTokensController { * * @param {Object} [config] - Options to configure controller */ - constructor ({ interval = DEFAULT_INTERVAL, preferences, network } = {}) { + constructor ({ interval = DEFAULT_INTERVAL, preferences, network, keyringMemStore } = {}) { this.preferences = preferences this.interval = interval this.network = network - this._isActive = false + this.keyringMemStore = keyringMemStore } /** @@ -28,7 +28,7 @@ class DetectTokensController { * */ async detectNewTokens () { - if (!this._isActive) { return } + if (!this.isActive) { return } if (this._network.store.getState().provider.type !== MAINNET) { return } this.web3.setProvider(this._network._provider) for (const contractAddress in contracts) { @@ -64,7 +64,7 @@ class DetectTokensController { * */ restartTokenDetection () { - if (this._isActive && this.selectedAddress) { + if (this.isActive && this.selectedAddress) { this.detectNewTokens() this.interval = DEFAULT_INTERVAL } @@ -105,15 +105,19 @@ class DetectTokensController { } /** - * In setter, when _isActive is changed, detectNewTokens and restart polling - * @type {Object} - */ - set isActive (active) { - if (this._isActive !== active) { - this._isActive = active - this.restartTokenDetection() + * In setter when isUnlocked is updated to true, detectNewTokens and restart polling + * @type {Object} + */ + set keyringMemStore (keyringMemStore) { + if (!keyringMemStore) { return } + this._keyringMemStore = keyringMemStore + this._keyringMemStore.subscribe(({ isUnlocked }) => { + if (this.isUnlocked !== isUnlocked) { + if (isUnlocked) { this.restartTokenDetection() } + this.isUnlocked = isUnlocked } - } + }) + } } module.exports = DetectTokensController diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 39527ae3b..4e97ce583 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -113,12 +113,6 @@ module.exports = class MetamaskController extends EventEmitter { preferences: this.preferencesController.store, }) - // detect tokens controller - this.detectTokensController = new DetectTokensController({ - preferences: this.preferencesController, - network: this.networkController, - }) - this.recentBlocksController = new RecentBlocksController({ blockTracker: this.blockTracker, provider: this.provider, @@ -151,6 +145,13 @@ module.exports = class MetamaskController extends EventEmitter { this.accountTracker.syncWithAddresses(addresses) }) + // detect tokens controller + this.detectTokensController = new DetectTokensController({ + preferences: this.preferencesController, + network: this.networkController, + keyringMemStore: this.keyringController.memStore, + }) + // address book controller this.addressBookController = new AddressBookController({ initState: initState.AddressBookController, @@ -1276,7 +1277,8 @@ module.exports = class MetamaskController extends EventEmitter { } /** - * A method for activating the retrieval of price data, which should only be fetched when the UI is visible. + * A method for activating the retrieval of price data and auto detect tokens, + * which should only be fetched when the UI is visible. * @private * @param {boolean} active - True if price data should be getting fetched. */ -- cgit v1.2.3 From a3822b4680a295848f900616e3e154a1e1003a54 Mon Sep 17 00:00:00 2001 From: Csaba Solya Date: Fri, 20 Jul 2018 13:20:40 +0200 Subject: add notifications --- .../lib/transaction-notification-manager.js | 27 --------- .../controllers/transactions/tx-state-manager.js | 3 - app/scripts/metamask-controller.js | 7 +++ app/scripts/platforms/extension.js | 64 ++++++++++++++++++++++ 4 files changed, 71 insertions(+), 30 deletions(-) delete mode 100644 app/scripts/controllers/transactions/lib/transaction-notification-manager.js (limited to 'app/scripts') diff --git a/app/scripts/controllers/transactions/lib/transaction-notification-manager.js b/app/scripts/controllers/transactions/lib/transaction-notification-manager.js deleted file mode 100644 index 6bdf40b62..000000000 --- a/app/scripts/controllers/transactions/lib/transaction-notification-manager.js +++ /dev/null @@ -1,27 +0,0 @@ -const extension = require('extensionizer') - -// Confirmed tx -// Transaction ${tx.nonce} confirmed! View on Etherscan - -// Failed tx -// Transaction ${tx.nonce} failed. (Maybe append tx.error.message) - -// Dropped tx -// A Transaction ${tx.nonce} was dropped, because another transaction with that number was successfully processed. - -function showConfirmedNotification (txMeta) { - extension.notifications.create({ - "type": "basic", - "title": "Confirmed transaction", - "iconUrl": extension.extension.getURL('../../../../images/icon-64.png'), - "message": JSON.stringify(txMeta) - }); -} - - -/** -@module -*/ -module.exports = { - showConfirmedNotification -} \ No newline at end of file diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js index 72da45913..28a18ca2e 100644 --- a/app/scripts/controllers/transactions/tx-state-manager.js +++ b/app/scripts/controllers/transactions/tx-state-manager.js @@ -5,7 +5,6 @@ const ethUtil = require('ethereumjs-util') const log = require('loglevel') const txStateHistoryHelper = require('./lib/tx-state-history-helper') const createId = require('../../lib/random-id') -const transactionNotificationManager = require('./lib/transaction-notification-manager') const { getFinalStates } = require('./lib/util') /** TransactionStateManager is responsible for the state of a transaction and @@ -333,8 +332,6 @@ class TransactionStateManager extends EventEmitter { */ setTxStatusConfirmed (txId) { this._setTxStatus(txId, 'confirmed') - const txMeta = this.getTx(txId) - transactionNotificationManager.showConfirmedNotification(txMeta) } /** diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 450113acf..31156b5b6 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -164,6 +164,13 @@ module.exports = class MetamaskController extends EventEmitter { }) this.txController.on('newUnapprovedTx', opts.showUnapprovedTx.bind(opts)) + this.txController.on(`tx:status-update`, (txId, status) => { + if (status === 'confirmed' || status === 'failed' || status === 'dropped') { + const txMeta = this.txController.txStateManager.getTx(txId) + this.platform.showTransactionNotification(txMeta) + } + }) + // computed balances (accounting for pending transactions) this.balancesController = new BalancesController({ accountTracker: this.accountTracker, diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js index f5cc255d1..afcc9bbca 100644 --- a/app/scripts/platforms/extension.js +++ b/app/scripts/platforms/extension.js @@ -1,4 +1,5 @@ const extension = require('extensionizer') +const explorerLink = require('etherscan-link').createExplorerLink class ExtensionPlatform { @@ -31,6 +32,69 @@ class ExtensionPlatform { cb(e) } } + + showTransactionNotification (txMeta) { + + const status = txMeta.status + if (status === 'confirmed') { + this._showConfirmedTransaction(txMeta) + } else if (status === 'failed') { + this._showFailedTransaction(txMeta) + } else if (status === 'dropped') { + this._showDroppedTransaction(txMeta) + } + } + + _showConfirmedTransaction (txMeta) { + + this._subscribeToNotificationClicked() + + const url = explorerLink(txMeta.hash, parseInt(txMeta.metamaskNetworkId)) + const nonce = parseInt(txMeta.txParams.nonce, 16) + + const title = 'Confirmed transaction' + const message = `Transaction ${nonce} confirmed! View on EtherScan` + this._showNotification(title, message, url) + } + + _showFailedTransaction (txMeta) { + + const nonce = parseInt(txMeta.txParams.nonce, 16) + const title = 'Failed transaction' + const message = `Transaction ${nonce} failed! ${txMeta.err.message}` + this._showNotification(title, message) + } + + _showDroppedTransaction (txMeta) { + + const nonce = parseInt(txMeta.txParams.nonce, 16) + const title = 'Dropped transaction' + const message = `Transaction ${nonce} was dropped, because another transaction with that number was successfully processed.` + this._showNotification(title, message) + } + + _showNotification (title, message, url) { + extension.notifications.create( + url, + { + 'type': 'basic', + 'title': title, + 'iconUrl': extension.extension.getURL('../../images/icon-64.png'), + 'message': message, + }) + } + + _subscribeToNotificationClicked () { + if (!extension.notifications.onClicked.hasListener(this._viewOnEtherScan)) { + extension.notifications.onClicked.addListener(this._viewOnEtherScan) + } + } + + _viewOnEtherScan (txId) { + if (txId.startsWith('http://')) { + global.metamaskController.platform.openWindow({ url: txId }) + } + } } module.exports = ExtensionPlatform -- cgit v1.2.3 From cb045fd8feec88bd631329ab9b3285aeed0f2e97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20Mi=C3=B1o?= Date: Fri, 20 Jul 2018 12:36:24 -0400 Subject: Auto-detect tokens #3034 (#4683) * detect tokens polling * network store to detect token * tests for spec * passtest-lint * fix lint * improve tests * detect tokens through infura * detect tokens when submit password and new account selected * keyring unlocked detect and unit tests * add changelog --- app/scripts/controllers/detect-tokens.js | 123 +++++++++++++++++++++++++++++++ app/scripts/metamask-controller.js | 12 ++- 2 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 app/scripts/controllers/detect-tokens.js (limited to 'app/scripts') diff --git a/app/scripts/controllers/detect-tokens.js b/app/scripts/controllers/detect-tokens.js new file mode 100644 index 000000000..f1810cfa1 --- /dev/null +++ b/app/scripts/controllers/detect-tokens.js @@ -0,0 +1,123 @@ +const Web3 = require('web3') +const contracts = require('eth-contract-metadata') +const { warn } = require('loglevel') +const { MAINNET } = require('./network/enums') +// By default, poll every 3 minutes +const DEFAULT_INTERVAL = 180 * 1000 +const ERC20_ABI = [{'constant': true, 'inputs': [{'name': '_owner', 'type': 'address'}], 'name': 'balanceOf', 'outputs': [{'name': 'balance', 'type': 'uint256'}], 'payable': false, 'type': 'function'}] + +/** + * A controller that polls for token exchange + * rates based on a user's current token list + */ +class DetectTokensController { + /** + * Creates a DetectTokensController + * + * @param {Object} [config] - Options to configure controller + */ + constructor ({ interval = DEFAULT_INTERVAL, preferences, network, keyringMemStore } = {}) { + this.preferences = preferences + this.interval = interval + this.network = network + this.keyringMemStore = keyringMemStore + } + + /** + * For each token in eth-contract-metada, find check selectedAddress balance. + * + */ + async detectNewTokens () { + if (!this.isActive) { return } + if (this._network.store.getState().provider.type !== MAINNET) { return } + this.web3.setProvider(this._network._provider) + for (const contractAddress in contracts) { + if (contracts[contractAddress].erc20 && !(this.tokenAddresses.includes(contractAddress.toLowerCase()))) { + this.detectTokenBalance(contractAddress) + } + } + } + + /** + * Find if selectedAddress has tokens with contract in contractAddress. + * + * @param {string} contractAddress Hex address of the token contract to explore. + * @returns {boolean} If balance is detected, token is added. + * + */ + async detectTokenBalance (contractAddress) { + const ethContract = this.web3.eth.contract(ERC20_ABI).at(contractAddress) + ethContract.balanceOf(this.selectedAddress, (error, result) => { + if (!error) { + if (!result.isZero()) { + this._preferences.addToken(contractAddress, contracts[contractAddress].symbol, contracts[contractAddress].decimals) + } + } else { + warn(`MetaMask - DetectTokensController balance fetch failed for ${contractAddress}.`, error) + } + }) + } + + /** + * Restart token detection polling period and call detectNewTokens + * in case of address change or user session initialization. + * + */ + restartTokenDetection () { + if (this.isActive && this.selectedAddress) { + this.detectNewTokens() + this.interval = DEFAULT_INTERVAL + } + } + + /** + * @type {Number} + */ + set interval (interval) { + this._handle && clearInterval(this._handle) + if (!interval) { return } + this._handle = setInterval(() => { this.detectNewTokens() }, interval) + } + + /** + * In setter when selectedAddress is changed, detectNewTokens and restart polling + * @type {Object} + */ + set preferences (preferences) { + if (!preferences) { return } + this._preferences = preferences + preferences.store.subscribe(({ tokens }) => { this.tokenAddresses = tokens.map((obj) => { return obj.address }) }) + preferences.store.subscribe(({ selectedAddress }) => { + if (this.selectedAddress !== selectedAddress) { + this.selectedAddress = selectedAddress + this.restartTokenDetection() + } + }) + } + + /** + * @type {Object} + */ + set network (network) { + if (!network) { return } + this._network = network + this.web3 = new Web3(network._provider) + } + + /** + * In setter when isUnlocked is updated to true, detectNewTokens and restart polling + * @type {Object} + */ + set keyringMemStore (keyringMemStore) { + if (!keyringMemStore) { return } + this._keyringMemStore = keyringMemStore + this._keyringMemStore.subscribe(({ isUnlocked }) => { + if (this.isUnlocked !== isUnlocked) { + if (isUnlocked) { this.restartTokenDetection() } + this.isUnlocked = isUnlocked + } + }) + } +} + +module.exports = DetectTokensController diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index dc5c24b1b..6f5908414 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -35,6 +35,7 @@ const TypedMessageManager = require('./lib/typed-message-manager') const TransactionController = require('./controllers/transactions') const BalancesController = require('./controllers/computed-balances') const TokenRatesController = require('./controllers/token-rates') +const DetectTokensController = require('./controllers/detect-tokens') const ConfigManager = require('./lib/config-manager') const nodeify = require('./lib/nodeify') const accountImporter = require('./account-import-strategies') @@ -147,6 +148,13 @@ module.exports = class MetamaskController extends EventEmitter { this.accountTracker.syncWithAddresses(addresses) }) + // detect tokens controller + this.detectTokensController = new DetectTokensController({ + preferences: this.preferencesController, + network: this.networkController, + keyringMemStore: this.keyringController.memStore, + }) + // address book controller this.addressBookController = new AddressBookController({ initState: initState.AddressBookController, @@ -1420,11 +1428,13 @@ module.exports = class MetamaskController extends EventEmitter { } /** - * A method for activating the retrieval of price data, which should only be fetched when the UI is visible. + * A method for activating the retrieval of price data and auto detect tokens, + * which should only be fetched when the UI is visible. * @private * @param {boolean} active - True if price data should be getting fetched. */ set isClientOpenAndUnlocked (active) { this.tokenRatesController.isActive = active + this.detectTokensController.isActive = active } } -- cgit v1.2.3 From 72591d4f41aafa7556cbc37d613fcbe016444156 Mon Sep 17 00:00:00 2001 From: Csaba Solya Date: Fri, 20 Jul 2018 19:58:26 +0200 Subject: remove dropped handler --- app/scripts/metamask-controller.js | 2 +- app/scripts/platforms/extension.js | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 31156b5b6..939a30d74 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -165,7 +165,7 @@ module.exports = class MetamaskController extends EventEmitter { this.txController.on('newUnapprovedTx', opts.showUnapprovedTx.bind(opts)) this.txController.on(`tx:status-update`, (txId, status) => { - if (status === 'confirmed' || status === 'failed' || status === 'dropped') { + if (status === 'confirmed' || status === 'failed') { const txMeta = this.txController.txStateManager.getTx(txId) this.platform.showTransactionNotification(txMeta) } diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js index afcc9bbca..60ecfeff4 100644 --- a/app/scripts/platforms/extension.js +++ b/app/scripts/platforms/extension.js @@ -40,8 +40,6 @@ class ExtensionPlatform { this._showConfirmedTransaction(txMeta) } else if (status === 'failed') { this._showFailedTransaction(txMeta) - } else if (status === 'dropped') { - this._showDroppedTransaction(txMeta) } } @@ -65,14 +63,6 @@ class ExtensionPlatform { this._showNotification(title, message) } - _showDroppedTransaction (txMeta) { - - const nonce = parseInt(txMeta.txParams.nonce, 16) - const title = 'Dropped transaction' - const message = `Transaction ${nonce} was dropped, because another transaction with that number was successfully processed.` - this._showNotification(title, message) - } - _showNotification (title, message, url) { extension.notifications.create( url, -- cgit v1.2.3 From 9c955549338f49d8b5eb6ca003c2c65c725aa328 Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Fri, 20 Jul 2018 19:58:03 -0400 Subject: fix detection on submit password --- app/scripts/controllers/detect-tokens.js | 13 ++++++++----- app/scripts/metamask-controller.js | 5 ++--- 2 files changed, 10 insertions(+), 8 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/detect-tokens.js b/app/scripts/controllers/detect-tokens.js index f1810cfa1..4fe4b4c61 100644 --- a/app/scripts/controllers/detect-tokens.js +++ b/app/scripts/controllers/detect-tokens.js @@ -64,10 +64,9 @@ class DetectTokensController { * */ restartTokenDetection () { - if (this.isActive && this.selectedAddress) { - this.detectNewTokens() - this.interval = DEFAULT_INTERVAL - } + if (!(this.isActive && this.selectedAddress)) { return } + this.detectNewTokens() + this.interval = DEFAULT_INTERVAL } /** @@ -113,11 +112,15 @@ class DetectTokensController { this._keyringMemStore = keyringMemStore this._keyringMemStore.subscribe(({ isUnlocked }) => { if (this.isUnlocked !== isUnlocked) { - if (isUnlocked) { this.restartTokenDetection() } this.isUnlocked = isUnlocked + if (isUnlocked) { this.restartTokenDetection() } } }) } + + get isActive () { + return this.isOpen && this.isUnlocked + } } module.exports = DetectTokensController diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 4e97ce583..d3650815e 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1274,16 +1274,15 @@ module.exports = class MetamaskController extends EventEmitter { set isClientOpen (open) { this._isClientOpen = open this.isClientOpenAndUnlocked = this.getState().isUnlocked && open + this.detectTokensController.isOpen = open } /** - * A method for activating the retrieval of price data and auto detect tokens, - * which should only be fetched when the UI is visible. + * A method for activating the retrieval of price data, which should only be fetched when the UI is visible. * @private * @param {boolean} active - True if price data should be getting fetched. */ set isClientOpenAndUnlocked (active) { this.tokenRatesController.isActive = active - this.detectTokensController.isActive = active } } -- cgit v1.2.3 From 9be22775c31699b49873a43820dc315067a567a9 Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Fri, 20 Jul 2018 20:15:33 -0400 Subject: fix merge --- app/scripts/metamask-controller.js | 1 - 1 file changed, 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 18448961d..3b303a95c 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1443,6 +1443,5 @@ module.exports = class MetamaskController extends EventEmitter { */ set isClientOpenAndUnlocked (active) { this.tokenRatesController.isActive = active - this.detectTokensController.isActive = active } } -- cgit v1.2.3 From 5ebefc0e502696098599a4a17f185a88aaeed628 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Sat, 21 Jul 2018 16:03:31 -0400 Subject: run linter --- app/scripts/controllers/detect-tokens.js | 2 +- app/scripts/metamask-controller.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/detect-tokens.js b/app/scripts/controllers/detect-tokens.js index b30dc00f1..195ec918a 100644 --- a/app/scripts/controllers/detect-tokens.js +++ b/app/scripts/controllers/detect-tokens.js @@ -117,7 +117,7 @@ class DetectTokensController { } }) } - + /** * Internal isActive state * @type {Object} diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 3b303a95c..bcc7075c2 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1436,7 +1436,7 @@ module.exports = class MetamaskController extends EventEmitter { } /** - * A method for activating the retrieval of price data and auto detect tokens, + * A method for activating the retrieval of price data and auto detect tokens, * which should only be fetched when the UI is visible. * @private * @param {boolean} active - True if price data should be getting fetched. -- cgit v1.2.3 From 2359062b62cf65f38b36ccb6bb33fa7d15ada1ae Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 23 Jul 2018 22:20:06 -0230 Subject: UI confirm screen closes confirmation window on submit or cancel of a tx --- app/scripts/platforms/extension.js | 6 ++++++ app/scripts/ui.js | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js index 901c26cab..bd4f7bd9f 100644 --- a/app/scripts/platforms/extension.js +++ b/app/scripts/platforms/extension.js @@ -14,6 +14,12 @@ class ExtensionPlatform { extension.tabs.create({ url }) } + closeCurrentWindow (cb) { + return extension.windows.getCurrent((windowDetails) => { + return extension.windows.remove(windowDetails.id) + }) + } + getVersion () { return extension.runtime.getManifest().version } diff --git a/app/scripts/ui.js b/app/scripts/ui.js index 9bf97be87..42703f029 100644 --- a/app/scripts/ui.js +++ b/app/scripts/ui.js @@ -64,16 +64,16 @@ async function start () { css = betaUIState ? NewMetaMaskUiCss() : OldMetaMaskUiCss() deleteInjectedCss = injectCss(css) } - if (state.appState.shouldClose) notificationManager.closePopup() + // if (state.appState.shouldClose) notificationManager.closePopup() }) }) function closePopupIfOpen (windowType) { - if (windowType !== ENVIRONMENT_TYPE_NOTIFICATION) { + // if (windowType !== ENVIRONMENT_TYPE_NOTIFICATION) { // should close only chrome popup - notificationManager.closePopup() - } + // notificationManager.closePopup() + // } } function displayCriticalError (err) { -- cgit v1.2.3 From 64a82fd3dae66f33b0934d58e43e85df27fbfe1d Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Tue, 24 Jul 2018 11:45:36 -0230 Subject: Uncomment closePopupIfOpen code. --- app/scripts/ui.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/ui.js b/app/scripts/ui.js index 42703f029..cbe15c92d 100644 --- a/app/scripts/ui.js +++ b/app/scripts/ui.js @@ -70,10 +70,10 @@ async function start () { function closePopupIfOpen (windowType) { - // if (windowType !== ENVIRONMENT_TYPE_NOTIFICATION) { + if (windowType !== ENVIRONMENT_TYPE_NOTIFICATION) { // should close only chrome popup - // notificationManager.closePopup() - // } + notificationManager.closePopup() + } } function displayCriticalError (err) { -- cgit v1.2.3 From a61227f224e37559c8d6e2c59441b0032633feaf Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Tue, 24 Jul 2018 15:01:03 -0230 Subject: Remove unneeded code from i4829-close-notifications-from-ui branch. --- app/scripts/platforms/extension.js | 2 +- app/scripts/ui.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js index bd4f7bd9f..0803164e8 100644 --- a/app/scripts/platforms/extension.js +++ b/app/scripts/platforms/extension.js @@ -14,7 +14,7 @@ class ExtensionPlatform { extension.tabs.create({ url }) } - closeCurrentWindow (cb) { + closeCurrentWindow () { return extension.windows.getCurrent((windowDetails) => { return extension.windows.remove(windowDetails.id) }) diff --git a/app/scripts/ui.js b/app/scripts/ui.js index cbe15c92d..da100f928 100644 --- a/app/scripts/ui.js +++ b/app/scripts/ui.js @@ -64,7 +64,6 @@ async function start () { css = betaUIState ? NewMetaMaskUiCss() : OldMetaMaskUiCss() deleteInjectedCss = injectCss(css) } - // if (state.appState.shouldClose) notificationManager.closePopup() }) }) -- cgit v1.2.3 From 3667f3cb8556d55ad893ec8c1a0f84447273906f Mon Sep 17 00:00:00 2001 From: pinkiebell <40266861+pinkiebell@users.noreply.github.com> Date: Wed, 25 Jul 2018 00:03:36 +0200 Subject: network.js: convert rpc protocol to lower case (#4855) Fixes #4253 --- app/scripts/controllers/network/network.js | 3 +++ 1 file changed, 3 insertions(+) (limited to 'app/scripts') diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js index a50f6dc45..b6f7705b5 100644 --- a/app/scripts/controllers/network/network.js +++ b/app/scripts/controllers/network/network.js @@ -9,6 +9,7 @@ const extend = require('xtend') const EthQuery = require('eth-query') const createEventEmitterProxy = require('../../lib/events-proxy.js') const log = require('loglevel') +const urlUtil = require('url') const { ROPSTEN, RINKEBY, @@ -155,6 +156,8 @@ module.exports = class NetworkController extends EventEmitter { } _configureStandardProvider ({ rpcUrl }) { + // urlUtil handles malformed urls + rpcUrl = urlUtil.parse(rpcUrl).format() const providerParams = extend(this._baseProviderParams, { rpcUrl, engineParams: { -- cgit v1.2.3 From 527b62ee8e57335360402c2e121bf34d3e621a8c Mon Sep 17 00:00:00 2001 From: frankiebee Date: Wed, 25 Jul 2018 14:11:07 -0700 Subject: migrations - fix to include 27 --- app/scripts/migrations/index.js | 1 + 1 file changed, 1 insertion(+) (limited to 'app/scripts') diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index 04d90bfff..bd0005221 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -37,4 +37,5 @@ module.exports = [ require('./024'), require('./025'), require('./026'), + require('./027'), ] -- cgit v1.2.3