Merge pull request #1027 from MetaMask/Version-3.1.0

Version 3.1.0
feature/default_network_editable
Dan Finlay 8 years ago committed by GitHub
commit 25e5793992
  1. 5
      .eslintrc
  2. 8
      CHANGELOG.md
  3. 2
      app/manifest.json
  4. 6
      app/scripts/keyring-controller.js
  5. 2
      app/scripts/keyrings/hd.js
  6. 21
      app/scripts/keyrings/simple.js
  7. 2
      app/scripts/lib/config-manager.js
  8. 7
      app/scripts/metamask-controller.js
  9. 11
      app/scripts/transaction-manager.js
  10. 84
      development/states/account-list-with-imported.json
  11. 124
      development/states/compilation-bug.json
  12. 92
      development/states/import-private-key-warning.json
  13. 64
      development/states/import-private-key.json
  14. 66
      development/states/new-account.json
  15. 12
      notices/notice_0.md
  16. 1
      package.json
  17. 3
      test/integration/lib/first-time.js
  18. 18
      test/unit/keyrings/simple-test.js
  19. 91
      ui/app/accounts/import/index.js
  20. 27
      ui/app/accounts/import/json.js
  21. 69
      ui/app/accounts/import/private-key.js
  22. 30
      ui/app/accounts/import/seed.js
  23. 10
      ui/app/accounts/index.js
  24. 26
      ui/app/actions.js
  25. 11
      ui/app/app.js
  26. 82
      ui/app/components/buy-button-subview.js
  27. 35
      ui/app/components/tab-bar.js
  28. 15
      ui/app/conf-tx.js
  29. 8
      ui/app/css/lib.css
  30. 2
      ui/app/info.js
  31. 17
      ui/app/reducers/app.js
  32. 2
      ui/app/unlock.js
  33. 1
      ui/css.js

@ -1,5 +1,6 @@
{ {
"parserOptions": { "parserOptions": {
"sourceType": "module",
"ecmaVersion": 6, "ecmaVersion": 6,
"ecmaFeatures": { "ecmaFeatures": {
"experimentalObjectRestSpread": true, "experimentalObjectRestSpread": true,
@ -44,7 +45,7 @@
"eol-last": 1, "eol-last": 1,
"eqeqeq": [2, "allow-null"], "eqeqeq": [2, "allow-null"],
"generator-star-spacing": [2, { "before": true, "after": true }], "generator-star-spacing": [2, { "before": true, "after": true }],
"handle-callback-err": [2, "^(err|error)$" ], "handle-callback-err": [1, "^(err|error)$" ],
"indent": [2, 2, { "SwitchCase": 1 }], "indent": [2, 2, { "SwitchCase": 1 }],
"jsx-quotes": [2, "prefer-single"], "jsx-quotes": [2, "prefer-single"],
"key-spacing": [2, { "beforeColon": false, "afterColon": true }], "key-spacing": [2, { "beforeColon": false, "afterColon": true }],
@ -145,6 +146,6 @@
"wrap-iife": [2, "any"], "wrap-iife": [2, "any"],
"yield-star-spacing": [2, "both"], "yield-star-spacing": [2, "both"],
"yoda": [2, "never"], "yoda": [2, "never"],
"prefer-const": 1 "prefer-const": 1,
} }
} }

@ -2,6 +2,14 @@
## Current Master ## Current Master
## 3.1.0 2017-1-18
- Add ability to import accounts by private key.
- Fixed bug that returned the wrong transaction hashes on private networks that had not implemented EIP 155 replay protection (like TestRPC).
## 3.0.1 2017-1-17
- Fixed bug that prevented eth.sign from working.
- Fix the displaying of transactions that have been submitted to the network in Transaction History - Fix the displaying of transactions that have been submitted to the network in Transaction History
## 3.0.0 2017-1-16 ## 3.0.0 2017-1-16

@ -1,7 +1,7 @@
{ {
"name": "MetaMask", "name": "MetaMask",
"short_name": "Metamask", "short_name": "Metamask",
"version": "3.0.0", "version": "3.1.0",
"manifest_version": 2, "manifest_version": 2,
"author": "https://metamask.io", "author": "https://metamask.io",
"description": "Ethereum Browser Extension", "description": "Ethereum Browser Extension",

@ -234,7 +234,10 @@ module.exports = class KeyringController extends EventEmitter {
addNewKeyring (type, opts) { addNewKeyring (type, opts) {
const Keyring = this.getKeyringClassForType(type) const Keyring = this.getKeyringClassForType(type)
const keyring = new Keyring(opts) const keyring = new Keyring(opts)
return keyring.getAccounts() return keyring.deserialize(opts)
.then(() => {
return keyring.getAccounts()
})
.then((accounts) => { .then((accounts) => {
this.keyrings.push(keyring) this.keyrings.push(keyring)
return this.setupAccounts(accounts) return this.setupAccounts(accounts)
@ -397,6 +400,7 @@ module.exports = class KeyringController extends EventEmitter {
}).then((rawSig) => { }).then((rawSig) => {
cb(null, rawSig) cb(null, rawSig)
approvalCb(null, true) approvalCb(null, true)
messageManager.confirmMsg(msgId)
return rawSig return rawSig
}) })
} catch (e) { } catch (e) {

@ -76,7 +76,7 @@ class HdKeyring extends EventEmitter {
// For eth_sign, we need to sign transactions: // For eth_sign, we need to sign transactions:
signMessage (withAccount, data) { signMessage (withAccount, data) {
const wallet = this._getWalletForAccount(withAccount) const wallet = this._getWalletForAccount(withAccount)
const message = ethUtil.removeHexPrefix(data) const message = ethUtil.stripHexPrefix(data)
var privKey = wallet.getPrivateKey() var privKey = wallet.getPrivateKey()
var msgSig = ethUtil.ecsign(new Buffer(message, 'hex'), privKey) var msgSig = ethUtil.ecsign(new Buffer(message, 'hex'), privKey)
var rawMsgSig = ethUtil.bufferToHex(sigUtil.concatSig(msgSig.v, msgSig.r, msgSig.s)) var rawMsgSig = ethUtil.bufferToHex(sigUtil.concatSig(msgSig.v, msgSig.r, msgSig.s))

@ -20,13 +20,19 @@ class SimpleKeyring extends EventEmitter {
} }
deserialize (privateKeys = []) { deserialize (privateKeys = []) {
this.wallets = privateKeys.map((privateKey) => { return new Promise((resolve, reject) => {
const stripped = ethUtil.stripHexPrefix(privateKey) try {
const buffer = new Buffer(stripped, 'hex') this.wallets = privateKeys.map((privateKey) => {
const wallet = Wallet.fromPrivateKey(buffer) const stripped = ethUtil.stripHexPrefix(privateKey)
return wallet const buffer = new Buffer(stripped, 'hex')
const wallet = Wallet.fromPrivateKey(buffer)
return wallet
})
} catch (e) {
reject(e)
}
resolve()
}) })
return Promise.resolve()
} }
addAccounts (n = 1) { addAccounts (n = 1) {
@ -54,8 +60,7 @@ class SimpleKeyring extends EventEmitter {
// For eth_sign, we need to sign transactions: // For eth_sign, we need to sign transactions:
signMessage (withAccount, data) { signMessage (withAccount, data) {
const wallet = this._getWalletForAccount(withAccount) const wallet = this._getWalletForAccount(withAccount)
const message = ethUtil.stripHexPrefix(data)
const message = ethUtil.removeHexPrefix(data)
var privKey = wallet.getPrivateKey() var privKey = wallet.getPrivateKey()
var msgSig = ethUtil.ecsign(new Buffer(message, 'hex'), privKey) var msgSig = ethUtil.ecsign(new Buffer(message, 'hex'), privKey)
var rawMsgSig = ethUtil.bufferToHex(sigUtil.concatSig(msgSig.v, msgSig.r, msgSig.s)) var rawMsgSig = ethUtil.bufferToHex(sigUtil.concatSig(msgSig.v, msgSig.r, msgSig.s))

@ -306,7 +306,7 @@ ConfigManager.prototype.updateConversionRate = function () {
this.setConversionPrice(parsedResponse.ticker.price) this.setConversionPrice(parsedResponse.ticker.price)
this.setConversionDate(parsedResponse.timestamp) this.setConversionDate(parsedResponse.timestamp)
}).catch((err) => { }).catch((err) => {
console.error('Error in conversion.', err) console.warn('MetaMask - Failed to query currency conversion.')
this.setConversionPrice(0) this.setConversionPrice(0)
this.setConversionDate('N/A') this.setConversionDate('N/A')
}) })

@ -115,7 +115,12 @@ module.exports = class MetamaskController extends EventEmitter {
.then((newState) => { cb(null, newState) }) .then((newState) => { cb(null, newState) })
.catch((reason) => { cb(reason) }) .catch((reason) => { cb(reason) })
}, },
addNewKeyring: nodeify(keyringController.addNewKeyring).bind(keyringController), addNewKeyring: (type, opts, cb) => {
keyringController.addNewKeyring(type, opts)
.then(() => keyringController.fullUpdate())
.then((newState) => { cb(null, newState) })
.catch((reason) => { cb(reason) })
},
addNewAccount: nodeify(keyringController.addNewAccount).bind(keyringController), addNewAccount: nodeify(keyringController.addNewAccount).bind(keyringController),
setSelectedAccount: nodeify(keyringController.setSelectedAccount).bind(keyringController), setSelectedAccount: nodeify(keyringController.setSelectedAccount).bind(keyringController),
saveAccountLabel: nodeify(keyringController.saveAccountLabel).bind(keyringController), saveAccountLabel: nodeify(keyringController.saveAccountLabel).bind(keyringController),

@ -190,7 +190,7 @@ module.exports = class TransactionManager extends EventEmitter {
let fromAddress = txParams.from let fromAddress = txParams.from
let ethTx = this.txProviderUtils.buildEthTxFromParams(txParams, txMeta.gasMultiplier) let ethTx = this.txProviderUtils.buildEthTxFromParams(txParams, txMeta.gasMultiplier)
this.signEthTx(ethTx, fromAddress).then(() => { this.signEthTx(ethTx, fromAddress).then(() => {
this.updateTxAsSigned(txMeta.id, ethTx) this.setTxStatusSigned(txMeta.id)
cb(null, ethUtil.bufferToHex(ethTx.serialize())) cb(null, ethUtil.bufferToHex(ethTx.serialize()))
}).catch((err) => { }).catch((err) => {
cb(err) cb(err)
@ -198,21 +198,20 @@ module.exports = class TransactionManager extends EventEmitter {
} }
publishTransaction (txId, rawTx, cb) { publishTransaction (txId, rawTx, cb) {
this.txProviderUtils.publishTransaction(rawTx, (err) => { this.txProviderUtils.publishTransaction(rawTx, (err, txHash) => {
if (err) return cb(err) if (err) return cb(err)
this.setTxHash(txId, txHash)
this.setTxStatusSubmitted(txId) this.setTxStatusSubmitted(txId)
cb() cb()
}) })
} }
// receives a signed tx object and updates the tx hash // receives a txHash records the tx as signed
updateTxAsSigned (txId, ethTx) { setTxHash (txId, txHash) {
// Add the tx hash to the persisted meta-tx object // Add the tx hash to the persisted meta-tx object
let txHash = ethUtil.bufferToHex(ethTx.hash())
let txMeta = this.getTx(txId) let txMeta = this.getTx(txId)
txMeta.hash = txHash txMeta.hash = txHash
this.updateTx(txMeta) this.updateTx(txMeta)
this.setTxStatusSigned(txMeta.id)
} }
/* /*

@ -0,0 +1,84 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0x58bda1f9d87dc7d2bcc6f7c2513efc9d03fca683": {
"address": "0x58bda1f9d87dc7d2bcc6f7c2513efc9d03fca683",
"name": "Account 1"
},
"0x9858e7d8b79fc3e6d989636721584498926da38a": {
"address": "0x9858e7d8b79fc3e6d989636721584498926da38a",
"name": "Imported Account"
}
},
"unconfTxs": {},
"currentFiat": "USD",
"conversionRate": 10.19458075,
"conversionDate": 1484696373,
"noActiveNotices": true,
"network": "3",
"accounts": {
"0x58bda1f9d87dc7d2bcc6f7c2513efc9d03fca683": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0x58bda1f9d87dc7d2bcc6f7c2513efc9d03fca683"
},
"0x9858e7d8b79fc3e6d989636721584498926da38a": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0x9858e7d8b79fc3e6d989636721584498926da38a"
}
},
"transactions": [],
"provider": {
"type": "testnet"
},
"selectedAccount": "0x9858e7d8b79fc3e6d989636721584498926da38a",
"selectedAccountTxList": [],
"isDisclaimerConfirmed": true,
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],
"keyringTypes": [
"Simple Key Pair",
"HD Key Tree"
],
"keyrings": [
{
"type": "HD Key Tree",
"accounts": [
"58bda1f9d87dc7d2bcc6f7c2513efc9d03fca683"
]
},
{
"type": "Simple Key Pair",
"accounts": [
"0x9858e7d8b79fc3e6d989636721584498926da38a"
]
}
],
"lostAccounts": [],
"seedWords": null
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "accounts"
},
"accountDetail": {
"subview": "transactions",
"accountExport": "none",
"privateKey": ""
},
"transForward": true,
"isLoading": false,
"warning": null,
"scrollToBottom": false,
"forgottenPassword": false
},
"identities": {}
}

@ -0,0 +1,124 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9": {
"address": "0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9",
"name": "Account 1"
},
"0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4": {
"address": "0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4",
"name": "Account 2"
},
"0x1acfb961c5a8268eac8e09d6241a26cbeff42241": {
"address": "0x1acfb961c5a8268eac8e09d6241a26cbeff42241",
"name": "Account 3"
},
"0xabc2bca51709b8615147352c62420f547a63a00c": {
"address": "0xabc2bca51709b8615147352c62420f547a63a00c",
"name": "Account 4"
}
},
"unconfTxs": {
"7992944905869041": {
"id": 7992944905869041,
"txParams": {
"from": "0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9",
"value": "0x0",
"data": "0x606060405234610000575b60da806100186000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630dbe671f14603c575b6000565b3460005760466088565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16815600a165627a7a72305820a99dfa6091771f518dd1ae8d1ee347bae3304dffd98fd24b1b99a8380bc60a750029",
"gas": "0x1af75",
"metamaskId": 7992944905869041,
"metamaskNetworkId": "3"
},
"time": 1482279685589,
"status": "unconfirmed",
"gasMultiplier": 1,
"metamaskNetworkId": "3",
"gasLimitSpecified": true,
"estimatedGas": "0x1af75",
"simulationFails": true
}
},
"currentFiat": "USD",
"conversionRate": 7.69158136,
"conversionDate": 1482279663,
"noActiveNotices": true,
"network": "3",
"accounts": {
"0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9": {
"code": "0x",
"nonce": "0x3",
"balance": "0x11f646fe14c9c000",
"address": "0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9"
},
"0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4": {
"code": "0x",
"nonce": "0x0",
"balance": "0x0",
"address": "0xd7c0cd9e7d2701c710d64fc492c7086679bdf7b4"
},
"0x1acfb961c5a8268eac8e09d6241a26cbeff42241": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0x1acfb961c5a8268eac8e09d6241a26cbeff42241"
},
"0xabc2bca51709b8615147352c62420f547a63a00c": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0xabc2bca51709b8615147352c62420f547a63a00c"
}
},
"transactions": [
{
"id": 7992944905869041,
"txParams": {
"from": "0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9",
"value": "0x0",
"data": "0x606060405234610000575b60da806100186000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630dbe671f14603c575b6000565b3460005760466088565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16815600a165627a7a72305820a99dfa6091771f518dd1ae8d1ee347bae3304dffd98fd24b1b99a8380bc60a750029",
"gas": "0x1af75",
"metamaskId": 7992944905869041,
"metamaskNetworkId": "3"
},
"time": 1482279685589,
"status": "unconfirmed",
"gasMultiplier": 1,
"metamaskNetworkId": "3",
"gasLimitSpecified": true,
"estimatedGas": "0x1af75",
"simulationFails": true
}
],
"provider": {
"type": "testnet"
},
"selectedAccount": "0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9",
"seedWords": false,
"isDisclaimerConfirmed": true,
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],
"keyringTypes": [
"Simple Key Pair",
"HD Key Tree"
],
"lostAccounts": []
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "confTx",
"context": 0
},
"accountDetail": {
"subview": "transactions"
},
"transForward": true,
"isLoading": false,
"warning": null
},
"identities": {}
}

@ -0,0 +1,92 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0x01208723ba84e15da2e71656544a2963b0c06d40": {
"address": "0x01208723ba84e15da2e71656544a2963b0c06d40",
"name": "Account 1"
}
},
"unconfTxs": {},
"currentFiat": "USD",
"conversionRate": 10.1219126,
"conversionDate": 1484695442,
"noActiveNotices": true,
"network": "3",
"accounts": {
"0x01208723ba84e15da2e71656544a2963b0c06d40": {
"nonce": "0x0",
"balance": "0x0",
"code": "0x",
"address": "0x01208723ba84e15da2e71656544a2963b0c06d40"
}
},
"transactions": [],
"provider": {
"type": "testnet"
},
"selectedAccount": "0x01208723ba84e15da2e71656544a2963b0c06d40",
"selectedAccountTxList": [],
"seedWords": false,
"isDisclaimerConfirmed": true,
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],
"keyringTypes": [
"Simple Key Pair",
"HD Key Tree"
],
"keyrings": [
{
"type": "Simple Key Pair",
"accounts": []
},
{
"type": "Simple Key Pair",
"accounts": []
},
{
"type": "Simple Key Pair",
"accounts": []
},
{
"type": "Simple Key Pair",
"accounts": []
},
{
"type": "Simple Key Pair",
"accounts": []
},
{
"type": "Simple Key Pair",
"accounts": []
},
{
"type": "Simple Key Pair",
"accounts": []
},
{
"type": "HD Key Tree",
"accounts": [
"01208723ba84e15da2e71656544a2963b0c06d40"
]
}
],
"lostAccounts": []
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "import-menu"
},
"accountDetail": {
"subview": "transactions"
},
"transForward": true,
"isLoading": false,
"warning": "Invalid hex string"
},
"identities": {}
}

@ -0,0 +1,64 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0x01208723ba84e15da2e71656544a2963b0c06d40": {
"address": "0x01208723ba84e15da2e71656544a2963b0c06d40",
"name": "Account 1"
}
},
"unconfTxs": {},
"currentFiat": "USD",
"conversionRate": 10.10788584,
"conversionDate": 1484694362,
"noActiveNotices": true,
"network": "3",
"accounts": {
"0x01208723ba84e15da2e71656544a2963b0c06d40": {
"balance": "0x0",
"code": "0x",
"nonce": "0x0",
"address": "0x01208723ba84e15da2e71656544a2963b0c06d40"
}
},
"transactions": [],
"provider": {
"type": "testnet"
},
"selectedAccount": "0x01208723ba84e15da2e71656544a2963b0c06d40",
"selectedAccountTxList": [],
"seedWords": null,
"isDisclaimerConfirmed": true,
"unconfMsgs": {},
"messages": [],
"shapeShiftTxList": [],
"keyringTypes": [
"Simple Key Pair",
"HD Key Tree"
],
"keyrings": [
{
"type": "HD Key Tree",
"accounts": [
"01208723ba84e15da2e71656544a2963b0c06d40"
]
}
],
"lostAccounts": []
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "import-menu"
},
"accountDetail": {
"subview": "transactions"
},
"transForward": true,
"isLoading": false,
"warning": null
},
"identities": {}
}

@ -0,0 +1,66 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0xa6ef573d60594731178b7f85d80da13cc2af52dd": {
"address": "0xa6ef573d60594731178b7f85d80da13cc2af52dd",
"name": "Dan! 1"
},
"0xf9f52e84ad2c9122caa87478d27041ddaa215666": {
"address": "0xf9f52e84ad2c9122caa87478d27041ddaa215666",
"name": "Account 2"
}
},
"unconfTxs": {},
"currentFiat": "USD",
"conversionRate": 10.92067835,
"conversionDate": 1478282884,
"network": null,
"accounts": {
"0xa6ef573d60594731178b7f85d80da13cc2af52dd": {
"balance": "0x00",
"nonce": "0x100000",
"code": "0x",
"address": "0xa6ef573d60594731178b7f85d80da13cc2af52dd"
},
"0xf9f52e84ad2c9122caa87478d27041ddaa215666": {
"balance": "0x00",
"nonce": "0x100000",
"code": "0x",
"address": "0xf9f52e84ad2c9122caa87478d27041ddaa215666"
}
},
"transactions": [],
"provider": {
"type": "testnet"
},
"selectedAccount": "0xa6ef573d60594731178b7f85d80da13cc2af52dd",
"isConfirmed": true,
"unconfMsgs": {},
"messages": [],
"selectedAddress": "0xa6ef573d60594731178b7f85d80da13cc2af52dd",
"shapeShiftTxList": [],
"keyringTypes": [
"Simple Key Pair",
"HD Key Tree"
]
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "new-account"
},
"accountDetail": {
"subview": "transactions"
},
"transForward": true,
"isLoading": false,
"warning": null,
"forgottenPassword": null,
"detailView": {},
"scrollToBottom": false
},
"identities": {}
}

@ -1,12 +0,0 @@
Due to [recent events](https://blog.ethereum.org/2016/11/20/from-morden-to-ropsten/), MetaMask is now deprecating support for the Morden Test Network.
Users will still be able to access Morden through a locally hosted node, but we will no longer be providing hosted access to this network through [Infura](http://infura.io/).
Please use the new Ropsten Network as your new default test network.
You can fund your Ropsten account using the buy button on your account page.
Best wishes!
The MetaMask Team

@ -83,6 +83,7 @@
"react-hyperscript": "^2.2.2", "react-hyperscript": "^2.2.2",
"react-markdown": "^2.3.0", "react-markdown": "^2.3.0",
"react-redux": "^4.4.5", "react-redux": "^4.4.5",
"react-select": "^1.0.0-rc.2",
"react-tooltip-component": "^0.3.0", "react-tooltip-component": "^0.3.0",
"readable-stream": "^2.1.2", "readable-stream": "^2.1.2",
"redux": "^3.0.5", "redux": "^3.0.5",

@ -66,7 +66,8 @@ QUnit.test('agree to terms', function (assert) {
}).then(function() { }).then(function() {
var sandwich = app.find('.menu-droppo')[0] var sandwich = app.find('.menu-droppo')[0]
var lock = sandwich.children[2] var children = sandwich.children
var lock = children[children.length - 2]
assert.ok(lock, 'Lock menu item found') assert.ok(lock, 'Lock menu item found')
lock.click() lock.click()

@ -49,6 +49,24 @@ describe('simple-keyring', function() {
}) })
}) })
describe('#signMessage', function() {
const address = '0x9858e7d8b79fc3e6d989636721584498926da38a'
const message = '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0'
const privateKey = '0x7dd98753d7b4394095de7d176c58128e2ed6ee600abe97c9f6d9fd65015d9b18'
const expectedResult = '0x28fcb6768e5110144a55b2e6ce9d1ea5a58103033632d272d2b5cf506906f7941a00b539383fd872109633d8c71c404e13dba87bc84166ee31b0e36061a69e161c'
it('passes the dennis test', function(done) {
keyring.deserialize([ privateKey ])
.then(() => {
return keyring.signMessage(address, message)
})
.then((result) => {
assert.equal(result, expectedResult)
done()
})
})
})
describe('#addAccounts', function() { describe('#addAccounts', function() {
describe('with no arguments', function() { describe('with no arguments', function() {
it('creates a single wallet', function() { it('creates a single wallet', function() {

@ -0,0 +1,91 @@
const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
import Select from 'react-select'
// Subviews
const JsonImportView = require('./json.js')
const SeedImportView = require('./seed.js')
const PrivateKeyImportView = require('./private-key.js')
const menuItems = [
'Private Key',
]
module.exports = connect(mapStateToProps)(AccountImportSubview)
function mapStateToProps (state) {
return {
menuItems,
}
}
inherits(AccountImportSubview, Component)
function AccountImportSubview () {
Component.call(this)
}
AccountImportSubview.prototype.render = function () {
const props = this.props
const state = this.state || {}
const { menuItems } = props
const { type } = state
return (
h('div', {
style: {
},
}, [
h('div', {
style: {
padding: '10px',
color: 'rgb(174, 174, 174)',
},
}, [
h('h3', { style: { padding: '3px' } }, 'SELECT TYPE'),
h('style', `
.has-value.Select--single > .Select-control .Select-value .Select-value-label, .Select-value-label {
color: rgb(174,174,174);
}
`),
h(Select, {
name: 'import-type-select',
clearable: false,
value: type || menuItems[0],
options: menuItems.map((type) => {
return {
value: type,
label: type,
}
}),
onChange: (opt) => {
this.setState({ type: opt.value })
},
}),
]),
this.renderImportView(),
])
)
}
AccountImportSubview.prototype.renderImportView = function() {
const props = this.props
const state = this.state || {}
const { type } = state
const { menuItems } = props
const current = type || menuItems[0]
switch (current) {
case 'HD Key Tree':
return h(SeedImportView)
case 'Private Key':
return h(PrivateKeyImportView)
default:
return h(JsonImportView)
}
}

@ -0,0 +1,27 @@
const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
module.exports = connect(mapStateToProps)(JsonImportSubview)
function mapStateToProps (state) {
return {}
}
inherits(JsonImportSubview, Component)
function JsonImportSubview () {
Component.call(this)
}
JsonImportSubview.prototype.render = function () {
return (
h('div', {
style: {
},
}, [
`Upload your json file here!`,
])
)
}

@ -0,0 +1,69 @@
const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const type = 'Simple Key Pair'
const actions = require('../../actions')
module.exports = connect(mapStateToProps)(PrivateKeyImportView)
function mapStateToProps (state) {
return {
error: state.appState.warning,
}
}
inherits(PrivateKeyImportView, Component)
function PrivateKeyImportView () {
Component.call(this)
}
PrivateKeyImportView.prototype.render = function () {
const { error } = this.props
return (
h('div', {
style: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: '5px 15px 0px 15px',
},
}, [
h('span', 'Paste your private key string here'),
h('input.large-input.letter-spacey', {
type: 'password',
id: 'private-key-box',
onKeyPress: this.createKeyringOnEnter.bind(this),
style: {
width: 260,
marginTop: 12,
},
}),
h('button.primary', {
onClick: this.createNewKeychain.bind(this),
style: {
margin: 12,
},
}, 'Import'),
error ? h('span.warning', error) : null,
])
)
}
PrivateKeyImportView.prototype.createKeyringOnEnter = function (event) {
if (event.key === 'Enter') {
event.preventDefault()
this.createNewKeychain()
}
}
PrivateKeyImportView.prototype.createNewKeychain = function () {
const input = document.getElementById('private-key-box')
const privateKey = input.value
this.props.dispatch(actions.addNewKeyring(type, [ privateKey ]))
}

@ -0,0 +1,30 @@
const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
module.exports = connect(mapStateToProps)(SeedImportSubview)
function mapStateToProps (state) {
return {}
}
inherits(SeedImportSubview, Component)
function SeedImportSubview () {
Component.call(this)
}
SeedImportSubview.prototype.render = function () {
return (
h('div', {
style: {
},
}, [
`Paste your seed phrase here!`,
h('textarea'),
h('br'),
h('button', 'Submit'),
])
)
}

@ -73,7 +73,8 @@ AccountsScreen.prototype.render = function () {
const simpleAddress = identity.address.substring(2).toLowerCase() const simpleAddress = identity.address.substring(2).toLowerCase()
const keyring = keyrings.find((kr) => { const keyring = keyrings.find((kr) => {
return kr.accounts.includes(simpleAddress) return kr.accounts.includes(simpleAddress) ||
kr.accounts.includes(identity.address)
}) })
return h(AccountListItem, { return h(AccountListItem, {
@ -154,6 +155,13 @@ AccountsScreen.prototype.addNewAccount = function () {
this.props.dispatch(actions.addNewAccount(0)) this.props.dispatch(actions.addNewAccount(0))
} }
/* An optional view proposed in this design:
* https://consensys.quip.com/zZVrAysM5znY
AccountsScreen.prototype.addNewAccount = function () {
this.props.dispatch(actions.navigateToNewAccountScreen())
}
*/
AccountsScreen.prototype.goHome = function () { AccountsScreen.prototype.goHome = function () {
this.props.dispatch(actions.goHome()) this.props.dispatch(actions.goHome())
} }

@ -32,16 +32,20 @@ var actions = {
SHOW_INIT_MENU: 'SHOW_INIT_MENU', SHOW_INIT_MENU: 'SHOW_INIT_MENU',
SHOW_NEW_VAULT_SEED: 'SHOW_NEW_VAULT_SEED', SHOW_NEW_VAULT_SEED: 'SHOW_NEW_VAULT_SEED',
SHOW_INFO_PAGE: 'SHOW_INFO_PAGE', SHOW_INFO_PAGE: 'SHOW_INFO_PAGE',
SHOW_IMPORT_PAGE: 'SHOW_IMPORT_PAGE',
unlockMetamask: unlockMetamask, unlockMetamask: unlockMetamask,
unlockFailed: unlockFailed, unlockFailed: unlockFailed,
showCreateVault: showCreateVault, showCreateVault: showCreateVault,
showRestoreVault: showRestoreVault, showRestoreVault: showRestoreVault,
showInitializeMenu: showInitializeMenu, showInitializeMenu: showInitializeMenu,
showImportPage,
createNewVaultAndKeychain: createNewVaultAndKeychain, createNewVaultAndKeychain: createNewVaultAndKeychain,
createNewVaultAndRestore: createNewVaultAndRestore, createNewVaultAndRestore: createNewVaultAndRestore,
createNewVaultInProgress: createNewVaultInProgress, createNewVaultInProgress: createNewVaultInProgress,
addNewKeyring, addNewKeyring,
addNewAccount, addNewAccount,
NEW_ACCOUNT_SCREEN: 'NEW_ACCOUNT_SCREEN',
navigateToNewAccountScreen,
showNewVaultSeed: showNewVaultSeed, showNewVaultSeed: showNewVaultSeed,
showInfoPage: showInfoPage, showInfoPage: showInfoPage,
// seed recovery actions // seed recovery actions
@ -249,7 +253,21 @@ function requestRevealSeed (password) {
} }
function addNewKeyring (type, opts) { function addNewKeyring (type, opts) {
return callBackgroundThenUpdate(background.addNewKeyring, type, opts) return (dispatch) => {
dispatch(actions.showLoadingIndication())
background.addNewKeyring(type, opts, (err, newState) => {
dispatch(actions.hideLoadingIndication())
if (err) return dispatch(actions.displayWarning(err.message))
dispatch(actions.updateMetamaskState(newState))
dispatch(actions.showAccountsPage())
})
}
}
function navigateToNewAccountScreen() {
return {
type: this.NEW_ACCOUNT_SCREEN,
}
} }
function addNewAccount (ringNumber = 0) { function addNewAccount (ringNumber = 0) {
@ -376,6 +394,12 @@ function showInitializeMenu () {
} }
} }
function showImportPage () {
return {
type: actions.SHOW_IMPORT_PAGE,
}
}
function agreeToDisclaimer () { function agreeToDisclaimer () {
return (dispatch) => { return (dispatch) => {
dispatch(this.showLoadingIndication()) dispatch(this.showLoadingIndication())

@ -20,6 +20,7 @@ const NoticeScreen = require('./components/notice')
const generateLostAccountsNotice = require('../lib/lost-accounts-notice') const generateLostAccountsNotice = require('../lib/lost-accounts-notice')
// other views // other views
const ConfigScreen = require('./config') const ConfigScreen = require('./config')
const Import = require('./accounts/import')
const InfoScreen = require('./info') const InfoScreen = require('./info')
const LoadingIndicator = require('./components/loading') const LoadingIndicator = require('./components/loading')
const SandwichExpando = require('sandwich-expando') const SandwichExpando = require('sandwich-expando')
@ -304,6 +305,13 @@ App.prototype.renderDropdown = function () {
icon: h('i.fa.fa-gear.fa-lg'), icon: h('i.fa.fa-gear.fa-lg'),
}), }),
h(DropMenuItem, {
label: 'Import Account',
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
action: () => this.props.dispatch(actions.showImportPage()),
icon: h('i.fa.fa-arrow-circle-o-up.fa-lg'),
}),
h(DropMenuItem, { h(DropMenuItem, {
label: 'Lock', label: 'Lock',
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
@ -411,6 +419,9 @@ App.prototype.renderPrimary = function () {
case 'config': case 'config':
return h(ConfigScreen, {key: 'config'}) return h(ConfigScreen, {key: 'config'})
case 'import-menu':
return h(Import, {key: 'import-menu'})
case 'reveal-seed-conf': case 'reveal-seed-conf':
return h(RevealSeedConfirmation, {key: 'reveal-seed-conf'}) return h(RevealSeedConfirmation, {key: 'reveal-seed-conf'})

@ -7,6 +7,7 @@ const CoinbaseForm = require('./coinbase-form')
const ShapeshiftForm = require('./shapeshift-form') const ShapeshiftForm = require('./shapeshift-form')
const extension = require('../../../app/scripts/lib/extension') const extension = require('../../../app/scripts/lib/extension')
const Loading = require('./loading') const Loading = require('./loading')
const TabBar = require('./tab-bar')
module.exports = connect(mapStateToProps)(BuyButtonSubview) module.exports = connect(mapStateToProps)(BuyButtonSubview)
@ -29,7 +30,6 @@ function BuyButtonSubview () {
BuyButtonSubview.prototype.render = function () { BuyButtonSubview.prototype.render = function () {
const props = this.props const props = this.props
const currentForm = props.buyView.formView
const isLoading = props.isSubLoading const isLoading = props.isSubLoading
return ( return (
@ -53,43 +53,53 @@ BuyButtonSubview.prototype.render = function () {
h(Loading, { isLoading }), h(Loading, { isLoading }),
h('h3.flex-row.text-transform-uppercase', { h(TabBar, {
style: { tabs: [
background: '#EBEBEB', {
color: '#AEAEAE', content: [
paddingTop: '4px', 'Coinbase',
justifyContent: 'space-around', h('a', {
onClick: (event) => this.navigateTo('https://github.com/MetaMask/faq/blob/master/COINBASE.md'),
}, [
h('i.fa.fa-question-circle', {
style: {
margin: '0px 5px',
},
}),
]),
],
key: 'coinbase',
},
{
content: [
'Shapeshift',
h('a', {
href: 'https://github.com/MetaMask/faq/blob/master/COINBASE.md',
onClick: (event) => this.navigateTo('https://info.shapeshift.io/about'),
}, [
h('i.fa.fa-question-circle', {
style: {
margin: '0px 5px',
},
}),
]),
],
key: 'shapeshift',
},
],
defaultTab: 'coinbase',
tabSelected: (key) => {
switch (key) {
case 'coinbase':
props.dispatch(actions.coinBaseSubview())
break
case 'shapeshift':
props.dispatch(actions.shapeShiftSubview(props.provider.type))
break
}
}, },
}, [ }),
h(currentForm.coinbase ? '.activeForm' : '.inactiveForm.pointer', {
onClick: () => props.dispatch(actions.coinBaseSubview()),
}, 'Coinbase'),
h('a', {
onClick: (event) => this.navigateTo('https://github.com/MetaMask/faq/blob/master/COINBASE.md'),
}, [
h('i.fa.fa-question-circle', {
style: {
position: 'relative',
right: '33px',
},
}),
]),
h(currentForm.shapeshift ? '.activeForm' : '.inactiveForm.pointer', {
onClick: () => props.dispatch(actions.shapeShiftSubview(props.provider.type)),
}, 'Shapeshift'),
h('a', {
href: 'https://github.com/MetaMask/faq/blob/master/COINBASE.md',
onClick: (event) => this.navigateTo('https://info.shapeshift.io/about'),
}, [
h('i.fa.fa-question-circle', {
style: {
position: 'relative',
right: '28px',
},
}),
]),
]),
this.formVersionSubview(), this.formVersionSubview(),
]) ])
) )

@ -0,0 +1,35 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
module.exports = TabBar
inherits(TabBar, Component)
function TabBar () {
Component.call(this)
}
TabBar.prototype.render = function () {
const props = this.props
const state = this.state || {}
const { tabs = [], defaultTab, tabSelected } = props
const { subview = defaultTab } = state
return (
h('.flex-row.space-around.text-transform-uppercase', {
style: {
background: '#EBEBEB',
color: '#AEAEAE',
paddingTop: '4px',
},
}, tabs.map((tab) => {
const { key, content } = tab
return h(subview === key ? '.activeForm' : '.inactiveForm.pointer', {
onClick: () => {
this.setState({ subview: key })
tabSelected(key)
},
}, content)
}))
)
}

@ -41,12 +41,13 @@ ConfirmTxScreen.prototype.render = function () {
var provider = state.provider var provider = state.provider
var unconfTxs = state.unconfTxs var unconfTxs = state.unconfTxs
var unconfMsgs = state.unconfMsgs var unconfMsgs = state.unconfMsgs
var unconfTxList = txHelper(unconfTxs, unconfMsgs, network) var unconfTxList = txHelper(unconfTxs, unconfMsgs, network)
var index = state.index !== undefined ? state.index : 0 var index = state.index !== undefined && unconfTxList[index] ? state.index : 0
var txData = unconfTxList[index] || {} var txData = unconfTxList[index] || {}
var txParams = txData.txParams var txParams = txData.params || {}
var isNotification = isPopupOrNotification() === 'notification' var isNotification = isPopupOrNotification() === 'notification'
if (!txParams) return null if (unconfTxList.length === 0) return null
return ( return (
@ -116,15 +117,19 @@ ConfirmTxScreen.prototype.render = function () {
} }
function currentTxView (opts) { function currentTxView (opts) {
if ('txParams' in opts.txData) { const { txData } = opts
const { txParams, msgParams } = txData
if (txParams) {
// This is a pending transaction // This is a pending transaction
return h(PendingTx, opts) return h(PendingTx, opts)
} else if ('msgParams' in opts.txData) { } else if (msgParams) {
// This is a pending message to sign // This is a pending message to sign
return h(PendingMsg, opts) return h(PendingMsg, opts)
} }
} }
ConfirmTxScreen.prototype.checkBalanceAgainstTx = function (txData) { ConfirmTxScreen.prototype.checkBalanceAgainstTx = function (txData) {
if (!txData.txParams) return false
var state = this.props var state = this.props
var address = txData.txParams.from || state.selectedAccount var address = txData.txParams.from || state.selectedAccount
var account = state.accounts[address] var account = state.accounts[address]

@ -23,6 +23,14 @@
flex-direction: column; flex-direction: column;
} }
.space-between {
justify-content: space-between;
}
.space-around {
justify-content: space-around;
}
.flex-column-bottom { .flex-column-bottom {
display: flex; display: flex;
flex-direction: column-reverse; flex-direction: column-reverse;

@ -110,7 +110,7 @@ InfoScreen.prototype.render = function () {
onClick (event) { this.navigateTo(event.target.href) }, onClick (event) { this.navigateTo(event.target.href) },
}, [ }, [
h('img.icon-size', { h('img.icon-size', {
src: manifest.icons[128], src: manifest.icons['128'],
style: { style: {
filter: 'grayscale(100%)', /* IE6-9 */ filter: 'grayscale(100%)', /* IE6-9 */
WebkitFilter: 'grayscale(100%)', /* Microsoft Edge and Firefox 35+ */ WebkitFilter: 'grayscale(100%)', /* Microsoft Edge and Firefox 35+ */

@ -99,6 +99,14 @@ function reduceApp (state, action) {
transForward: action.value, transForward: action.value,
}) })
case actions.SHOW_IMPORT_PAGE:
return extend(appState, {
currentView: {
name: 'import-menu',
},
transForward: true,
})
case actions.SHOW_INFO_PAGE: case actions.SHOW_INFO_PAGE:
return extend(appState, { return extend(appState, {
currentView: { currentView: {
@ -128,6 +136,15 @@ function reduceApp (state, action) {
isLoading: false, isLoading: false,
}) })
case actions.NEW_ACCOUNT_SCREEN:
return extend(appState, {
currentView: {
name: 'new-account',
context: appState.currentView.context,
},
transForward: true,
})
case actions.SHOW_SEND_PAGE: case actions.SHOW_SEND_PAGE:
return extend(appState, { return extend(appState, {
currentView: { currentView: {

@ -26,7 +26,7 @@ UnlockScreen.prototype.render = function () {
const state = this.props const state = this.props
const warning = state.warning const warning = state.warning
return ( return (
h('.flex-column.hey-im-here', [ h('.flex-column', [
h('.unlock-screen.flex-column.flex-center.flex-grow', [ h('.unlock-screen.flex-column.flex-center.flex-grow', [
h(Mascot, { h(Mascot, {

@ -10,6 +10,7 @@ var cssFiles = {
'index.css': fs.readFileSync(path.join(__dirname, '/app/css/index.css'), 'utf8'), 'index.css': fs.readFileSync(path.join(__dirname, '/app/css/index.css'), 'utf8'),
'transitions.css': fs.readFileSync(path.join(__dirname, '/app/css/transitions.css'), 'utf8'), 'transitions.css': fs.readFileSync(path.join(__dirname, '/app/css/transitions.css'), 'utf8'),
'react-tooltip-component.css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-tooltip-component', 'dist', 'react-tooltip-component.css'), 'utf8'), 'react-tooltip-component.css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-tooltip-component', 'dist', 'react-tooltip-component.css'), 'utf8'),
'react-css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-select', 'dist', 'react-select.css'), 'utf8'),
} }
function bundleCss () { function bundleCss () {

Loading…
Cancel
Save