Merge pull request #2799 from MetaMask/NewUI-flat
Update UAT to version 4.0.5feature/default_network_editable
commit
409d1d30e9
After Width: | Height: | Size: 1.0 KiB |
@ -0,0 +1,44 @@ |
||||
const ObservableStore = require('obs-store') |
||||
const extend = require('xtend') |
||||
|
||||
class RecentBlocksController { |
||||
|
||||
constructor (opts = {}) { |
||||
const { blockTracker } = opts |
||||
this.blockTracker = blockTracker |
||||
this.historyLength = opts.historyLength || 40 |
||||
|
||||
const initState = extend({ |
||||
recentBlocks: [], |
||||
}, opts.initState) |
||||
this.store = new ObservableStore(initState) |
||||
|
||||
this.blockTracker.on('block', this.processBlock.bind(this)) |
||||
} |
||||
|
||||
resetState () { |
||||
this.store.updateState({ |
||||
recentBlocks: [], |
||||
}) |
||||
} |
||||
|
||||
processBlock (newBlock) { |
||||
const block = extend(newBlock, { |
||||
gasPrices: newBlock.transactions.map((tx) => { |
||||
return tx.gasPrice |
||||
}), |
||||
}) |
||||
delete block.transactions |
||||
|
||||
const state = this.store.getState() |
||||
state.recentBlocks.push(block) |
||||
|
||||
while (state.recentBlocks.length > this.historyLength) { |
||||
state.recentBlocks.shift() |
||||
} |
||||
|
||||
this.store.updateState(state) |
||||
} |
||||
} |
||||
|
||||
module.exports = RecentBlocksController |
@ -0,0 +1,41 @@ |
||||
const version = 20 |
||||
|
||||
/* |
||||
|
||||
This migration ensures previous installations |
||||
get a `firstTimeInfo` key on the metamask state, |
||||
so that we can version notices in the future. |
||||
|
||||
*/ |
||||
|
||||
const clone = require('clone') |
||||
|
||||
module.exports = { |
||||
version, |
||||
|
||||
migrate: function (originalVersionedData) { |
||||
const versionedData = clone(originalVersionedData) |
||||
versionedData.meta.version = version |
||||
try { |
||||
const state = versionedData.data |
||||
const newState = transformState(state) |
||||
versionedData.data = newState |
||||
} catch (err) { |
||||
console.warn(`MetaMask Migration #${version}` + err.stack) |
||||
} |
||||
return Promise.resolve(versionedData) |
||||
}, |
||||
} |
||||
|
||||
function transformState (state) { |
||||
const newState = state |
||||
if ('metamask' in newState && |
||||
!('firstTimeInfo' in newState.metamask)) { |
||||
newState.metamask.firstTimeInfo = { |
||||
version: '3.12.0', |
||||
date: Date.now(), |
||||
} |
||||
} |
||||
return newState |
||||
} |
||||
|
@ -0,0 +1,739 @@ |
||||
{ |
||||
"metamask": { |
||||
"isInitialized": true, |
||||
"isUnlocked": true, |
||||
"isMascara": false, |
||||
"rpcTarget": "https://rawtestrpc.metamask.io/", |
||||
"identities": { |
||||
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": { |
||||
"address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", |
||||
"name": "Account 1" |
||||
} |
||||
}, |
||||
"unapprovedTxs": {}, |
||||
"noActiveNotices": true, |
||||
"frequentRpcList": [ |
||||
"http://192.168.1.34:7545/" |
||||
], |
||||
"addressBook": [], |
||||
"tokenExchangeRates": {}, |
||||
"coinOptions": {}, |
||||
"provider": { |
||||
"type": "mainnet", |
||||
"rpcTarget": "https://mainnet.infura.io/metamask" |
||||
}, |
||||
"network": "1", |
||||
"accounts": { |
||||
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": { |
||||
"code": "0x", |
||||
"balance": "0x1b3f641ed0c2f62", |
||||
"nonce": "0x35", |
||||
"address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825" |
||||
} |
||||
}, |
||||
"currentBlockGasLimit": "0x66df83", |
||||
"selectedAddressTxList": [ |
||||
{ |
||||
"id": 3516145537630216, |
||||
"time": 1512615655535, |
||||
"status": "submitted", |
||||
"metamaskNetworkId": "1", |
||||
"txParams": { |
||||
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", |
||||
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", |
||||
"value": "0x16345785d8a0000", |
||||
"gasPrice": "0xc1b710800", |
||||
"gas": "0x7b0c", |
||||
"nonce": "0x35", |
||||
"chainId": "0x1" |
||||
}, |
||||
"gasPriceSpecified": false, |
||||
"gasLimitSpecified": false, |
||||
"estimatedGas": "5208", |
||||
"history": [ |
||||
{ |
||||
"id": 3516145537630216, |
||||
"time": 1512615655535, |
||||
"status": "unapproved", |
||||
"metamaskNetworkId": "1", |
||||
"txParams": { |
||||
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", |
||||
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", |
||||
"value": "0x16345785d8a0000", |
||||
"gasPrice": "0xe6f7cec00", |
||||
"gas": "0x7b0c" |
||||
}, |
||||
"gasPriceSpecified": false, |
||||
"gasLimitSpecified": false, |
||||
"estimatedGas": "5208" |
||||
}, |
||||
[ |
||||
{ |
||||
"op": "replace", |
||||
"path": "/txParams/gasPrice", |
||||
"value": "0xc1b710800", |
||||
"note": "confTx: user approved transaction" |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "replace", |
||||
"path": "/status", |
||||
"value": "approved", |
||||
"note": "txStateManager: setting status to approved" |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "add", |
||||
"path": "/txParams/nonce", |
||||
"value": "0x35", |
||||
"note": "transactions#approveTransaction" |
||||
}, |
||||
{ |
||||
"op": "add", |
||||
"path": "/nonceDetails", |
||||
"value": { |
||||
"params": { |
||||
"highestLocalNonce": 53, |
||||
"highestSuggested": 53, |
||||
"nextNetworkNonce": 53 |
||||
}, |
||||
"local": { |
||||
"name": "local", |
||||
"nonce": 53, |
||||
"details": { |
||||
"startPoint": 53, |
||||
"highest": 53 |
||||
} |
||||
}, |
||||
"network": { |
||||
"name": "network", |
||||
"nonce": 53, |
||||
"details": { |
||||
"baseCount": 53 |
||||
} |
||||
} |
||||
} |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "add", |
||||
"path": "/txParams/chainId", |
||||
"value": "0x1", |
||||
"note": "txStateManager: setting status to signed" |
||||
}, |
||||
{ |
||||
"op": "replace", |
||||
"path": "/status", |
||||
"value": "signed" |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "add", |
||||
"path": "/rawTx", |
||||
"value": "0xf86c35850c1b710800827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a0f5142ba79a13ca7ec65548953017edafb217803244bbf9821d9ad077d89921e9a03afcb614169c90be9905d5b469d06984825c76675d3a535937cdb8f2ad1c0a95", |
||||
"note": "transactions#publishTransaction" |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "add", |
||||
"path": "/hash", |
||||
"value": "0x7ce19c0d128ca11293b44a4e6d3cc9063665c00ea8c8eb400f548e132c147353", |
||||
"note": "transactions#setTxHash" |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "replace", |
||||
"path": "/status", |
||||
"value": "submitted", |
||||
"note": "txStateManager: setting status to submitted" |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "add", |
||||
"path": "/firstRetryBlockNumber", |
||||
"value": "0x478ab3", |
||||
"note": "transactions/pending-tx-tracker#event: tx:block-update" |
||||
} |
||||
] |
||||
], |
||||
"nonceDetails": { |
||||
"params": { |
||||
"highestLocalNonce": 53, |
||||
"highestSuggested": 53, |
||||
"nextNetworkNonce": 53 |
||||
}, |
||||
"local": { |
||||
"name": "local", |
||||
"nonce": 53, |
||||
"details": { |
||||
"startPoint": 53, |
||||
"highest": 53 |
||||
} |
||||
}, |
||||
"network": { |
||||
"name": "network", |
||||
"nonce": 53, |
||||
"details": { |
||||
"baseCount": 53 |
||||
} |
||||
} |
||||
}, |
||||
"rawTx": "0xf86c35850c1b710800827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a0f5142ba79a13ca7ec65548953017edafb217803244bbf9821d9ad077d89921e9a03afcb614169c90be9905d5b469d06984825c76675d3a535937cdb8f2ad1c0a95", |
||||
"hash": "0x7ce19c0d128ca11293b44a4e6d3cc9063665c00ea8c8eb400f548e132c147353", |
||||
"firstRetryBlockNumber": "0x478ab3" |
||||
}, |
||||
{ |
||||
"id": 3516145537630211, |
||||
"time": 1512613432658, |
||||
"status": "confirmed", |
||||
"metamaskNetworkId": "1", |
||||
"txParams": { |
||||
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", |
||||
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", |
||||
"value": "0x16345785d8a0000", |
||||
"gasPrice": "0xba43b7400", |
||||
"gas": "0x7b0c", |
||||
"nonce": "0x34", |
||||
"chainId": "0x1" |
||||
}, |
||||
"gasPriceSpecified": false, |
||||
"gasLimitSpecified": false, |
||||
"estimatedGas": "5208", |
||||
"history": [ |
||||
{ |
||||
"id": 3516145537630211, |
||||
"time": 1512613432658, |
||||
"status": "unapproved", |
||||
"metamaskNetworkId": "1", |
||||
"txParams": { |
||||
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", |
||||
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", |
||||
"value": "0x16345785d8a0000", |
||||
"gasPrice": "0xdf8475800", |
||||
"gas": "0x7b0c" |
||||
}, |
||||
"gasPriceSpecified": false, |
||||
"gasLimitSpecified": false, |
||||
"estimatedGas": "5208" |
||||
}, |
||||
[ |
||||
{ |
||||
"op": "replace", |
||||
"path": "/txParams/gasPrice", |
||||
"value": "0xba43b7400", |
||||
"note": "confTx: user approved transaction" |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "replace", |
||||
"path": "/status", |
||||
"value": "approved", |
||||
"note": "txStateManager: setting status to approved" |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "add", |
||||
"path": "/txParams/nonce", |
||||
"value": "0x34", |
||||
"note": "transactions#approveTransaction" |
||||
}, |
||||
{ |
||||
"op": "add", |
||||
"path": "/nonceDetails", |
||||
"value": { |
||||
"params": { |
||||
"highestLocalNonce": 52, |
||||
"highestSuggested": 52, |
||||
"nextNetworkNonce": 52 |
||||
}, |
||||
"local": { |
||||
"name": "local", |
||||
"nonce": 52, |
||||
"details": { |
||||
"startPoint": 52, |
||||
"highest": 52 |
||||
} |
||||
}, |
||||
"network": { |
||||
"name": "network", |
||||
"nonce": 52, |
||||
"details": { |
||||
"baseCount": 52 |
||||
} |
||||
} |
||||
} |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "add", |
||||
"path": "/txParams/chainId", |
||||
"value": "0x1", |
||||
"note": "txStateManager: setting status to signed" |
||||
}, |
||||
{ |
||||
"op": "replace", |
||||
"path": "/status", |
||||
"value": "signed" |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "add", |
||||
"path": "/rawTx", |
||||
"value": "0xf86c34850ba43b7400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a073a4afdb8e8ad32b0cf9039af56c66baffd60d30e75cee5c1b783208824eafb8a0021ca6c1714a2c71281333ab77f776d3514348ab77967280fca8a5b4be44285e", |
||||
"note": "transactions#publishTransaction" |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "add", |
||||
"path": "/hash", |
||||
"value": "0x5c98409883fdfd3cd24058a83b91470da6c40ffae41a40eb90d7dee0b837d26d", |
||||
"note": "transactions#setTxHash" |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "replace", |
||||
"path": "/status", |
||||
"value": "submitted", |
||||
"note": "txStateManager: setting status to submitted" |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "add", |
||||
"path": "/firstRetryBlockNumber", |
||||
"value": "0x478a2c", |
||||
"note": "transactions/pending-tx-tracker#event: tx:block-update" |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "replace", |
||||
"path": "/status", |
||||
"value": "confirmed", |
||||
"note": "txStateManager: setting status to confirmed" |
||||
} |
||||
] |
||||
], |
||||
"nonceDetails": { |
||||
"params": { |
||||
"highestLocalNonce": 52, |
||||
"highestSuggested": 52, |
||||
"nextNetworkNonce": 52 |
||||
}, |
||||
"local": { |
||||
"name": "local", |
||||
"nonce": 52, |
||||
"details": { |
||||
"startPoint": 52, |
||||
"highest": 52 |
||||
} |
||||
}, |
||||
"network": { |
||||
"name": "network", |
||||
"nonce": 52, |
||||
"details": { |
||||
"baseCount": 52 |
||||
} |
||||
} |
||||
}, |
||||
"rawTx": "0xf86c34850ba43b7400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a073a4afdb8e8ad32b0cf9039af56c66baffd60d30e75cee5c1b783208824eafb8a0021ca6c1714a2c71281333ab77f776d3514348ab77967280fca8a5b4be44285e", |
||||
"hash": "0x5c98409883fdfd3cd24058a83b91470da6c40ffae41a40eb90d7dee0b837d26d", |
||||
"firstRetryBlockNumber": "0x478a2c" |
||||
}, |
||||
{ |
||||
"id": 3516145537630210, |
||||
"time": 1512612826136, |
||||
"status": "confirmed", |
||||
"metamaskNetworkId": "1", |
||||
"txParams": { |
||||
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", |
||||
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", |
||||
"value": "0x16345785d8a0000", |
||||
"gasPrice": "0xa7a358200", |
||||
"gas": "0x7b0c", |
||||
"nonce": "0x33", |
||||
"chainId": "0x1" |
||||
}, |
||||
"gasPriceSpecified": false, |
||||
"gasLimitSpecified": false, |
||||
"estimatedGas": "5208", |
||||
"history": [ |
||||
{ |
||||
"id": 3516145537630210, |
||||
"time": 1512612826136, |
||||
"status": "unapproved", |
||||
"metamaskNetworkId": "1", |
||||
"txParams": { |
||||
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", |
||||
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", |
||||
"value": "0x16345785d8a0000", |
||||
"gasPrice": "0xba43b7400", |
||||
"gas": "0x7b0c" |
||||
}, |
||||
"gasPriceSpecified": false, |
||||
"gasLimitSpecified": false, |
||||
"estimatedGas": "5208" |
||||
}, |
||||
[ |
||||
{ |
||||
"op": "replace", |
||||
"path": "/txParams/gasPrice", |
||||
"value": "0xa7a358200", |
||||
"note": "confTx: user approved transaction" |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "replace", |
||||
"path": "/status", |
||||
"value": "approved", |
||||
"note": "txStateManager: setting status to approved" |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "add", |
||||
"path": "/txParams/nonce", |
||||
"value": "0x33", |
||||
"note": "transactions#approveTransaction" |
||||
}, |
||||
{ |
||||
"op": "add", |
||||
"path": "/nonceDetails", |
||||
"value": { |
||||
"params": { |
||||
"highestLocalNonce": 0, |
||||
"highestSuggested": 51, |
||||
"nextNetworkNonce": 51 |
||||
}, |
||||
"local": { |
||||
"name": "local", |
||||
"nonce": 51, |
||||
"details": { |
||||
"startPoint": 51, |
||||
"highest": 51 |
||||
} |
||||
}, |
||||
"network": { |
||||
"name": "network", |
||||
"nonce": 51, |
||||
"details": { |
||||
"baseCount": 51 |
||||
} |
||||
} |
||||
} |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "add", |
||||
"path": "/txParams/chainId", |
||||
"value": "0x1", |
||||
"note": "txStateManager: setting status to signed" |
||||
}, |
||||
{ |
||||
"op": "replace", |
||||
"path": "/status", |
||||
"value": "signed" |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "add", |
||||
"path": "/rawTx", |
||||
"value": "0xf86c33850a7a358200827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a0021a8cd6c10208cc593e22af53637e5d127cee5cc6f9443a3e758a02afff1d7ca025f7420e974d3f2c668c165040987c72543a8e709bfea3528a62836a6ced9ce8", |
||||
"note": "transactions#publishTransaction" |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "add", |
||||
"path": "/hash", |
||||
"value": "0x289772800898bc9cd414530d8581c0da257a9055e4aaaa6d10d92d700bfbd044", |
||||
"note": "transactions#setTxHash" |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "replace", |
||||
"path": "/status", |
||||
"value": "submitted", |
||||
"note": "txStateManager: setting status to submitted" |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "add", |
||||
"path": "/firstRetryBlockNumber", |
||||
"value": "0x478a04", |
||||
"note": "transactions/pending-tx-tracker#event: tx:block-update" |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "replace", |
||||
"path": "/status", |
||||
"value": "confirmed", |
||||
"note": "txStateManager: setting status to confirmed" |
||||
} |
||||
] |
||||
], |
||||
"nonceDetails": { |
||||
"params": { |
||||
"highestLocalNonce": 0, |
||||
"highestSuggested": 51, |
||||
"nextNetworkNonce": 51 |
||||
}, |
||||
"local": { |
||||
"name": "local", |
||||
"nonce": 51, |
||||
"details": { |
||||
"startPoint": 51, |
||||
"highest": 51 |
||||
} |
||||
}, |
||||
"network": { |
||||
"name": "network", |
||||
"nonce": 51, |
||||
"details": { |
||||
"baseCount": 51 |
||||
} |
||||
} |
||||
}, |
||||
"rawTx": "0xf86c33850a7a358200827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a0021a8cd6c10208cc593e22af53637e5d127cee5cc6f9443a3e758a02afff1d7ca025f7420e974d3f2c668c165040987c72543a8e709bfea3528a62836a6ced9ce8", |
||||
"hash": "0x289772800898bc9cd414530d8581c0da257a9055e4aaaa6d10d92d700bfbd044", |
||||
"firstRetryBlockNumber": "0x478a04" |
||||
}, |
||||
{ |
||||
"id": 3516145537630209, |
||||
"time": 1512612809252, |
||||
"status": "failed", |
||||
"metamaskNetworkId": "1", |
||||
"txParams": { |
||||
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", |
||||
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", |
||||
"value": "0x16345785d8a0000", |
||||
"gasPrice": "0x77359400", |
||||
"gas": "0x7b0c", |
||||
"nonce": "0x33", |
||||
"chainId": "0x1" |
||||
}, |
||||
"gasPriceSpecified": false, |
||||
"gasLimitSpecified": false, |
||||
"estimatedGas": "5208", |
||||
"history": [ |
||||
{ |
||||
"id": 3516145537630209, |
||||
"time": 1512612809252, |
||||
"status": "unapproved", |
||||
"metamaskNetworkId": "1", |
||||
"txParams": { |
||||
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", |
||||
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", |
||||
"value": "0x16345785d8a0000", |
||||
"gasPrice": "0xba43b7400", |
||||
"gas": "0x7b0c" |
||||
}, |
||||
"gasPriceSpecified": false, |
||||
"gasLimitSpecified": false, |
||||
"estimatedGas": "5208" |
||||
}, |
||||
[ |
||||
{ |
||||
"op": "replace", |
||||
"path": "/txParams/gasPrice", |
||||
"value": "0x77359400", |
||||
"note": "confTx: user approved transaction" |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "replace", |
||||
"path": "/status", |
||||
"value": "approved", |
||||
"note": "txStateManager: setting status to approved" |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "add", |
||||
"path": "/txParams/nonce", |
||||
"value": "0x33", |
||||
"note": "transactions#approveTransaction" |
||||
}, |
||||
{ |
||||
"op": "add", |
||||
"path": "/nonceDetails", |
||||
"value": { |
||||
"params": { |
||||
"highestLocalNonce": 0, |
||||
"highestSuggested": 51, |
||||
"nextNetworkNonce": 51 |
||||
}, |
||||
"local": { |
||||
"name": "local", |
||||
"nonce": 51, |
||||
"details": { |
||||
"startPoint": 51, |
||||
"highest": 51 |
||||
} |
||||
}, |
||||
"network": { |
||||
"name": "network", |
||||
"nonce": 51, |
||||
"details": { |
||||
"baseCount": 51 |
||||
} |
||||
} |
||||
} |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "add", |
||||
"path": "/txParams/chainId", |
||||
"value": "0x1", |
||||
"note": "txStateManager: setting status to signed" |
||||
}, |
||||
{ |
||||
"op": "replace", |
||||
"path": "/status", |
||||
"value": "signed" |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "add", |
||||
"path": "/rawTx", |
||||
"value": "0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7", |
||||
"note": "transactions#publishTransaction" |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "add", |
||||
"path": "/err", |
||||
"value": { |
||||
"message": "Error: [ethjs-rpc] rpc error with payload {\"id\":7801900228852,\"jsonrpc\":\"2.0\",\"params\":[\"0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7\"],\"method\":\"eth_sendRawTransaction\"} Error: transaction underpriced", |
||||
"stack": "Error: [ethjs-rpc] rpc error with payload {\"id\":7801900228852,\"jsonrpc\":\"2.0\",\"params\":[\"0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7\"],\"method\":\"eth_sendRawTransaction\"} Error: transaction underpriced\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:60327:26\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:88030:9\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16678:16\n at replenish (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16522:25)\n at iterateeCallback (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16512:17)\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16694:16\n at resultObj.id (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:88012:9)\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16813:16\n at replenish (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16527:17)\n at iterateeCallback (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16512:17)" |
||||
} |
||||
} |
||||
], |
||||
[ |
||||
{ |
||||
"op": "replace", |
||||
"path": "/status", |
||||
"value": "failed", |
||||
"note": "txStateManager: setting status to failed" |
||||
} |
||||
] |
||||
], |
||||
"nonceDetails": { |
||||
"params": { |
||||
"highestLocalNonce": 0, |
||||
"highestSuggested": 51, |
||||
"nextNetworkNonce": 51 |
||||
}, |
||||
"local": { |
||||
"name": "local", |
||||
"nonce": 51, |
||||
"details": { |
||||
"startPoint": 51, |
||||
"highest": 51 |
||||
} |
||||
}, |
||||
"network": { |
||||
"name": "network", |
||||
"nonce": 51, |
||||
"details": { |
||||
"baseCount": 51 |
||||
} |
||||
} |
||||
}, |
||||
"rawTx": "0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7", |
||||
"err": { |
||||
"message": "Error: [ethjs-rpc] rpc error with payload {\"id\":7801900228852,\"jsonrpc\":\"2.0\",\"params\":[\"0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7\"],\"method\":\"eth_sendRawTransaction\"} Error: transaction underpriced", |
||||
"stack": "Error: [ethjs-rpc] rpc error with payload {\"id\":7801900228852,\"jsonrpc\":\"2.0\",\"params\":[\"0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7\"],\"method\":\"eth_sendRawTransaction\"} Error: transaction underpriced\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:60327:26\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:88030:9\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16678:16\n at replenish (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16522:25)\n at iterateeCallback (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16512:17)\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16694:16\n at resultObj.id (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:88012:9)\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16813:16\n at replenish (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16527:17)\n at iterateeCallback (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16512:17)" |
||||
} |
||||
} |
||||
], |
||||
"unapprovedMsgs": {}, |
||||
"unapprovedMsgCount": 0, |
||||
"unapprovedPersonalMsgs": {}, |
||||
"unapprovedPersonalMsgCount": 0, |
||||
"unapprovedTypedMessages": {}, |
||||
"unapprovedTypedMessagesCount": 0, |
||||
"keyringTypes": [ |
||||
"Simple Key Pair", |
||||
"HD Key Tree" |
||||
], |
||||
"keyrings": [ |
||||
{ |
||||
"type": "HD Key Tree", |
||||
"accounts": [ |
||||
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825" |
||||
] |
||||
} |
||||
], |
||||
"computedBalances": {}, |
||||
"currentAccountTab": "history", |
||||
"tokens": [ |
||||
{ |
||||
"address": "0x0d8775f648430679a709e98d2b0cb6250d2887ef", |
||||
"symbol": "BAT", |
||||
"decimals": "18" |
||||
} |
||||
], |
||||
"selectedAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825", |
||||
"currentCurrency": "usd", |
||||
"conversionRate": 418.62, |
||||
"conversionDate": 1512615622, |
||||
"infuraNetworkStatus": { |
||||
"mainnet": "ok", |
||||
"ropsten": "ok", |
||||
"kovan": "ok", |
||||
"rinkeby": "ok" |
||||
}, |
||||
"shapeShiftTxList": [], |
||||
"lostAccounts": [] |
||||
}, |
||||
"appState": { |
||||
"shouldClose": true, |
||||
"menuOpen": false, |
||||
"currentView": { |
||||
"name": "accountDetail", |
||||
"context": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825" |
||||
}, |
||||
"accountDetail": { |
||||
"subview": "transactions", |
||||
"accountExport": "none", |
||||
"privateKey": "" |
||||
}, |
||||
"transForward": false, |
||||
"isLoading": false, |
||||
"warning": null, |
||||
"forgottenPassword": false, |
||||
"scrollToBottom": false |
||||
}, |
||||
"identities": {}, |
||||
"version": "3.12.1", |
||||
"platform": { |
||||
"arch": "x86-64", |
||||
"nacl_arch": "x86-64", |
||||
"os": "mac" |
||||
}, |
||||
"browser": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36" |
||||
} |
@ -0,0 +1,11 @@ |
||||
Please take a moment to [back up your seed phrase again](https://support.metamask.io/kb/article/28-abbu-always-be-backed-up-how-to-make-sure-your-12-word-metamask-seed-phrase-is-backed-up). |
||||
|
||||
MetaMask has become aware of a previous issue where a very small number of users were shown the wrong seed phrase to back up. The only way to protect yourself from this issue, is to back up your seed phrase again now. |
||||
|
||||
You can follow the guide at this link: |
||||
|
||||
[https://support.metamask.io/kb/article/28-abbu-always-be-backed-up-how-to-make-sure-your-12-word-metamask-seed-phrase-is-backed-up](https://support.metamask.io/kb/article/28-abbu-always-be-backed-up-how-to-make-sure-your-12-word-metamask-seed-phrase-is-backed-up) |
||||
|
||||
We have fixed the known issue, but will be issuing ongoing bug bounties to help prevent this kind of problem in the future. |
||||
|
||||
For more information on this issue, [see this blog post](https://medium.com/metamask/seed-phrase-issue-bounty-awarded-e1986e811021) |
@ -1 +1 @@ |
||||
3 |
||||
4 |
File diff suppressed because one or more lines are too long
@ -0,0 +1,66 @@ |
||||
|
||||
# Created by https://www.gitignore.io/api/osx,node |
||||
|
||||
### OSX ### |
||||
.DS_Store |
||||
.AppleDouble |
||||
.LSOverride |
||||
|
||||
# Icon must end with two \r |
||||
Icon
|
||||
|
||||
# Thumbnails |
||||
._* |
||||
|
||||
# Files that might appear in the root of a volume |
||||
.DocumentRevisions-V100 |
||||
.fseventsd |
||||
.Spotlight-V100 |
||||
.TemporaryItems |
||||
.Trashes |
||||
.VolumeIcon.icns |
||||
|
||||
# Directories potentially created on remote AFP share |
||||
.AppleDB |
||||
.AppleDesktop |
||||
Network Trash Folder |
||||
Temporary Items |
||||
.apdisk |
||||
|
||||
|
||||
### Node ### |
||||
# Logs |
||||
logs |
||||
*.log |
||||
npm-debug.log* |
||||
|
||||
# Runtime data |
||||
pids |
||||
*.pid |
||||
*.seed |
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover |
||||
lib-cov |
||||
|
||||
# Coverage directory used by tools like istanbul |
||||
coverage |
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) |
||||
.grunt |
||||
|
||||
# node-waf configuration |
||||
.lock-wscript |
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html) |
||||
build/Release |
||||
|
||||
# Dependency directory |
||||
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git |
||||
node_modules |
||||
|
||||
# Optional npm cache directory |
||||
.npm |
||||
|
||||
# Optional REPL history |
||||
.node_repl_history |
||||
|
@ -0,0 +1,289 @@ |
||||
const inherits = require('util').inherits |
||||
const extend = require('xtend') |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const connect = require('react-redux').connect |
||||
const actions = require('../../ui/app/actions') |
||||
const valuesFor = require('./util').valuesFor |
||||
const Identicon = require('./components/identicon') |
||||
const EthBalance = require('./components/eth-balance') |
||||
const TransactionList = require('./components/transaction-list') |
||||
const ExportAccountView = require('./components/account-export') |
||||
const ethUtil = require('ethereumjs-util') |
||||
const EditableLabel = require('./components/editable-label') |
||||
const TabBar = require('./components/tab-bar') |
||||
const TokenList = require('./components/token-list') |
||||
const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns |
||||
|
||||
module.exports = connect(mapStateToProps)(AccountDetailScreen) |
||||
|
||||
function mapStateToProps (state) { |
||||
return { |
||||
metamask: state.metamask, |
||||
identities: state.metamask.identities, |
||||
accounts: state.metamask.accounts, |
||||
address: state.metamask.selectedAddress, |
||||
accountDetail: state.appState.accountDetail, |
||||
network: state.metamask.network, |
||||
unapprovedMsgs: valuesFor(state.metamask.unapprovedMsgs), |
||||
shapeShiftTxList: state.metamask.shapeShiftTxList, |
||||
transactions: state.metamask.selectedAddressTxList || [], |
||||
conversionRate: state.metamask.conversionRate, |
||||
currentCurrency: state.metamask.currentCurrency, |
||||
currentAccountTab: state.metamask.currentAccountTab, |
||||
tokens: state.metamask.tokens, |
||||
computedBalances: state.metamask.computedBalances, |
||||
} |
||||
} |
||||
|
||||
inherits(AccountDetailScreen, Component) |
||||
function AccountDetailScreen () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
AccountDetailScreen.prototype.render = function () { |
||||
var props = this.props |
||||
var selected = props.address || Object.keys(props.accounts)[0] |
||||
var checksumAddress = selected && ethUtil.toChecksumAddress(selected) |
||||
var identity = props.identities[selected] |
||||
var account = props.accounts[selected] |
||||
const { network, conversionRate, currentCurrency } = props |
||||
|
||||
return ( |
||||
|
||||
h('.account-detail-section.full-flex-height', [ |
||||
|
||||
// identicon, label, balance, etc
|
||||
h('.account-data-subsection', { |
||||
style: { |
||||
margin: '0 20px', |
||||
flex: '1 0 auto', |
||||
}, |
||||
}, [ |
||||
|
||||
// header - identicon + nav
|
||||
h('div', { |
||||
style: { |
||||
paddingTop: '20px', |
||||
display: 'flex', |
||||
justifyContent: 'flex-start', |
||||
alignItems: 'flex-start', |
||||
}, |
||||
}, [ |
||||
|
||||
// large identicon and addresses
|
||||
h('.identicon-wrapper.select-none', [ |
||||
h(Identicon, { |
||||
diameter: 62, |
||||
address: selected, |
||||
}), |
||||
]), |
||||
h('div.flex-column', { |
||||
style: { |
||||
lineHeight: '10px', |
||||
width: '100%', |
||||
}, |
||||
}, [ |
||||
h(EditableLabel, { |
||||
textValue: identity ? identity.name : '', |
||||
state: { |
||||
isEditingLabel: false, |
||||
}, |
||||
saveText: (text) => { |
||||
props.dispatch(actions.saveAccountLabel(selected, text)) |
||||
}, |
||||
}, [ |
||||
|
||||
// What is shown when not editing + edit text:
|
||||
h('label.editing-label', [h('.edit-text', 'edit')]), |
||||
h( |
||||
'div', |
||||
{ |
||||
style: { |
||||
display: 'flex', |
||||
justifyContent: 'space-between', |
||||
alignItems: 'center', |
||||
}, |
||||
}, |
||||
[ |
||||
h( |
||||
'div.font-medium.color-forest', |
||||
{ |
||||
name: 'edit', |
||||
style: { |
||||
}, |
||||
}, |
||||
[ |
||||
h('h2', { |
||||
style: { |
||||
maxWidth: '180px', |
||||
overflow: 'hidden', |
||||
textOverflow: 'ellipsis', |
||||
padding: '5px 0px', |
||||
lineHeight: '25px', |
||||
}, |
||||
}, [ |
||||
identity && identity.name, |
||||
]), |
||||
] |
||||
), |
||||
h( |
||||
AccountDropdowns, |
||||
{ |
||||
style: { |
||||
cursor: 'pointer', |
||||
}, |
||||
selected, |
||||
network, |
||||
identities: props.identities, |
||||
enableAccountOptions: true, |
||||
}, |
||||
), |
||||
] |
||||
), |
||||
]), |
||||
h('.flex-row', { |
||||
style: { |
||||
justifyContent: 'space-between', |
||||
alignItems: 'baseline', |
||||
}, |
||||
}, [ |
||||
|
||||
// address
|
||||
|
||||
h('div', { |
||||
style: { |
||||
overflow: 'hidden', |
||||
textOverflow: 'ellipsis', |
||||
paddingTop: '3px', |
||||
width: '5em', |
||||
fontSize: '13px', |
||||
fontFamily: 'Montserrat Light', |
||||
textRendering: 'geometricPrecision', |
||||
marginTop: '15px', |
||||
marginBottom: '15px', |
||||
color: '#AEAEAE', |
||||
}, |
||||
}, checksumAddress), |
||||
]), |
||||
|
||||
// account ballence
|
||||
|
||||
]), |
||||
]), |
||||
h('.flex-row', { |
||||
style: { |
||||
justifyContent: 'space-between', |
||||
alignItems: 'flex-start', |
||||
}, |
||||
}, [ |
||||
|
||||
h(EthBalance, { |
||||
value: account && account.balance, |
||||
conversionRate, |
||||
currentCurrency, |
||||
style: { |
||||
lineHeight: '7px', |
||||
marginTop: '10px', |
||||
}, |
||||
}), |
||||
|
||||
h('div', {}, [ |
||||
|
||||
h('button', { |
||||
onClick: () => props.dispatch(actions.buyEthView(selected)), |
||||
style: { marginRight: '10px' }, |
||||
}, 'BUY'), |
||||
|
||||
h('button', { |
||||
onClick: () => props.dispatch(actions.showSendPage()), |
||||
style: { |
||||
marginBottom: '20px', |
||||
}, |
||||
}, 'SEND'), |
||||
|
||||
]), |
||||
|
||||
]), |
||||
]), |
||||
|
||||
// subview (tx history, pk export confirm, buy eth warning)
|
||||
this.subview(), |
||||
|
||||
]) |
||||
) |
||||
} |
||||
|
||||
AccountDetailScreen.prototype.subview = function () { |
||||
var subview |
||||
try { |
||||
subview = this.props.accountDetail.subview |
||||
} catch (e) { |
||||
subview = null |
||||
} |
||||
|
||||
switch (subview) { |
||||
case 'transactions': |
||||
return this.tabSections() |
||||
case 'export': |
||||
var state = extend({key: 'export'}, this.props) |
||||
return h(ExportAccountView, state) |
||||
default: |
||||
return this.tabSections() |
||||
} |
||||
} |
||||
|
||||
AccountDetailScreen.prototype.tabSections = function () { |
||||
const { currentAccountTab } = this.props |
||||
|
||||
return h('section.tabSection.full-flex-height.grow-tenx', [ |
||||
|
||||
h(TabBar, { |
||||
tabs: [ |
||||
{ content: 'Sent', key: 'history' }, |
||||
{ content: 'Tokens', key: 'tokens' }, |
||||
], |
||||
defaultTab: currentAccountTab || 'history', |
||||
tabSelected: (key) => { |
||||
this.props.dispatch(actions.setCurrentAccountTab(key)) |
||||
}, |
||||
}), |
||||
|
||||
this.tabSwitchView(), |
||||
]) |
||||
} |
||||
|
||||
AccountDetailScreen.prototype.tabSwitchView = function () { |
||||
const props = this.props |
||||
const { address, network } = props |
||||
const { currentAccountTab, tokens } = this.props |
||||
|
||||
switch (currentAccountTab) { |
||||
case 'tokens': |
||||
return h(TokenList, { |
||||
userAddress: address, |
||||
network, |
||||
tokens, |
||||
addToken: () => this.props.dispatch(actions.showAddTokenPage()), |
||||
}) |
||||
default: |
||||
return this.transactionList() |
||||
} |
||||
} |
||||
|
||||
AccountDetailScreen.prototype.transactionList = function () { |
||||
const {transactions, unapprovedMsgs, address, |
||||
network, shapeShiftTxList, conversionRate } = this.props |
||||
|
||||
return h(TransactionList, { |
||||
transactions: transactions.sort((a, b) => b.time - a.time), |
||||
network, |
||||
unapprovedMsgs, |
||||
conversionRate, |
||||
address, |
||||
shapeShiftTxList, |
||||
viewPendingTx: (txId) => { |
||||
this.props.dispatch(actions.viewPendingTx(txId)) |
||||
}, |
||||
}) |
||||
} |
@ -0,0 +1,101 @@ |
||||
const inherits = require('util').inherits |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const connect = require('react-redux').connect |
||||
const actions = require('../../../../ui/app/actions') |
||||
import Select from 'react-select' |
||||
|
||||
// Subviews
|
||||
const JsonImportView = require('./json.js') |
||||
const PrivateKeyImportView = require('./private-key.js') |
||||
|
||||
const menuItems = [ |
||||
'Private Key', |
||||
'JSON File', |
||||
] |
||||
|
||||
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('.section-title.flex-row.flex-center', [ |
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', { |
||||
onClick: (event) => { |
||||
props.dispatch(actions.goHome()) |
||||
}, |
||||
}), |
||||
h('h2.page-subtitle', 'Import Accounts'), |
||||
]), |
||||
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) => { |
||||
props.dispatch(actions.showImportPage()) |
||||
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 'Private Key': |
||||
return h(PrivateKeyImportView) |
||||
case 'JSON File': |
||||
return h(JsonImportView) |
||||
default: |
||||
return h(JsonImportView) |
||||
} |
||||
} |
@ -0,0 +1,100 @@ |
||||
const inherits = require('util').inherits |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const connect = require('react-redux').connect |
||||
const actions = require('../../../../ui/app/actions') |
||||
const FileInput = require('react-simple-file-input').default |
||||
|
||||
const HELP_LINK = 'https://github.com/MetaMask/faq/blob/master/README.md#q-i-cant-use-the-import-feature-for-uploading-a-json-file-the-window-keeps-closing-when-i-try-to-select-a-file' |
||||
|
||||
module.exports = connect(mapStateToProps)(JsonImportSubview) |
||||
|
||||
function mapStateToProps (state) { |
||||
return { |
||||
error: state.appState.warning, |
||||
} |
||||
} |
||||
|
||||
inherits(JsonImportSubview, Component) |
||||
function JsonImportSubview () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
JsonImportSubview.prototype.render = function () { |
||||
const { error } = this.props |
||||
|
||||
return ( |
||||
h('div', { |
||||
style: { |
||||
display: 'flex', |
||||
flexDirection: 'column', |
||||
alignItems: 'center', |
||||
padding: '5px 15px 0px 15px', |
||||
}, |
||||
}, [ |
||||
|
||||
h('p', 'Used by a variety of different clients'), |
||||
h('a.warning', { href: HELP_LINK, target: '_blank' }, 'File import not working? Click here!'), |
||||
|
||||
h(FileInput, { |
||||
readAs: 'text', |
||||
onLoad: this.onLoad.bind(this), |
||||
style: { |
||||
margin: '20px 0px 12px 20px', |
||||
fontSize: '15px', |
||||
}, |
||||
}), |
||||
|
||||
h('input.large-input.letter-spacey', { |
||||
type: 'password', |
||||
placeholder: 'Enter password', |
||||
id: 'json-password-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.error', error) : null, |
||||
]) |
||||
) |
||||
} |
||||
|
||||
JsonImportSubview.prototype.onLoad = function (event, file) { |
||||
this.setState({file: file, fileContents: event.target.result}) |
||||
} |
||||
|
||||
JsonImportSubview.prototype.createKeyringOnEnter = function (event) { |
||||
if (event.key === 'Enter') { |
||||
event.preventDefault() |
||||
this.createNewKeychain() |
||||
} |
||||
} |
||||
|
||||
JsonImportSubview.prototype.createNewKeychain = function () { |
||||
const state = this.state |
||||
const { fileContents } = state |
||||
|
||||
if (!fileContents) { |
||||
const message = 'You must select a file to import.' |
||||
return this.props.dispatch(actions.displayWarning(message)) |
||||
} |
||||
|
||||
const passwordInput = document.getElementById('json-password-box') |
||||
const password = passwordInput.value |
||||
|
||||
if (!password) { |
||||
const message = 'You must enter a password for the selected file.' |
||||
return this.props.dispatch(actions.displayWarning(message)) |
||||
} |
||||
|
||||
this.props.dispatch(actions.importNewAccount('JSON File', [ fileContents, password ])) |
||||
} |
@ -0,0 +1,67 @@ |
||||
const inherits = require('util').inherits |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const connect = require('react-redux').connect |
||||
const actions = require('../../../../ui/app/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.error', 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.importNewAccount('Private Key', [ 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'), |
||||
]) |
||||
) |
||||
} |
||||
|
@ -0,0 +1,238 @@ |
||||
const inherits = require('util').inherits |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const connect = require('react-redux').connect |
||||
const actions = require('../../ui/app/actions') |
||||
const Tooltip = require('./components/tooltip.js') |
||||
|
||||
|
||||
const ethUtil = require('ethereumjs-util') |
||||
const abi = require('human-standard-token-abi') |
||||
const Eth = require('ethjs-query') |
||||
const EthContract = require('ethjs-contract') |
||||
|
||||
const emptyAddr = '0x0000000000000000000000000000000000000000' |
||||
|
||||
module.exports = connect(mapStateToProps)(AddTokenScreen) |
||||
|
||||
function mapStateToProps (state) { |
||||
return { |
||||
identities: state.metamask.identities, |
||||
} |
||||
} |
||||
|
||||
inherits(AddTokenScreen, Component) |
||||
function AddTokenScreen () { |
||||
this.state = { |
||||
warning: null, |
||||
address: null, |
||||
symbol: 'TOKEN', |
||||
decimals: 18, |
||||
} |
||||
Component.call(this) |
||||
} |
||||
|
||||
AddTokenScreen.prototype.render = function () { |
||||
const state = this.state |
||||
const props = this.props |
||||
const { warning, symbol, decimals } = state |
||||
|
||||
return ( |
||||
h('.flex-column.flex-grow', [ |
||||
|
||||
// subtitle and nav
|
||||
h('.section-title.flex-row.flex-center', [ |
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', { |
||||
onClick: (event) => { |
||||
props.dispatch(actions.goHome()) |
||||
}, |
||||
}), |
||||
h('h2.page-subtitle', 'Add Token'), |
||||
]), |
||||
|
||||
h('.error', { |
||||
style: { |
||||
display: warning ? 'block' : 'none', |
||||
padding: '0 20px', |
||||
textAlign: 'center', |
||||
}, |
||||
}, warning), |
||||
|
||||
// conf view
|
||||
h('.flex-column.flex-justify-center.flex-grow.select-none', [ |
||||
h('.flex-space-around', { |
||||
style: { |
||||
padding: '20px', |
||||
}, |
||||
}, [ |
||||
|
||||
h('div', [ |
||||
h(Tooltip, { |
||||
position: 'top', |
||||
title: 'The contract of the actual token contract. Click for more info.', |
||||
}, [ |
||||
h('a', { |
||||
style: { fontWeight: 'bold', paddingRight: '10px'}, |
||||
href: 'https://support.metamask.io/kb/article/24-what-is-a-token-contract-address', |
||||
target: '_blank', |
||||
}, [ |
||||
h('span', 'Token Contract Address '), |
||||
h('i.fa.fa-question-circle'), |
||||
]), |
||||
]), |
||||
]), |
||||
|
||||
h('section.flex-row.flex-center', [ |
||||
h('input#token-address', { |
||||
name: 'address', |
||||
placeholder: 'Token Contract Address', |
||||
onChange: this.tokenAddressDidChange.bind(this), |
||||
style: { |
||||
width: 'inherit', |
||||
flex: '1 0 auto', |
||||
height: '30px', |
||||
margin: '8px', |
||||
}, |
||||
}), |
||||
]), |
||||
|
||||
h('div', [ |
||||
h('span', { |
||||
style: { fontWeight: 'bold', paddingRight: '10px'}, |
||||
}, 'Token Symbol'), |
||||
]), |
||||
|
||||
h('div', { style: {display: 'flex'} }, [ |
||||
h('input#token_symbol', { |
||||
placeholder: `Like "ETH"`, |
||||
value: symbol, |
||||
style: { |
||||
width: 'inherit', |
||||
flex: '1 0 auto', |
||||
height: '30px', |
||||
margin: '8px', |
||||
}, |
||||
onChange: (event) => { |
||||
var element = event.target |
||||
var symbol = element.value |
||||
this.setState({ symbol }) |
||||
}, |
||||
}), |
||||
]), |
||||
|
||||
h('div', [ |
||||
h('span', { |
||||
style: { fontWeight: 'bold', paddingRight: '10px'}, |
||||
}, 'Decimals of Precision'), |
||||
]), |
||||
|
||||
h('div', { style: {display: 'flex'} }, [ |
||||
h('input#token_decimals', { |
||||
value: decimals, |
||||
type: 'number', |
||||
min: 0, |
||||
max: 36, |
||||
style: { |
||||
width: 'inherit', |
||||
flex: '1 0 auto', |
||||
height: '30px', |
||||
margin: '8px', |
||||
}, |
||||
onChange: (event) => { |
||||
var element = event.target |
||||
var decimals = element.value.trim() |
||||
this.setState({ decimals }) |
||||
}, |
||||
}), |
||||
]), |
||||
|
||||
h('button', { |
||||
style: { |
||||
alignSelf: 'center', |
||||
}, |
||||
onClick: (event) => { |
||||
const valid = this.validateInputs() |
||||
if (!valid) return |
||||
|
||||
const { address, symbol, decimals } = this.state |
||||
this.props.dispatch(actions.addToken(address.trim(), symbol.trim(), decimals)) |
||||
}, |
||||
}, 'Add'), |
||||
]), |
||||
]), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
AddTokenScreen.prototype.componentWillMount = function () { |
||||
if (typeof global.ethereumProvider === 'undefined') return |
||||
|
||||
this.eth = new Eth(global.ethereumProvider) |
||||
this.contract = new EthContract(this.eth) |
||||
this.TokenContract = this.contract(abi) |
||||
} |
||||
|
||||
AddTokenScreen.prototype.tokenAddressDidChange = function (event) { |
||||
const el = event.target |
||||
const address = el.value.trim() |
||||
if (ethUtil.isValidAddress(address) && address !== emptyAddr) { |
||||
this.setState({ address }) |
||||
this.attemptToAutoFillTokenParams(address) |
||||
} |
||||
} |
||||
|
||||
AddTokenScreen.prototype.validateInputs = function () { |
||||
let msg = '' |
||||
const state = this.state |
||||
const identitiesList = Object.keys(this.props.identities) |
||||
const { address, symbol, decimals } = state |
||||
const standardAddress = ethUtil.addHexPrefix(address).toLowerCase() |
||||
|
||||
const validAddress = ethUtil.isValidAddress(address) |
||||
if (!validAddress) { |
||||
msg += 'Address is invalid. ' |
||||
} |
||||
|
||||
const validDecimals = decimals >= 0 && decimals < 36 |
||||
if (!validDecimals) { |
||||
msg += 'Decimals must be at least 0, and not over 36. ' |
||||
} |
||||
|
||||
const symbolLen = symbol.trim().length |
||||
const validSymbol = symbolLen > 0 && symbolLen < 10 |
||||
if (!validSymbol) { |
||||
msg += 'Symbol must be between 0 and 10 characters.' |
||||
} |
||||
|
||||
const ownAddress = identitiesList.includes(standardAddress) |
||||
if (ownAddress) { |
||||
msg = 'Personal address detected. Input the token contract address.' |
||||
} |
||||
|
||||
const isValid = validAddress && validDecimals && !ownAddress |
||||
|
||||
if (!isValid) { |
||||
this.setState({ |
||||
warning: msg, |
||||
}) |
||||
} else { |
||||
this.setState({ warning: null }) |
||||
} |
||||
|
||||
return isValid |
||||
} |
||||
|
||||
AddTokenScreen.prototype.attemptToAutoFillTokenParams = async function (address) { |
||||
const contract = this.TokenContract.at(address) |
||||
|
||||
const results = await Promise.all([ |
||||
contract.symbol(), |
||||
contract.decimals(), |
||||
]) |
||||
|
||||
const [ symbol, decimals ] = results |
||||
if (symbol && decimals) { |
||||
console.log('SETTING SYMBOL AND DECIMALS', { symbol, decimals }) |
||||
this.setState({ symbol: symbol[0], decimals: decimals[0].toString() }) |
||||
} |
||||
} |
@ -0,0 +1,684 @@ |
||||
const inherits = require('util').inherits |
||||
const Component = require('react').Component |
||||
const connect = require('react-redux').connect |
||||
const h = require('react-hyperscript') |
||||
const actions = require('../../ui/app/actions') |
||||
// mascara
|
||||
const MascaraFirstTime = require('../../mascara/src/app/first-time').default |
||||
const MascaraBuyEtherScreen = require('../../mascara/src/app/first-time/buy-ether-screen').default |
||||
// init
|
||||
const InitializeMenuScreen = require('./first-time/init-menu') |
||||
const NewKeyChainScreen = require('./new-keychain') |
||||
// unlock
|
||||
const UnlockScreen = require('./unlock') |
||||
// accounts
|
||||
const AccountDetailScreen = require('./account-detail') |
||||
const SendTransactionScreen = require('./send') |
||||
const ConfirmTxScreen = require('./conf-tx') |
||||
// notice
|
||||
const NoticeScreen = require('./components/notice') |
||||
const generateLostAccountsNotice = require('../lib/lost-accounts-notice') |
||||
// other views
|
||||
const ConfigScreen = require('./config') |
||||
const AddTokenScreen = require('./add-token') |
||||
const Import = require('./accounts/import') |
||||
const InfoScreen = require('./info') |
||||
const Loading = require('./components/loading') |
||||
const SandwichExpando = require('sandwich-expando') |
||||
const Dropdown = require('./components/dropdown').Dropdown |
||||
const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem |
||||
const NetworkIndicator = require('./components/network') |
||||
const BuyView = require('./components/buy-button-subview') |
||||
const QrView = require('./components/qr-code') |
||||
const HDCreateVaultComplete = require('./keychains/hd/create-vault-complete') |
||||
const HDRestoreVaultScreen = require('./keychains/hd/restore-vault') |
||||
const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation') |
||||
const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns |
||||
const { BETA_UI_NETWORK_TYPE } = require('../../app/scripts/config').enums |
||||
|
||||
module.exports = connect(mapStateToProps)(App) |
||||
|
||||
inherits(App, Component) |
||||
function App () { Component.call(this) } |
||||
|
||||
function mapStateToProps (state) { |
||||
const { |
||||
identities, |
||||
accounts, |
||||
address, |
||||
keyrings, |
||||
isInitialized, |
||||
noActiveNotices, |
||||
seedWords, |
||||
featureFlags, |
||||
} = state.metamask |
||||
const selected = address || Object.keys(accounts)[0] |
||||
|
||||
return { |
||||
// state from plugin
|
||||
isLoading: state.appState.isLoading, |
||||
loadingMessage: state.appState.loadingMessage, |
||||
noActiveNotices: state.metamask.noActiveNotices, |
||||
isInitialized: state.metamask.isInitialized, |
||||
isUnlocked: state.metamask.isUnlocked, |
||||
currentView: state.appState.currentView, |
||||
activeAddress: state.appState.activeAddress, |
||||
transForward: state.appState.transForward, |
||||
isMascara: state.metamask.isMascara, |
||||
isOnboarding: Boolean(!noActiveNotices || seedWords || !isInitialized), |
||||
seedWords: state.metamask.seedWords, |
||||
unapprovedTxs: state.metamask.unapprovedTxs, |
||||
unapprovedMsgs: state.metamask.unapprovedMsgs, |
||||
menuOpen: state.appState.menuOpen, |
||||
network: state.metamask.network, |
||||
provider: state.metamask.provider, |
||||
forgottenPassword: state.appState.forgottenPassword, |
||||
lastUnreadNotice: state.metamask.lastUnreadNotice, |
||||
lostAccounts: state.metamask.lostAccounts, |
||||
frequentRpcList: state.metamask.frequentRpcList || [], |
||||
featureFlags, |
||||
|
||||
// state needed to get account dropdown temporarily rendering from app bar
|
||||
identities, |
||||
selected, |
||||
keyrings, |
||||
} |
||||
} |
||||
|
||||
App.prototype.render = function () { |
||||
var props = this.props |
||||
const { isLoading, loadingMessage, transForward, network } = props |
||||
const isLoadingNetwork = network === 'loading' && props.currentView.name !== 'config' |
||||
const loadMessage = loadingMessage || isLoadingNetwork ? |
||||
`Connecting to ${this.getNetworkName()}` : null |
||||
log.debug('Main ui render function') |
||||
|
||||
return ( |
||||
h('.flex-column.full-height', { |
||||
style: { |
||||
// Windows was showing a vertical scroll bar:
|
||||
overflow: 'hidden', |
||||
position: 'relative', |
||||
alignItems: 'center', |
||||
}, |
||||
}, [ |
||||
|
||||
// app bar
|
||||
this.renderAppBar(), |
||||
this.renderNetworkDropdown(), |
||||
this.renderDropdown(), |
||||
|
||||
this.renderLoadingIndicator({ isLoading, isLoadingNetwork, loadMessage }), |
||||
|
||||
// panel content
|
||||
h('.app-primary' + (transForward ? '.from-right' : '.from-left'), { |
||||
style: { |
||||
width: '100%', |
||||
}, |
||||
}, [ |
||||
this.renderPrimary(), |
||||
]), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
App.prototype.renderAppBar = function () { |
||||
if (window.METAMASK_UI_TYPE === 'notification') { |
||||
return null |
||||
} |
||||
|
||||
const props = this.props |
||||
const state = this.state || {} |
||||
const isNetworkMenuOpen = state.isNetworkMenuOpen || false |
||||
const {isMascara, isOnboarding} = props |
||||
|
||||
// Do not render header if user is in mascara onboarding
|
||||
if (isMascara && isOnboarding) { |
||||
return null |
||||
} |
||||
|
||||
// Do not render header if user is in mascara buy ether
|
||||
if (isMascara && props.currentView.name === 'buyEth') { |
||||
return null |
||||
} |
||||
|
||||
return ( |
||||
|
||||
h('.full-width', { |
||||
height: '38px', |
||||
}, [ |
||||
|
||||
h('.app-header.flex-row.flex-space-between', { |
||||
style: { |
||||
alignItems: 'center', |
||||
visibility: props.isUnlocked ? 'visible' : 'none', |
||||
background: props.isUnlocked ? 'white' : 'none', |
||||
height: '38px', |
||||
position: 'relative', |
||||
zIndex: 12, |
||||
}, |
||||
}, [ |
||||
|
||||
h('div.left-menu-section', { |
||||
style: { |
||||
display: 'flex', |
||||
flexDirection: 'row', |
||||
alignItems: 'center', |
||||
}, |
||||
}, [ |
||||
|
||||
// mini logo
|
||||
h('img', { |
||||
height: 24, |
||||
width: 24, |
||||
src: '/images/icon-128.png', |
||||
}), |
||||
|
||||
h(NetworkIndicator, { |
||||
network: this.props.network, |
||||
provider: this.props.provider, |
||||
onClick: (event) => { |
||||
event.preventDefault() |
||||
event.stopPropagation() |
||||
this.setState({ isNetworkMenuOpen: !isNetworkMenuOpen }) |
||||
}, |
||||
}), |
||||
]), |
||||
|
||||
props.isUnlocked && h('div', { |
||||
style: { |
||||
display: 'flex', |
||||
flexDirection: 'row', |
||||
alignItems: 'center', |
||||
}, |
||||
}, [ |
||||
|
||||
props.isUnlocked && h(AccountDropdowns, { |
||||
style: {}, |
||||
enableAccountsSelector: true, |
||||
identities: this.props.identities, |
||||
selected: this.props.currentView.context, |
||||
network: this.props.network, |
||||
keyrings: this.props.keyrings, |
||||
}, []), |
||||
|
||||
// hamburger
|
||||
props.isUnlocked && h(SandwichExpando, { |
||||
className: 'sandwich-expando', |
||||
width: 16, |
||||
barHeight: 2, |
||||
padding: 0, |
||||
isOpen: state.isMainMenuOpen, |
||||
color: 'rgb(247,146,30)', |
||||
onClick: () => { |
||||
this.setState({ |
||||
isMainMenuOpen: !state.isMainMenuOpen, |
||||
}) |
||||
}, |
||||
}), |
||||
]), |
||||
]), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
App.prototype.renderNetworkDropdown = function () { |
||||
const props = this.props |
||||
const { provider: { type: providerType, rpcTarget: activeNetwork } } = props |
||||
const rpcList = props.frequentRpcList |
||||
const state = this.state || {} |
||||
const isOpen = state.isNetworkMenuOpen |
||||
|
||||
return h(Dropdown, { |
||||
useCssTransition: true, |
||||
isOpen, |
||||
onClickOutside: (event) => { |
||||
const { classList } = event.target |
||||
const isNotToggleElement = [ |
||||
classList.contains('menu-icon'), |
||||
classList.contains('network-name'), |
||||
classList.contains('network-indicator'), |
||||
].filter(bool => bool).length === 0 |
||||
// classes from three constituent nodes of the toggle element
|
||||
|
||||
if (isNotToggleElement) { |
||||
this.setState({ isNetworkMenuOpen: false }) |
||||
} |
||||
}, |
||||
zIndex: 11, |
||||
style: { |
||||
position: 'absolute', |
||||
left: '2px', |
||||
top: '36px', |
||||
}, |
||||
innerStyle: { |
||||
padding: '2px 16px 2px 0px', |
||||
}, |
||||
}, [ |
||||
|
||||
h( |
||||
DropdownMenuItem, |
||||
{ |
||||
key: 'main', |
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }), |
||||
onClick: () => props.dispatch(actions.setProviderType('mainnet')), |
||||
style: { |
||||
fontSize: '18px', |
||||
}, |
||||
}, |
||||
[ |
||||
h('.menu-icon.diamond'), |
||||
'Main Ethereum Network', |
||||
providerType === 'mainnet' ? h('.check', '✓') : null, |
||||
] |
||||
), |
||||
|
||||
h( |
||||
DropdownMenuItem, |
||||
{ |
||||
key: 'ropsten', |
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }), |
||||
onClick: () => props.dispatch(actions.setProviderType('ropsten')), |
||||
style: { |
||||
fontSize: '18px', |
||||
}, |
||||
}, |
||||
[ |
||||
h('.menu-icon.red-dot'), |
||||
'Ropsten Test Network', |
||||
providerType === 'ropsten' ? h('.check', '✓') : null, |
||||
] |
||||
), |
||||
|
||||
h( |
||||
DropdownMenuItem, |
||||
{ |
||||
key: 'kovan', |
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }), |
||||
onClick: () => props.dispatch(actions.setProviderType('kovan')), |
||||
style: { |
||||
fontSize: '18px', |
||||
}, |
||||
}, |
||||
[ |
||||
h('.menu-icon.hollow-diamond'), |
||||
'Kovan Test Network', |
||||
providerType === 'kovan' ? h('.check', '✓') : null, |
||||
] |
||||
), |
||||
|
||||
h( |
||||
DropdownMenuItem, |
||||
{ |
||||
key: 'rinkeby', |
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }), |
||||
onClick: () => props.dispatch(actions.setProviderType('rinkeby')), |
||||
style: { |
||||
fontSize: '18px', |
||||
}, |
||||
}, |
||||
[ |
||||
h('.menu-icon.golden-square'), |
||||
'Rinkeby Test Network', |
||||
providerType === 'rinkeby' ? h('.check', '✓') : null, |
||||
] |
||||
), |
||||
|
||||
h( |
||||
DropdownMenuItem, |
||||
{ |
||||
key: 'default', |
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }), |
||||
onClick: () => props.dispatch(actions.setProviderType('localhost')), |
||||
style: { |
||||
fontSize: '18px', |
||||
}, |
||||
}, |
||||
[ |
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'), |
||||
'Localhost 8545', |
||||
activeNetwork === 'http://localhost:8545' ? h('.check', '✓') : null, |
||||
] |
||||
), |
||||
|
||||
this.renderCustomOption(props.provider), |
||||
this.renderCommonRpc(rpcList, props.provider), |
||||
|
||||
h( |
||||
DropdownMenuItem, |
||||
{ |
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }), |
||||
onClick: () => this.props.dispatch(actions.showConfigPage()), |
||||
style: { |
||||
fontSize: '18px', |
||||
}, |
||||
}, |
||||
[ |
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'), |
||||
'Custom RPC', |
||||
activeNetwork === 'custom' ? h('.check', '✓') : null, |
||||
] |
||||
), |
||||
|
||||
]) |
||||
} |
||||
|
||||
App.prototype.renderDropdown = function () { |
||||
const state = this.state || {} |
||||
const isOpen = state.isMainMenuOpen |
||||
|
||||
return h(Dropdown, { |
||||
useCssTransition: true, |
||||
isOpen: isOpen, |
||||
zIndex: 11, |
||||
onClickOutside: (event) => { |
||||
const classList = event.target.classList |
||||
const parentClassList = event.target.parentElement.classList |
||||
|
||||
const isToggleElement = classList.contains('sandwich-expando') || |
||||
parentClassList.contains('sandwich-expando') |
||||
|
||||
if (isOpen && !isToggleElement) { |
||||
this.setState({ isMainMenuOpen: false }) |
||||
} |
||||
}, |
||||
style: { |
||||
position: 'absolute', |
||||
right: '2px', |
||||
top: '38px', |
||||
}, |
||||
innerStyle: {}, |
||||
}, [ |
||||
h(DropdownMenuItem, { |
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), |
||||
onClick: () => { this.props.dispatch(actions.showConfigPage()) }, |
||||
}, 'Settings'), |
||||
|
||||
h(DropdownMenuItem, { |
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), |
||||
onClick: () => { this.props.dispatch(actions.lockMetamask()) }, |
||||
}, 'Lock'), |
||||
|
||||
h(DropdownMenuItem, { |
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), |
||||
onClick: () => { this.props.dispatch(actions.showInfoPage()) }, |
||||
}, 'Info/Help'), |
||||
|
||||
h(DropdownMenuItem, { |
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), |
||||
onClick: () => { |
||||
this.props.dispatch(actions.setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL')) |
||||
.then(() => this.props.dispatch(actions.setNetworkEndpoints(BETA_UI_NETWORK_TYPE))) |
||||
}, |
||||
}, 'Try Beta!'), |
||||
]) |
||||
} |
||||
|
||||
App.prototype.renderLoadingIndicator = function ({ isLoading, isLoadingNetwork, loadMessage }) { |
||||
const { isMascara } = this.props |
||||
|
||||
return isMascara |
||||
? null |
||||
: h(Loading, { |
||||
isLoading: isLoading || isLoadingNetwork, |
||||
loadingMessage: loadMessage, |
||||
}) |
||||
} |
||||
|
||||
App.prototype.renderBackButton = function (style, justArrow = false) { |
||||
var props = this.props |
||||
return ( |
||||
h('.flex-row', { |
||||
key: 'leftArrow', |
||||
style: style, |
||||
onClick: () => props.dispatch(actions.goBackToInitView()), |
||||
}, [ |
||||
h('i.fa.fa-arrow-left.cursor-pointer'), |
||||
justArrow ? null : h('div.cursor-pointer', { |
||||
style: { |
||||
marginLeft: '3px', |
||||
}, |
||||
onClick: () => props.dispatch(actions.goBackToInitView()), |
||||
}, 'BACK'), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
App.prototype.renderPrimary = function () { |
||||
log.debug('rendering primary') |
||||
var props = this.props |
||||
const {isMascara, isOnboarding} = props |
||||
|
||||
if (isMascara && isOnboarding) { |
||||
return h(MascaraFirstTime) |
||||
} |
||||
|
||||
// notices
|
||||
if (!props.noActiveNotices) { |
||||
log.debug('rendering notice screen for unread notices.') |
||||
return h(NoticeScreen, { |
||||
notice: props.lastUnreadNotice, |
||||
key: 'NoticeScreen', |
||||
onConfirm: () => props.dispatch(actions.markNoticeRead(props.lastUnreadNotice)), |
||||
}) |
||||
} else if (props.lostAccounts && props.lostAccounts.length > 0) { |
||||
log.debug('rendering notice screen for lost accounts view.') |
||||
return h(NoticeScreen, { |
||||
notice: generateLostAccountsNotice(props.lostAccounts), |
||||
key: 'LostAccountsNotice', |
||||
onConfirm: () => props.dispatch(actions.markAccountsFound()), |
||||
}) |
||||
} |
||||
|
||||
if (props.seedWords) { |
||||
log.debug('rendering seed words') |
||||
return h(HDCreateVaultComplete, {key: 'HDCreateVaultComplete'}) |
||||
} |
||||
|
||||
// show initialize screen
|
||||
if (!props.isInitialized || props.forgottenPassword) { |
||||
// show current view
|
||||
log.debug('rendering an initialize screen') |
||||
switch (props.currentView.name) { |
||||
|
||||
case 'restoreVault': |
||||
log.debug('rendering restore vault screen') |
||||
return h(HDRestoreVaultScreen, {key: 'HDRestoreVaultScreen'}) |
||||
|
||||
default: |
||||
log.debug('rendering menu screen') |
||||
return h(InitializeMenuScreen, {key: 'menuScreenInit'}) |
||||
} |
||||
} |
||||
|
||||
// show unlock screen
|
||||
if (!props.isUnlocked) { |
||||
switch (props.currentView.name) { |
||||
|
||||
case 'restoreVault': |
||||
log.debug('rendering restore vault screen') |
||||
return h(HDRestoreVaultScreen, {key: 'HDRestoreVaultScreen'}) |
||||
|
||||
case 'config': |
||||
log.debug('rendering config screen from unlock screen.') |
||||
return h(ConfigScreen, {key: 'config'}) |
||||
|
||||
default: |
||||
log.debug('rendering locked screen') |
||||
return h(UnlockScreen, {key: 'locked'}) |
||||
} |
||||
} |
||||
|
||||
// show current view
|
||||
switch (props.currentView.name) { |
||||
|
||||
case 'accountDetail': |
||||
log.debug('rendering account detail screen') |
||||
return h(AccountDetailScreen, {key: 'account-detail'}) |
||||
|
||||
case 'sendTransaction': |
||||
log.debug('rendering send tx screen') |
||||
return h(SendTransactionScreen, {key: 'send-transaction'}) |
||||
|
||||
case 'newKeychain': |
||||
log.debug('rendering new keychain screen') |
||||
return h(NewKeyChainScreen, {key: 'new-keychain'}) |
||||
|
||||
case 'confTx': |
||||
log.debug('rendering confirm tx screen') |
||||
return h(ConfirmTxScreen, {key: 'confirm-tx'}) |
||||
|
||||
case 'add-token': |
||||
log.debug('rendering add-token screen from unlock screen.') |
||||
return h(AddTokenScreen, {key: 'add-token'}) |
||||
|
||||
case 'config': |
||||
log.debug('rendering config screen') |
||||
return h(ConfigScreen, {key: 'config'}) |
||||
|
||||
case 'import-menu': |
||||
log.debug('rendering import screen') |
||||
return h(Import, {key: 'import-menu'}) |
||||
|
||||
case 'reveal-seed-conf': |
||||
log.debug('rendering reveal seed confirmation screen') |
||||
return h(RevealSeedConfirmation, {key: 'reveal-seed-conf'}) |
||||
|
||||
case 'info': |
||||
log.debug('rendering info screen') |
||||
return h(InfoScreen, {key: 'info'}) |
||||
|
||||
case 'buyEth': |
||||
log.debug('rendering buy ether screen') |
||||
return h(BuyView, {key: 'buyEthView'}) |
||||
|
||||
case 'onboardingBuyEth': |
||||
log.debug('rendering onboarding buy ether screen') |
||||
return h(MascaraBuyEtherScreen, {key: 'buyEthView'}) |
||||
|
||||
case 'qr': |
||||
log.debug('rendering show qr screen') |
||||
console.log(`QrView`, QrView); |
||||
return h('div', { |
||||
style: { |
||||
position: 'absolute', |
||||
height: '100%', |
||||
top: '0px', |
||||
left: '0px', |
||||
}, |
||||
}, [ |
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', { |
||||
onClick: () => props.dispatch(actions.backToAccountDetail(props.activeAddress)), |
||||
style: { |
||||
marginLeft: '10px', |
||||
marginTop: '50px', |
||||
}, |
||||
}), |
||||
h('div', { |
||||
style: { |
||||
position: 'absolute', |
||||
left: '44px', |
||||
width: '285px', |
||||
}, |
||||
}, [ |
||||
h(QrView, {key: 'qr'}), |
||||
]), |
||||
]) |
||||
|
||||
default: |
||||
log.debug('rendering default, account detail screen') |
||||
return h(AccountDetailScreen, {key: 'account-detail'}) |
||||
} |
||||
} |
||||
|
||||
App.prototype.toggleMetamaskActive = function () { |
||||
if (!this.props.isUnlocked) { |
||||
// currently inactive: redirect to password box
|
||||
var passwordBox = document.querySelector('input[type=password]') |
||||
if (!passwordBox) return |
||||
passwordBox.focus() |
||||
} else { |
||||
// currently active: deactivate
|
||||
this.props.dispatch(actions.lockMetamask(false)) |
||||
} |
||||
} |
||||
|
||||
App.prototype.renderCustomOption = function (provider) { |
||||
const { rpcTarget, type } = provider |
||||
const props = this.props |
||||
|
||||
if (type !== 'rpc') return null |
||||
|
||||
// Concatenate long URLs
|
||||
let label = rpcTarget |
||||
if (rpcTarget.length > 31) { |
||||
label = label.substr(0, 34) + '...' |
||||
} |
||||
|
||||
switch (rpcTarget) { |
||||
|
||||
case 'http://localhost:8545': |
||||
return null |
||||
|
||||
default: |
||||
return h( |
||||
DropdownMenuItem, |
||||
{ |
||||
key: rpcTarget, |
||||
onClick: () => props.dispatch(actions.setRpcTarget(rpcTarget)), |
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }), |
||||
}, |
||||
[ |
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'), |
||||
label, |
||||
h('.check', '✓'), |
||||
] |
||||
) |
||||
} |
||||
} |
||||
|
||||
App.prototype.getNetworkName = function () { |
||||
const { provider } = this.props |
||||
const providerName = provider.type |
||||
|
||||
let name |
||||
|
||||
if (providerName === 'mainnet') { |
||||
name = 'Main Ethereum Network' |
||||
} else if (providerName === 'ropsten') { |
||||
name = 'Ropsten Test Network' |
||||
} else if (providerName === 'kovan') { |
||||
name = 'Kovan Test Network' |
||||
} else if (providerName === 'rinkeby') { |
||||
name = 'Rinkeby Test Network' |
||||
} else { |
||||
name = 'Unknown Private Network' |
||||
} |
||||
|
||||
return name |
||||
} |
||||
|
||||
App.prototype.renderCommonRpc = function (rpcList, provider) { |
||||
const props = this.props |
||||
const rpcTarget = provider.rpcTarget |
||||
|
||||
return rpcList.map((rpc) => { |
||||
if ((rpc === 'http://localhost:8545') || (rpc === rpcTarget)) { |
||||
return null |
||||
} else { |
||||
return h( |
||||
DropdownMenuItem, |
||||
{ |
||||
key: `common${rpc}`, |
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }), |
||||
onClick: () => props.dispatch(actions.setRpcTarget(rpc)), |
||||
}, |
||||
[ |
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'), |
||||
rpc, |
||||
rpcTarget === rpc ? h('.check', '✓') : null, |
||||
] |
||||
) |
||||
} |
||||
}) |
||||
} |
@ -0,0 +1,319 @@ |
||||
const Component = require('react').Component |
||||
const PropTypes = require('react').PropTypes |
||||
const h = require('react-hyperscript') |
||||
const actions = require('../../../ui/app/actions') |
||||
const genAccountLink = require('etherscan-link').createAccountLink |
||||
const connect = require('react-redux').connect |
||||
const Dropdown = require('./dropdown').Dropdown |
||||
const DropdownMenuItem = require('./dropdown').DropdownMenuItem |
||||
const Identicon = require('./identicon') |
||||
const ethUtil = require('ethereumjs-util') |
||||
const copyToClipboard = require('copy-to-clipboard') |
||||
|
||||
class AccountDropdowns extends Component { |
||||
constructor (props) { |
||||
super(props) |
||||
this.state = { |
||||
accountSelectorActive: false, |
||||
optionsMenuActive: false, |
||||
} |
||||
this.accountSelectorToggleClassName = 'accounts-selector' |
||||
this.optionsMenuToggleClassName = 'fa-ellipsis-h' |
||||
} |
||||
|
||||
renderAccounts () { |
||||
const { identities, selected, keyrings } = this.props |
||||
|
||||
return Object.keys(identities).map((key, index) => { |
||||
const identity = identities[key] |
||||
const isSelected = identity.address === selected |
||||
|
||||
const simpleAddress = identity.address.substring(2).toLowerCase() |
||||
|
||||
const keyring = keyrings.find((kr) => { |
||||
return kr.accounts.includes(simpleAddress) || |
||||
kr.accounts.includes(identity.address) |
||||
}) |
||||
|
||||
return h( |
||||
DropdownMenuItem, |
||||
{ |
||||
closeMenu: () => {}, |
||||
onClick: () => { |
||||
this.props.actions.showAccountDetail(identity.address) |
||||
}, |
||||
style: { |
||||
marginTop: index === 0 ? '5px' : '', |
||||
fontSize: '24px', |
||||
}, |
||||
}, |
||||
[ |
||||
h( |
||||
Identicon, |
||||
{ |
||||
address: identity.address, |
||||
diameter: 32, |
||||
style: { |
||||
marginLeft: '10px', |
||||
}, |
||||
}, |
||||
), |
||||
this.indicateIfLoose(keyring), |
||||
h('span', { |
||||
style: { |
||||
marginLeft: '20px', |
||||
fontSize: '24px', |
||||
maxWidth: '145px', |
||||
whiteSpace: 'nowrap', |
||||
overflow: 'hidden', |
||||
textOverflow: 'ellipsis', |
||||
}, |
||||
}, identity.name || ''), |
||||
h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, isSelected ? h('.check', '✓') : null), |
||||
] |
||||
) |
||||
}) |
||||
} |
||||
|
||||
indicateIfLoose (keyring) { |
||||
try { // Sometimes keyrings aren't loaded yet:
|
||||
const type = keyring.type |
||||
const isLoose = type !== 'HD Key Tree' |
||||
return isLoose ? h('.keyring-label', 'LOOSE') : null |
||||
} catch (e) { return } |
||||
} |
||||
|
||||
renderAccountSelector () { |
||||
const { actions } = this.props |
||||
const { accountSelectorActive } = this.state |
||||
|
||||
return h( |
||||
Dropdown, |
||||
{ |
||||
useCssTransition: true, // Hardcoded because account selector is temporarily in app-header
|
||||
style: { |
||||
marginLeft: '-238px', |
||||
marginTop: '38px', |
||||
minWidth: '180px', |
||||
overflowY: 'auto', |
||||
maxHeight: '300px', |
||||
width: '300px', |
||||
}, |
||||
innerStyle: { |
||||
padding: '8px 25px', |
||||
}, |
||||
isOpen: accountSelectorActive, |
||||
onClickOutside: (event) => { |
||||
const { classList } = event.target |
||||
const isNotToggleElement = !classList.contains(this.accountSelectorToggleClassName) |
||||
if (accountSelectorActive && isNotToggleElement) { |
||||
this.setState({ accountSelectorActive: false }) |
||||
} |
||||
}, |
||||
}, |
||||
[ |
||||
...this.renderAccounts(), |
||||
h( |
||||
DropdownMenuItem, |
||||
{ |
||||
closeMenu: () => {}, |
||||
onClick: () => actions.addNewAccount(), |
||||
}, |
||||
[ |
||||
h( |
||||
Identicon, |
||||
{ |
||||
style: { |
||||
marginLeft: '10px', |
||||
}, |
||||
diameter: 32, |
||||
}, |
||||
), |
||||
h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, 'Create Account'), |
||||
], |
||||
), |
||||
h( |
||||
DropdownMenuItem, |
||||
{ |
||||
closeMenu: () => {}, |
||||
onClick: () => actions.showImportPage(), |
||||
}, |
||||
[ |
||||
h( |
||||
Identicon, |
||||
{ |
||||
style: { |
||||
marginLeft: '10px', |
||||
}, |
||||
diameter: 32, |
||||
}, |
||||
), |
||||
h('span', { |
||||
style: { |
||||
marginLeft: '20px', |
||||
fontSize: '24px', |
||||
marginBottom: '5px', |
||||
}, |
||||
}, 'Import Account'), |
||||
] |
||||
), |
||||
] |
||||
) |
||||
} |
||||
|
||||
renderAccountOptions () { |
||||
const { actions } = this.props |
||||
const { optionsMenuActive } = this.state |
||||
|
||||
return h( |
||||
Dropdown, |
||||
{ |
||||
style: { |
||||
marginLeft: '-215px', |
||||
minWidth: '180px', |
||||
}, |
||||
isOpen: optionsMenuActive, |
||||
onClickOutside: () => { |
||||
const { classList } = event.target |
||||
const isNotToggleElement = !classList.contains(this.optionsMenuToggleClassName) |
||||
if (optionsMenuActive && isNotToggleElement) { |
||||
this.setState({ optionsMenuActive: false }) |
||||
} |
||||
}, |
||||
}, |
||||
[ |
||||
h( |
||||
DropdownMenuItem, |
||||
{ |
||||
closeMenu: () => {}, |
||||
onClick: () => { |
||||
const { selected, network } = this.props |
||||
const url = genAccountLink(selected, network) |
||||
global.platform.openWindow({ url }) |
||||
}, |
||||
}, |
||||
'View account on Etherscan', |
||||
), |
||||
h( |
||||
DropdownMenuItem, |
||||
{ |
||||
closeMenu: () => {}, |
||||
onClick: () => { |
||||
const { selected, identities } = this.props |
||||
var identity = identities[selected] |
||||
actions.showQrView(selected, identity ? identity.name : '') |
||||
}, |
||||
}, |
||||
'Show QR Code', |
||||
), |
||||
h( |
||||
DropdownMenuItem, |
||||
{ |
||||
closeMenu: () => {}, |
||||
onClick: () => { |
||||
const { selected } = this.props |
||||
const checkSumAddress = selected && ethUtil.toChecksumAddress(selected) |
||||
copyToClipboard(checkSumAddress) |
||||
}, |
||||
}, |
||||
'Copy Address to clipboard', |
||||
), |
||||
h( |
||||
DropdownMenuItem, |
||||
{ |
||||
closeMenu: () => {}, |
||||
onClick: () => { |
||||
actions.requestAccountExport() |
||||
}, |
||||
}, |
||||
'Export Private Key', |
||||
), |
||||
] |
||||
) |
||||
} |
||||
|
||||
render () { |
||||
const { style, enableAccountsSelector, enableAccountOptions } = this.props |
||||
const { optionsMenuActive, accountSelectorActive } = this.state |
||||
|
||||
return h( |
||||
'span', |
||||
{ |
||||
style: style, |
||||
}, |
||||
[ |
||||
enableAccountsSelector && h( |
||||
// 'i.fa.fa-angle-down',
|
||||
'div.cursor-pointer.color-orange.accounts-selector', |
||||
{ |
||||
style: { |
||||
// fontSize: '1.8em',
|
||||
background: 'url(images/switch_acc.svg) white center center no-repeat', |
||||
height: '25px', |
||||
width: '25px', |
||||
transform: 'scale(0.75)', |
||||
marginRight: '3px', |
||||
}, |
||||
onClick: (event) => { |
||||
event.stopPropagation() |
||||
this.setState({ |
||||
accountSelectorActive: !accountSelectorActive, |
||||
optionsMenuActive: false, |
||||
}) |
||||
}, |
||||
}, |
||||
this.renderAccountSelector(), |
||||
), |
||||
enableAccountOptions && h( |
||||
'i.fa.fa-ellipsis-h', |
||||
{ |
||||
style: { |
||||
fontSize: '1.8em', |
||||
}, |
||||
onClick: (event) => { |
||||
event.stopPropagation() |
||||
this.setState({ |
||||
accountSelectorActive: false, |
||||
optionsMenuActive: !optionsMenuActive, |
||||
}) |
||||
}, |
||||
}, |
||||
this.renderAccountOptions() |
||||
), |
||||
] |
||||
) |
||||
} |
||||
} |
||||
|
||||
AccountDropdowns.defaultProps = { |
||||
enableAccountsSelector: false, |
||||
enableAccountOptions: false, |
||||
} |
||||
|
||||
AccountDropdowns.propTypes = { |
||||
identities: PropTypes.objectOf(PropTypes.object), |
||||
selected: PropTypes.string, |
||||
keyrings: PropTypes.array, |
||||
actions: PropTypes.objectOf(PropTypes.func), |
||||
network: PropTypes.string, |
||||
style: PropTypes.object, |
||||
enableAccountOptions: PropTypes.bool, |
||||
enableAccountsSelector: PropTypes.bool, |
||||
} |
||||
|
||||
const mapDispatchToProps = (dispatch) => { |
||||
return { |
||||
actions: { |
||||
showConfigPage: () => dispatch(actions.showConfigPage()), |
||||
requestAccountExport: () => dispatch(actions.requestExportAccount()), |
||||
showAccountDetail: (address) => dispatch(actions.showAccountDetail(address)), |
||||
addNewAccount: () => dispatch(actions.addNewAccount()), |
||||
showImportPage: () => dispatch(actions.showImportPage()), |
||||
showQrView: (selected, identity) => dispatch(actions.showQrView(selected, identity)), |
||||
}, |
||||
} |
||||
} |
||||
|
||||
module.exports = { |
||||
AccountDropdowns: connect(null, mapDispatchToProps)(AccountDropdowns), |
||||
} |
@ -0,0 +1,132 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
const exportAsFile = require('../util').exportAsFile |
||||
const copyToClipboard = require('copy-to-clipboard') |
||||
const actions = require('../../../ui/app/actions') |
||||
const ethUtil = require('ethereumjs-util') |
||||
const connect = require('react-redux').connect |
||||
|
||||
module.exports = connect(mapStateToProps)(ExportAccountView) |
||||
|
||||
inherits(ExportAccountView, Component) |
||||
function ExportAccountView () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
function mapStateToProps (state) { |
||||
return { |
||||
warning: state.appState.warning, |
||||
} |
||||
} |
||||
|
||||
ExportAccountView.prototype.render = function () { |
||||
const state = this.props |
||||
const accountDetail = state.accountDetail |
||||
const nickname = state.identities[state.address].name |
||||
|
||||
if (!accountDetail) return h('div') |
||||
const accountExport = accountDetail.accountExport |
||||
|
||||
const notExporting = accountExport === 'none' |
||||
const exportRequested = accountExport === 'requested' |
||||
const accountExported = accountExport === 'completed' |
||||
|
||||
if (notExporting) return h('div') |
||||
|
||||
if (exportRequested) { |
||||
const warning = `Export private keys at your own risk.` |
||||
return ( |
||||
h('div', { |
||||
style: { |
||||
display: 'inline-block', |
||||
textAlign: 'center', |
||||
}, |
||||
}, |
||||
[ |
||||
h('div', { |
||||
key: 'exporting', |
||||
style: { |
||||
margin: '0 20px', |
||||
}, |
||||
}, [ |
||||
h('p.error', warning), |
||||
h('input#exportAccount.sizing-input', { |
||||
type: 'password', |
||||
placeholder: 'confirm password', |
||||
onKeyPress: this.onExportKeyPress.bind(this), |
||||
style: { |
||||
position: 'relative', |
||||
top: '1.5px', |
||||
marginBottom: '7px', |
||||
}, |
||||
}), |
||||
]), |
||||
h('div', { |
||||
key: 'buttons', |
||||
style: { |
||||
margin: '0 20px', |
||||
}, |
||||
}, |
||||
[ |
||||
h('button', { |
||||
onClick: () => this.onExportKeyPress({ key: 'Enter', preventDefault: () => {} }), |
||||
style: { |
||||
marginRight: '10px', |
||||
}, |
||||
}, 'Submit'), |
||||
h('button', { |
||||
onClick: () => this.props.dispatch(actions.backToAccountDetail(this.props.address)), |
||||
}, 'Cancel'), |
||||
]), |
||||
(this.props.warning) && ( |
||||
h('span.error', { |
||||
style: { |
||||
margin: '20px', |
||||
}, |
||||
}, this.props.warning.split('-')) |
||||
), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
if (accountExported) { |
||||
const plainKey = ethUtil.stripHexPrefix(accountDetail.privateKey) |
||||
|
||||
return h('div.privateKey', { |
||||
style: { |
||||
margin: '0 20px', |
||||
}, |
||||
}, [ |
||||
h('label', 'Your private key (click to copy):'), |
||||
h('p.error.cursor-pointer', { |
||||
style: { |
||||
textOverflow: 'ellipsis', |
||||
overflow: 'hidden', |
||||
webkitUserSelect: 'text', |
||||
maxWidth: '275px', |
||||
}, |
||||
onClick: function (event) { |
||||
copyToClipboard(ethUtil.stripHexPrefix(accountDetail.privateKey)) |
||||
}, |
||||
}, plainKey), |
||||
h('button', { |
||||
onClick: () => this.props.dispatch(actions.backToAccountDetail(this.props.address)), |
||||
}, 'Done'), |
||||
h('button', { |
||||
style: { |
||||
marginLeft: '10px', |
||||
}, |
||||
onClick: () => exportAsFile(`MetaMask ${nickname} Private Key`, plainKey), |
||||
}, 'Save as File'), |
||||
]) |
||||
} |
||||
} |
||||
|
||||
ExportAccountView.prototype.onExportKeyPress = function (event) { |
||||
if (event.key !== 'Enter') return |
||||
event.preventDefault() |
||||
|
||||
const input = document.getElementById('exportAccount').value |
||||
this.props.dispatch(actions.exportAccount(input, this.props.address)) |
||||
} |
@ -0,0 +1,86 @@ |
||||
const inherits = require('util').inherits |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const Identicon = require('./identicon') |
||||
const formatBalance = require('../util').formatBalance |
||||
const addressSummary = require('../util').addressSummary |
||||
|
||||
module.exports = AccountPanel |
||||
|
||||
|
||||
inherits(AccountPanel, Component) |
||||
function AccountPanel () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
AccountPanel.prototype.render = function () { |
||||
var state = this.props |
||||
var identity = state.identity || {} |
||||
var account = state.account || {} |
||||
var isFauceting = state.isFauceting |
||||
|
||||
var panelState = { |
||||
key: `accountPanel${identity.address}`, |
||||
identiconKey: identity.address, |
||||
identiconLabel: identity.name || '', |
||||
attributes: [ |
||||
{ |
||||
key: 'ADDRESS', |
||||
value: addressSummary(identity.address), |
||||
}, |
||||
balanceOrFaucetingIndication(account, isFauceting), |
||||
], |
||||
} |
||||
|
||||
return ( |
||||
|
||||
h('.identity-panel.flex-row.flex-space-between', { |
||||
style: { |
||||
flex: '1 0 auto', |
||||
cursor: panelState.onClick ? 'pointer' : undefined, |
||||
}, |
||||
onClick: panelState.onClick, |
||||
}, [ |
||||
|
||||
// account identicon
|
||||
h('.identicon-wrapper.flex-column.select-none', [ |
||||
h(Identicon, { |
||||
address: panelState.identiconKey, |
||||
imageify: state.imageifyIdenticons, |
||||
}), |
||||
h('span.font-small', panelState.identiconLabel.substring(0, 7) + '...'), |
||||
]), |
||||
|
||||
// account address, balance
|
||||
h('.identity-data.flex-column.flex-justify-center.flex-grow.select-none', [ |
||||
|
||||
panelState.attributes.map((attr) => { |
||||
return h('.flex-row.flex-space-between', { |
||||
key: '' + Math.round(Math.random() * 1000000), |
||||
}, [ |
||||
h('label.font-small.no-select', attr.key), |
||||
h('span.font-small', attr.value), |
||||
]) |
||||
}), |
||||
]), |
||||
|
||||
]) |
||||
|
||||
) |
||||
} |
||||
|
||||
function balanceOrFaucetingIndication (account, isFauceting) { |
||||
// Temporarily deactivating isFauceting indication
|
||||
// because it shows fauceting for empty restored accounts.
|
||||
if (/* isFauceting*/ false) { |
||||
return { |
||||
key: 'Account is auto-funding.', |
||||
value: 'Please wait.', |
||||
} |
||||
} else { |
||||
return { |
||||
key: 'BALANCE', |
||||
value: formatBalance(account.balance), |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,89 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
const formatBalance = require('../util').formatBalance |
||||
const generateBalanceObject = require('../util').generateBalanceObject |
||||
const Tooltip = require('./tooltip.js') |
||||
const FiatValue = require('./fiat-value.js') |
||||
|
||||
module.exports = EthBalanceComponent |
||||
|
||||
inherits(EthBalanceComponent, Component) |
||||
function EthBalanceComponent () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
EthBalanceComponent.prototype.render = function () { |
||||
var props = this.props |
||||
let { value } = props |
||||
var style = props.style |
||||
var needsParse = this.props.needsParse !== undefined ? this.props.needsParse : true |
||||
value = value ? formatBalance(value, 6, needsParse) : '...' |
||||
var width = props.width |
||||
|
||||
return ( |
||||
|
||||
h('.ether-balance.ether-balance-amount', { |
||||
style: style, |
||||
}, [ |
||||
h('div', { |
||||
style: { |
||||
display: 'inline', |
||||
width: width, |
||||
}, |
||||
}, this.renderBalance(value)), |
||||
]) |
||||
|
||||
) |
||||
} |
||||
EthBalanceComponent.prototype.renderBalance = function (value) { |
||||
var props = this.props |
||||
if (value === 'None') return value |
||||
if (value === '...') return value |
||||
var balanceObj = generateBalanceObject(value, props.shorten ? 1 : 3) |
||||
var balance |
||||
var splitBalance = value.split(' ') |
||||
var ethNumber = splitBalance[0] |
||||
var ethSuffix = splitBalance[1] |
||||
const showFiat = 'showFiat' in props ? props.showFiat : true |
||||
|
||||
if (props.shorten) { |
||||
balance = balanceObj.shortBalance |
||||
} else { |
||||
balance = balanceObj.balance |
||||
} |
||||
|
||||
var label = balanceObj.label |
||||
|
||||
return ( |
||||
h(Tooltip, { |
||||
position: 'bottom', |
||||
title: `${ethNumber} ${ethSuffix}`, |
||||
}, h('div.flex-column', [ |
||||
h('.flex-row', { |
||||
style: { |
||||
alignItems: 'flex-end', |
||||
lineHeight: '13px', |
||||
fontFamily: 'Montserrat Light', |
||||
textRendering: 'geometricPrecision', |
||||
}, |
||||
}, [ |
||||
h('div', { |
||||
style: { |
||||
width: '100%', |
||||
textAlign: 'right', |
||||
}, |
||||
}, this.props.incoming ? `+${balance}` : balance), |
||||
h('div', { |
||||
style: { |
||||
color: ' #AEAEAE', |
||||
fontSize: '12px', |
||||
marginLeft: '5px', |
||||
}, |
||||
}, label), |
||||
]), |
||||
|
||||
showFiat ? h(FiatValue, { value: props.value }) : null, |
||||
])) |
||||
) |
||||
} |
@ -0,0 +1,46 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
const ethUtil = require('ethereumjs-util') |
||||
const extend = require('xtend') |
||||
|
||||
module.exports = BinaryRenderer |
||||
|
||||
inherits(BinaryRenderer, Component) |
||||
function BinaryRenderer () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
BinaryRenderer.prototype.render = function () { |
||||
const props = this.props |
||||
const { value, style } = props |
||||
const text = this.hexToText(value) |
||||
|
||||
const defaultStyle = extend({ |
||||
width: '315px', |
||||
maxHeight: '210px', |
||||
resize: 'none', |
||||
border: 'none', |
||||
background: 'white', |
||||
padding: '3px', |
||||
}, style) |
||||
|
||||
return ( |
||||
h('textarea.font-small', { |
||||
readOnly: true, |
||||
style: defaultStyle, |
||||
defaultValue: text, |
||||
}) |
||||
) |
||||
} |
||||
|
||||
BinaryRenderer.prototype.hexToText = function (hex) { |
||||
try { |
||||
const stripped = ethUtil.stripHexPrefix(hex) |
||||
const buff = Buffer.from(stripped, 'hex') |
||||
return buff.toString('utf8') |
||||
} catch (e) { |
||||
return hex |
||||
} |
||||
} |
||||
|
@ -0,0 +1,181 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
const ethUtil = require('ethereumjs-util') |
||||
const BN = ethUtil.BN |
||||
const extend = require('xtend') |
||||
|
||||
module.exports = BnAsDecimalInput |
||||
|
||||
inherits(BnAsDecimalInput, Component) |
||||
function BnAsDecimalInput () { |
||||
this.state = { invalid: null } |
||||
Component.call(this) |
||||
} |
||||
|
||||
/* Bn as Decimal Input |
||||
* |
||||
* A component for allowing easy, decimal editing |
||||
* of a passed in bn string value. |
||||
* |
||||
* On change, calls back its `onChange` function parameter |
||||
* and passes it an updated bn string. |
||||
*/ |
||||
|
||||
BnAsDecimalInput.prototype.render = function () { |
||||
const props = this.props |
||||
const state = this.state |
||||
|
||||
const { value, scale, precision, onChange, min, max } = props |
||||
|
||||
const suffix = props.suffix |
||||
const style = props.style |
||||
const valueString = value.toString(10) |
||||
const newMin = min && this.downsize(min.toString(10), scale) |
||||
const newMax = max && this.downsize(max.toString(10), scale) |
||||
const newValue = this.downsize(valueString, scale) |
||||
|
||||
return ( |
||||
h('.flex-column', [ |
||||
h('.flex-row', { |
||||
style: { |
||||
alignItems: 'flex-end', |
||||
lineHeight: '13px', |
||||
fontFamily: 'Montserrat Light', |
||||
textRendering: 'geometricPrecision', |
||||
}, |
||||
}, [ |
||||
h('input.hex-input', { |
||||
type: 'number', |
||||
step: 'any', |
||||
required: true, |
||||
min: newMin, |
||||
max: newMax, |
||||
style: extend({ |
||||
display: 'block', |
||||
textAlign: 'right', |
||||
backgroundColor: 'transparent', |
||||
border: '1px solid #bdbdbd', |
||||
|
||||
}, style), |
||||
value: newValue, |
||||
onBlur: (event) => { |
||||
this.updateValidity(event) |
||||
}, |
||||
onChange: (event) => { |
||||
this.updateValidity(event) |
||||
const value = (event.target.value === '') ? '' : event.target.value |
||||
|
||||
|
||||
const scaledNumber = this.upsize(value, scale, precision) |
||||
const precisionBN = new BN(scaledNumber, 10) |
||||
onChange(precisionBN, event.target.checkValidity()) |
||||
}, |
||||
onInvalid: (event) => { |
||||
const msg = this.constructWarning() |
||||
if (msg === state.invalid) { |
||||
return |
||||
} |
||||
this.setState({ invalid: msg }) |
||||
event.preventDefault() |
||||
return false |
||||
}, |
||||
}), |
||||
h('div', { |
||||
style: { |
||||
color: ' #AEAEAE', |
||||
fontSize: '12px', |
||||
marginLeft: '5px', |
||||
marginRight: '6px', |
||||
width: '20px', |
||||
}, |
||||
}, suffix), |
||||
]), |
||||
|
||||
state.invalid ? h('span.error', { |
||||
style: { |
||||
position: 'absolute', |
||||
right: '0px', |
||||
textAlign: 'right', |
||||
transform: 'translateY(26px)', |
||||
padding: '3px', |
||||
background: 'rgba(255,255,255,0.85)', |
||||
zIndex: '1', |
||||
textTransform: 'capitalize', |
||||
border: '2px solid #E20202', |
||||
}, |
||||
}, state.invalid) : null, |
||||
]) |
||||
) |
||||
} |
||||
|
||||
BnAsDecimalInput.prototype.setValid = function (message) { |
||||
this.setState({ invalid: null }) |
||||
} |
||||
|
||||
BnAsDecimalInput.prototype.updateValidity = function (event) { |
||||
const target = event.target |
||||
const value = this.props.value |
||||
const newValue = target.value |
||||
|
||||
if (value === newValue) { |
||||
return |
||||
} |
||||
|
||||
const valid = target.checkValidity() |
||||
|
||||
if (valid) { |
||||
this.setState({ invalid: null }) |
||||
} |
||||
} |
||||
|
||||
BnAsDecimalInput.prototype.constructWarning = function () { |
||||
const { name, min, max, scale, suffix } = this.props |
||||
const newMin = min && this.downsize(min.toString(10), scale) |
||||
const newMax = max && this.downsize(max.toString(10), scale) |
||||
let message = name ? name + ' ' : '' |
||||
|
||||
if (min && max) { |
||||
message += `must be greater than or equal to ${newMin} ${suffix} and less than or equal to ${newMax} ${suffix}.` |
||||
} else if (min) { |
||||
message += `must be greater than or equal to ${newMin} ${suffix}.` |
||||
} else if (max) { |
||||
message += `must be less than or equal to ${newMax} ${suffix}.` |
||||
} else { |
||||
message += 'Invalid input.' |
||||
} |
||||
|
||||
return message |
||||
} |
||||
|
||||
|
||||
BnAsDecimalInput.prototype.downsize = function (number, scale) { |
||||
// if there is no scaling, simply return the number
|
||||
if (scale === 0) { |
||||
return Number(number) |
||||
} else { |
||||
// if the scale is the same as the precision, account for this edge case.
|
||||
var adjustedNumber = number |
||||
while (adjustedNumber.length < scale) { |
||||
adjustedNumber = '0' + adjustedNumber |
||||
} |
||||
return Number(adjustedNumber.slice(0, -scale) + '.' + adjustedNumber.slice(-scale)) |
||||
} |
||||
} |
||||
|
||||
BnAsDecimalInput.prototype.upsize = function (number, scale, precision) { |
||||
var stringArray = number.toString().split('.') |
||||
var decimalLength = stringArray[1] ? stringArray[1].length : 0 |
||||
var newString = stringArray[0] |
||||
|
||||
// If there is scaling and decimal parts exist, integrate them in.
|
||||
if ((scale !== 0) && (decimalLength !== 0)) { |
||||
newString += stringArray[1].slice(0, precision) |
||||
} |
||||
|
||||
// Add 0s to account for the upscaling.
|
||||
for (var i = decimalLength; i < scale; i++) { |
||||
newString += '0' |
||||
} |
||||
return newString |
||||
} |
@ -0,0 +1,262 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
const connect = require('react-redux').connect |
||||
const actions = require('../../../ui/app/actions') |
||||
const CoinbaseForm = require('./coinbase-form') |
||||
const ShapeshiftForm = require('./shapeshift-form') |
||||
const Loading = require('./loading') |
||||
const AccountPanel = require('./account-panel') |
||||
const RadioList = require('./custom-radio-list') |
||||
const networkNames = require('../../../app/scripts/config.js').networkNames |
||||
|
||||
module.exports = connect(mapStateToProps)(BuyButtonSubview) |
||||
|
||||
function mapStateToProps (state) { |
||||
return { |
||||
identity: state.appState.identity, |
||||
account: state.metamask.accounts[state.appState.buyView.buyAddress], |
||||
warning: state.appState.warning, |
||||
buyView: state.appState.buyView, |
||||
network: state.metamask.network, |
||||
provider: state.metamask.provider, |
||||
context: state.appState.currentView.context, |
||||
isSubLoading: state.appState.isSubLoading, |
||||
} |
||||
} |
||||
|
||||
inherits(BuyButtonSubview, Component) |
||||
function BuyButtonSubview () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
BuyButtonSubview.prototype.render = function () { |
||||
return ( |
||||
h('div', { |
||||
style: { |
||||
width: '100%', |
||||
}, |
||||
}, [ |
||||
this.headerSubview(), |
||||
this.primarySubview(), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
BuyButtonSubview.prototype.headerSubview = function () { |
||||
const props = this.props |
||||
const isLoading = props.isSubLoading |
||||
return ( |
||||
|
||||
h('.flex-column', { |
||||
style: { |
||||
alignItems: 'center', |
||||
}, |
||||
}, [ |
||||
|
||||
// header bar (back button, label)
|
||||
h('.flex-row', { |
||||
style: { |
||||
alignItems: 'center', |
||||
justifyContent: 'center', |
||||
}, |
||||
}, [ |
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', { |
||||
onClick: this.backButtonContext.bind(this), |
||||
style: { |
||||
position: 'absolute', |
||||
left: '10px', |
||||
}, |
||||
}), |
||||
h('h2.text-transform-uppercase.flex-center', { |
||||
style: { |
||||
width: '100vw', |
||||
background: 'rgb(235, 235, 235)', |
||||
color: 'rgb(174, 174, 174)', |
||||
paddingTop: '4px', |
||||
paddingBottom: '4px', |
||||
}, |
||||
}, 'Buy Eth'), |
||||
]), |
||||
|
||||
// loading indication
|
||||
h('div', { |
||||
style: { |
||||
position: 'absolute', |
||||
top: '57vh', |
||||
left: '49vw', |
||||
}, |
||||
}, [ |
||||
h(Loading, { isLoading }), |
||||
]), |
||||
|
||||
// account panel
|
||||
h('div', { |
||||
style: { |
||||
width: '80%', |
||||
}, |
||||
}, [ |
||||
h(AccountPanel, { |
||||
showFullAddress: true, |
||||
identity: props.identity, |
||||
account: props.account, |
||||
}), |
||||
]), |
||||
|
||||
h('.flex-row', { |
||||
style: { |
||||
alignItems: 'center', |
||||
justifyContent: 'center', |
||||
}, |
||||
}, [ |
||||
h('h3.text-transform-uppercase.flex-center', { |
||||
style: { |
||||
paddingLeft: '15px', |
||||
width: '100vw', |
||||
background: 'rgb(235, 235, 235)', |
||||
color: 'rgb(174, 174, 174)', |
||||
paddingTop: '4px', |
||||
paddingBottom: '4px', |
||||
}, |
||||
}, 'Select Service'), |
||||
]), |
||||
|
||||
]) |
||||
|
||||
) |
||||
} |
||||
|
||||
|
||||
BuyButtonSubview.prototype.primarySubview = function () { |
||||
const props = this.props |
||||
const network = props.network |
||||
|
||||
switch (network) { |
||||
case 'loading': |
||||
return |
||||
|
||||
case '1': |
||||
return this.mainnetSubview() |
||||
|
||||
// Ropsten, Rinkeby, Kovan
|
||||
case '3': |
||||
case '4': |
||||
case '42': |
||||
const networkName = networkNames[network] |
||||
const label = `${networkName} Test Faucet` |
||||
return ( |
||||
h('div.flex-column', { |
||||
style: { |
||||
alignItems: 'center', |
||||
margin: '20px 50px', |
||||
}, |
||||
}, [ |
||||
h('button.text-transform-uppercase', { |
||||
onClick: () => this.props.dispatch(actions.buyEth({ network })), |
||||
style: { |
||||
marginTop: '15px', |
||||
}, |
||||
}, label), |
||||
// Kovan only: Dharma loans beta
|
||||
network === '42' ? ( |
||||
h('button.text-transform-uppercase', { |
||||
onClick: () => this.navigateTo('https://borrow.dharma.io/'), |
||||
style: { |
||||
marginTop: '15px', |
||||
}, |
||||
}, 'Borrow With Dharma (Beta)') |
||||
) : null, |
||||
]) |
||||
) |
||||
|
||||
default: |
||||
return ( |
||||
h('h2.error', 'Unknown network ID') |
||||
) |
||||
|
||||
} |
||||
} |
||||
|
||||
BuyButtonSubview.prototype.mainnetSubview = function () { |
||||
const props = this.props |
||||
|
||||
return ( |
||||
|
||||
h('.flex-column', { |
||||
style: { |
||||
alignItems: 'center', |
||||
}, |
||||
}, [ |
||||
|
||||
h('.flex-row.selected-exchange', { |
||||
style: { |
||||
position: 'relative', |
||||
right: '35px', |
||||
marginTop: '20px', |
||||
marginBottom: '20px', |
||||
}, |
||||
}, [ |
||||
h(RadioList, { |
||||
defaultFocus: props.buyView.subview, |
||||
labels: [ |
||||
'Coinbase', |
||||
'ShapeShift', |
||||
], |
||||
subtext: { |
||||
'Coinbase': 'Crypto/FIAT (USA only)', |
||||
'ShapeShift': 'Crypto', |
||||
}, |
||||
onClick: this.radioHandler.bind(this), |
||||
}), |
||||
]), |
||||
|
||||
h('h3.text-transform-uppercase', { |
||||
style: { |
||||
paddingLeft: '15px', |
||||
fontFamily: 'Montserrat Light', |
||||
width: '100vw', |
||||
background: 'rgb(235, 235, 235)', |
||||
color: 'rgb(174, 174, 174)', |
||||
paddingTop: '4px', |
||||
paddingBottom: '4px', |
||||
}, |
||||
}, props.buyView.subview), |
||||
|
||||
this.formVersionSubview(), |
||||
]) |
||||
|
||||
) |
||||
} |
||||
|
||||
BuyButtonSubview.prototype.formVersionSubview = function () { |
||||
const network = this.props.network |
||||
if (network === '1') { |
||||
if (this.props.buyView.formView.coinbase) { |
||||
return h(CoinbaseForm, this.props) |
||||
} else if (this.props.buyView.formView.shapeshift) { |
||||
return h(ShapeshiftForm, this.props) |
||||
} |
||||
} |
||||
} |
||||
|
||||
BuyButtonSubview.prototype.navigateTo = function (url) { |
||||
global.platform.openWindow({ url }) |
||||
} |
||||
|
||||
BuyButtonSubview.prototype.backButtonContext = function () { |
||||
if (this.props.context === 'confTx') { |
||||
this.props.dispatch(actions.showConfTxPage(false)) |
||||
} else { |
||||
console.log(`actions.goHome`, actions.goHome); |
||||
this.props.dispatch(actions.goHome()) |
||||
} |
||||
} |
||||
|
||||
BuyButtonSubview.prototype.radioHandler = function (event) { |
||||
switch (event.target.title) { |
||||
case 'Coinbase': |
||||
return this.props.dispatch(actions.coinBaseSubview()) |
||||
case 'ShapeShift': |
||||
return this.props.dispatch(actions.shapeShiftSubview(this.props.provider.type)) |
||||
} |
||||
} |
@ -0,0 +1,63 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
const connect = require('react-redux').connect |
||||
const actions = require('../../../ui/app/actions') |
||||
|
||||
module.exports = connect(mapStateToProps)(CoinbaseForm) |
||||
|
||||
function mapStateToProps (state) { |
||||
return { |
||||
warning: state.appState.warning, |
||||
} |
||||
} |
||||
|
||||
inherits(CoinbaseForm, Component) |
||||
|
||||
function CoinbaseForm () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
CoinbaseForm.prototype.render = function () { |
||||
var props = this.props |
||||
|
||||
return h('.flex-column', { |
||||
style: { |
||||
marginTop: '35px', |
||||
padding: '25px', |
||||
width: '100%', |
||||
}, |
||||
}, [ |
||||
h('.flex-row', { |
||||
style: { |
||||
justifyContent: 'space-around', |
||||
margin: '33px', |
||||
marginTop: '0px', |
||||
}, |
||||
}, [ |
||||
h('button.btn-green', { |
||||
onClick: this.toCoinbase.bind(this), |
||||
}, 'Continue to Coinbase'), |
||||
|
||||
h('button.btn-red', { |
||||
onClick: () => props.dispatch(actions.backTobuyView(props.accounts.address)), |
||||
}, 'Cancel'), |
||||
]), |
||||
]) |
||||
} |
||||
|
||||
CoinbaseForm.prototype.toCoinbase = function () { |
||||
const props = this.props |
||||
const address = props.buyView.buyAddress |
||||
props.dispatch(actions.buyEth({ network: '1', address, amount: 0 })) |
||||
} |
||||
|
||||
CoinbaseForm.prototype.renderLoading = function () { |
||||
return h('img', { |
||||
style: { |
||||
width: '27px', |
||||
marginRight: '-27px', |
||||
}, |
||||
src: 'images/loading.svg', |
||||
}) |
||||
} |
@ -0,0 +1,59 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
const copyToClipboard = require('copy-to-clipboard') |
||||
|
||||
const Tooltip = require('./tooltip') |
||||
|
||||
module.exports = CopyButton |
||||
|
||||
inherits(CopyButton, Component) |
||||
function CopyButton () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
// As parameters, accepts:
|
||||
// "value", which is the value to copy (mandatory)
|
||||
// "title", which is the text to show on hover (optional, defaults to 'Copy')
|
||||
CopyButton.prototype.render = function () { |
||||
const props = this.props |
||||
const state = this.state || {} |
||||
|
||||
const value = props.value |
||||
const copied = state.copied |
||||
|
||||
const message = copied ? 'Copied' : props.title || ' Copy ' |
||||
|
||||
return h('.copy-button', { |
||||
style: { |
||||
display: 'flex', |
||||
alignItems: 'center', |
||||
}, |
||||
}, [ |
||||
|
||||
h(Tooltip, { |
||||
title: message, |
||||
}, [ |
||||
h('i.fa.fa-clipboard.cursor-pointer.color-orange', { |
||||
style: { |
||||
margin: '5px', |
||||
}, |
||||
onClick: (event) => { |
||||
event.preventDefault() |
||||
event.stopPropagation() |
||||
copyToClipboard(value) |
||||
this.debounceRestore() |
||||
}, |
||||
}), |
||||
]), |
||||
|
||||
]) |
||||
} |
||||
|
||||
CopyButton.prototype.debounceRestore = function () { |
||||
this.setState({ copied: true }) |
||||
clearTimeout(this.timeout) |
||||
this.timeout = setTimeout(() => { |
||||
this.setState({ copied: false }) |
||||
}, 850) |
||||
} |
@ -0,0 +1,46 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
|
||||
const Tooltip = require('./tooltip') |
||||
const copyToClipboard = require('copy-to-clipboard') |
||||
|
||||
module.exports = Copyable |
||||
|
||||
inherits(Copyable, Component) |
||||
function Copyable () { |
||||
Component.call(this) |
||||
this.state = { |
||||
copied: false, |
||||
} |
||||
} |
||||
|
||||
Copyable.prototype.render = function () { |
||||
const props = this.props |
||||
const state = this.state |
||||
const { value, children } = props |
||||
const { copied } = state |
||||
|
||||
return h(Tooltip, { |
||||
title: copied ? 'Copied!' : 'Copy', |
||||
position: 'bottom', |
||||
}, h('span', { |
||||
style: { |
||||
cursor: 'pointer', |
||||
}, |
||||
onClick: (event) => { |
||||
event.preventDefault() |
||||
event.stopPropagation() |
||||
copyToClipboard(value) |
||||
this.debounceRestore() |
||||
}, |
||||
}, children)) |
||||
} |
||||
|
||||
Copyable.prototype.debounceRestore = function () { |
||||
this.setState({ copied: true }) |
||||
clearTimeout(this.timeout) |
||||
this.timeout = setTimeout(() => { |
||||
this.setState({ copied: false }) |
||||
}, 850) |
||||
} |
@ -0,0 +1,60 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
|
||||
module.exports = RadioList |
||||
|
||||
inherits(RadioList, Component) |
||||
function RadioList () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
RadioList.prototype.render = function () { |
||||
const props = this.props |
||||
const activeClass = '.custom-radio-selected' |
||||
const inactiveClass = '.custom-radio-inactive' |
||||
const { |
||||
labels, |
||||
defaultFocus, |
||||
} = props |
||||
|
||||
|
||||
return ( |
||||
h('.flex-row', { |
||||
style: { |
||||
fontSize: '12px', |
||||
}, |
||||
}, [ |
||||
h('.flex-column.custom-radios', { |
||||
style: { |
||||
marginRight: '5px', |
||||
}, |
||||
}, |
||||
labels.map((lable, i) => { |
||||
let isSelcted = (this.state !== null) |
||||
isSelcted = isSelcted ? (this.state.selected === lable) : (defaultFocus === lable) |
||||
return h(isSelcted ? activeClass : inactiveClass, { |
||||
title: lable, |
||||
onClick: (event) => { |
||||
this.setState({selected: event.target.title}) |
||||
props.onClick(event) |
||||
}, |
||||
}) |
||||
}) |
||||
), |
||||
h('.text', {}, |
||||
labels.map((lable) => { |
||||
if (props.subtext) { |
||||
return h('.flex-row', {}, [ |
||||
h('.radio-titles', lable), |
||||
h('.radio-titles-subtext', `- ${props.subtext[lable]}`), |
||||
]) |
||||
} else { |
||||
return h('.radio-titles', lable) |
||||
} |
||||
}) |
||||
), |
||||
]) |
||||
) |
||||
} |
||||
|
@ -0,0 +1,98 @@ |
||||
const Component = require('react').Component |
||||
const PropTypes = require('react').PropTypes |
||||
const h = require('react-hyperscript') |
||||
const MenuDroppo = require('./menu-droppo') |
||||
const extend = require('xtend') |
||||
|
||||
const noop = () => {} |
||||
|
||||
class Dropdown extends Component { |
||||
render () { |
||||
const { isOpen, onClickOutside, style, innerStyle, children, useCssTransition } = this.props |
||||
|
||||
const innerStyleDefaults = extend({ |
||||
borderRadius: '4px', |
||||
padding: '8px 16px', |
||||
background: 'rgba(0, 0, 0, 0.8)', |
||||
boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', |
||||
}, innerStyle) |
||||
|
||||
return h( |
||||
MenuDroppo, |
||||
{ |
||||
useCssTransition, |
||||
isOpen, |
||||
zIndex: 11, |
||||
onClickOutside, |
||||
style, |
||||
innerStyle: innerStyleDefaults, |
||||
}, |
||||
[ |
||||
h( |
||||
'style', |
||||
` |
||||
li.dropdown-menu-item:hover { color:rgb(225, 225, 225); } |
||||
li.dropdown-menu-item { color: rgb(185, 185, 185); position: relative } |
||||
` |
||||
), |
||||
...children, |
||||
] |
||||
) |
||||
} |
||||
} |
||||
|
||||
Dropdown.defaultProps = { |
||||
isOpen: false, |
||||
onClick: noop, |
||||
useCssTransition: false, |
||||
} |
||||
|
||||
Dropdown.propTypes = { |
||||
isOpen: PropTypes.bool.isRequired, |
||||
onClick: PropTypes.func.isRequired, |
||||
children: PropTypes.node, |
||||
style: PropTypes.object.isRequired, |
||||
onClickOutside: PropTypes.func, |
||||
innerStyle: PropTypes.object, |
||||
useCssTransition: PropTypes.bool, |
||||
} |
||||
|
||||
class DropdownMenuItem extends Component { |
||||
render () { |
||||
const { onClick, closeMenu, children, style } = this.props |
||||
|
||||
return h( |
||||
'li.dropdown-menu-item', |
||||
{ |
||||
onClick: () => { |
||||
onClick() |
||||
closeMenu() |
||||
}, |
||||
style: Object.assign({ |
||||
listStyle: 'none', |
||||
padding: '8px 0px 8px 0px', |
||||
fontSize: '18px', |
||||
fontStyle: 'normal', |
||||
fontFamily: 'Montserrat Regular', |
||||
cursor: 'pointer', |
||||
display: 'flex', |
||||
justifyContent: 'flex-start', |
||||
alignItems: 'center', |
||||
}, style), |
||||
}, |
||||
children |
||||
) |
||||
} |
||||
} |
||||
|
||||
DropdownMenuItem.propTypes = { |
||||
closeMenu: PropTypes.func.isRequired, |
||||
onClick: PropTypes.func.isRequired, |
||||
children: PropTypes.node, |
||||
style: PropTypes.object, |
||||
} |
||||
|
||||
module.exports = { |
||||
Dropdown, |
||||
DropdownMenuItem, |
||||
} |
@ -0,0 +1,57 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
const findDOMNode = require('react-dom').findDOMNode |
||||
|
||||
module.exports = EditableLabel |
||||
|
||||
inherits(EditableLabel, Component) |
||||
function EditableLabel () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
EditableLabel.prototype.render = function () { |
||||
const props = this.props |
||||
const state = this.state |
||||
|
||||
if (state && state.isEditingLabel) { |
||||
return h('div.editable-label', [ |
||||
h('input.sizing-input', { |
||||
defaultValue: props.textValue, |
||||
maxLength: '20', |
||||
onKeyPress: (event) => { |
||||
this.saveIfEnter(event) |
||||
}, |
||||
}), |
||||
h('button.editable-button', { |
||||
onClick: () => this.saveText(), |
||||
}, 'Save'), |
||||
]) |
||||
} else { |
||||
return h('div.name-label', { |
||||
onClick: (event) => { |
||||
const nameAttribute = event.target.getAttribute('name') |
||||
// checks for class to handle smaller CTA above the account name
|
||||
const classAttribute = event.target.getAttribute('class') |
||||
if (nameAttribute === 'edit' || classAttribute === 'edit-text') { |
||||
this.setState({ isEditingLabel: true }) |
||||
} |
||||
}, |
||||
}, this.props.children) |
||||
} |
||||
} |
||||
|
||||
EditableLabel.prototype.saveIfEnter = function (event) { |
||||
if (event.key === 'Enter') { |
||||
this.saveText() |
||||
} |
||||
} |
||||
|
||||
EditableLabel.prototype.saveText = function () { |
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
var container = findDOMNode(this) |
||||
var text = container.querySelector('.editable-label input').value |
||||
var truncatedText = text.substring(0, 20) |
||||
this.props.saveText(truncatedText) |
||||
this.setState({ isEditingLabel: false, textLabel: truncatedText }) |
||||
} |
@ -0,0 +1,170 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
const extend = require('xtend') |
||||
const debounce = require('debounce') |
||||
const copyToClipboard = require('copy-to-clipboard') |
||||
const ENS = require('ethjs-ens') |
||||
const networkMap = require('ethjs-ens/lib/network-map.json') |
||||
const ensRE = /.+\..+$/ |
||||
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' |
||||
|
||||
|
||||
module.exports = EnsInput |
||||
|
||||
inherits(EnsInput, Component) |
||||
function EnsInput () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
EnsInput.prototype.render = function () { |
||||
const props = this.props |
||||
const opts = extend(props, { |
||||
list: 'addresses', |
||||
onChange: () => { |
||||
const network = this.props.network |
||||
const networkHasEnsSupport = getNetworkEnsSupport(network) |
||||
if (!networkHasEnsSupport) return |
||||
|
||||
const recipient = document.querySelector('input[name="address"]').value |
||||
if (recipient.match(ensRE) === null) { |
||||
return this.setState({ |
||||
loadingEns: false, |
||||
ensResolution: null, |
||||
ensFailure: null, |
||||
}) |
||||
} |
||||
|
||||
this.setState({ |
||||
loadingEns: true, |
||||
}) |
||||
this.checkName() |
||||
}, |
||||
}) |
||||
return h('div', { |
||||
style: { width: '100%' }, |
||||
}, [ |
||||
h('input.large-input', opts), |
||||
// The address book functionality.
|
||||
h('datalist#addresses', |
||||
[ |
||||
// Corresponds to the addresses owned.
|
||||
Object.keys(props.identities).map((key) => { |
||||
const identity = props.identities[key] |
||||
return h('option', { |
||||
value: identity.address, |
||||
label: identity.name, |
||||
key: identity.address, |
||||
}) |
||||
}), |
||||
// Corresponds to previously sent-to addresses.
|
||||
props.addressBook.map((identity) => { |
||||
return h('option', { |
||||
value: identity.address, |
||||
label: identity.name, |
||||
key: identity.address, |
||||
}) |
||||
}), |
||||
]), |
||||
this.ensIcon(), |
||||
]) |
||||
} |
||||
|
||||
EnsInput.prototype.componentDidMount = function () { |
||||
const network = this.props.network |
||||
const networkHasEnsSupport = getNetworkEnsSupport(network) |
||||
this.setState({ ensResolution: ZERO_ADDRESS }) |
||||
|
||||
if (networkHasEnsSupport) { |
||||
const provider = global.ethereumProvider |
||||
this.ens = new ENS({ provider, network }) |
||||
this.checkName = debounce(this.lookupEnsName.bind(this), 200) |
||||
} |
||||
} |
||||
|
||||
EnsInput.prototype.lookupEnsName = function () { |
||||
const recipient = document.querySelector('input[name="address"]').value |
||||
const { ensResolution } = this.state |
||||
|
||||
log.info(`ENS attempting to resolve name: ${recipient}`) |
||||
this.ens.lookup(recipient.trim()) |
||||
.then((address) => { |
||||
if (address === ZERO_ADDRESS) throw new Error('No address has been set for this name.') |
||||
if (address !== ensResolution) { |
||||
this.setState({ |
||||
loadingEns: false, |
||||
ensResolution: address, |
||||
nickname: recipient.trim(), |
||||
hoverText: address + '\nClick to Copy', |
||||
ensFailure: false, |
||||
}) |
||||
} |
||||
}) |
||||
.catch((reason) => { |
||||
log.error(reason) |
||||
return this.setState({ |
||||
loadingEns: false, |
||||
ensResolution: ZERO_ADDRESS, |
||||
ensFailure: true, |
||||
hoverText: reason.message, |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
EnsInput.prototype.componentDidUpdate = function (prevProps, prevState) { |
||||
const state = this.state || {} |
||||
const ensResolution = state.ensResolution |
||||
// If an address is sent without a nickname, meaning not from ENS or from
|
||||
// the user's own accounts, a default of a one-space string is used.
|
||||
const nickname = state.nickname || ' ' |
||||
if (prevState && ensResolution && this.props.onChange && |
||||
ensResolution !== prevState.ensResolution) { |
||||
this.props.onChange(ensResolution, nickname) |
||||
} |
||||
} |
||||
|
||||
EnsInput.prototype.ensIcon = function (recipient) { |
||||
const { hoverText } = this.state || {} |
||||
return h('span', { |
||||
title: hoverText, |
||||
style: { |
||||
position: 'absolute', |
||||
padding: '9px', |
||||
transform: 'translatex(-40px)', |
||||
}, |
||||
}, this.ensIconContents(recipient)) |
||||
} |
||||
|
||||
EnsInput.prototype.ensIconContents = function (recipient) { |
||||
const { loadingEns, ensFailure, ensResolution } = this.state || { ensResolution: ZERO_ADDRESS} |
||||
|
||||
if (loadingEns) { |
||||
return h('img', { |
||||
src: 'images/loading.svg', |
||||
style: { |
||||
width: '30px', |
||||
height: '30px', |
||||
transform: 'translateY(-6px)', |
||||
}, |
||||
}) |
||||
} |
||||
|
||||
if (ensFailure) { |
||||
return h('i.fa.fa-warning.fa-lg.warning') |
||||
} |
||||
|
||||
if (ensResolution && (ensResolution !== ZERO_ADDRESS)) { |
||||
return h('i.fa.fa-check-circle.fa-lg.cursor-pointer', { |
||||
style: { color: 'green' }, |
||||
onClick: (event) => { |
||||
event.preventDefault() |
||||
event.stopPropagation() |
||||
copyToClipboard(ensResolution) |
||||
}, |
||||
}) |
||||
} |
||||
} |
||||
|
||||
function getNetworkEnsSupport (network) { |
||||
return Boolean(networkMap[network]) |
||||
} |
@ -0,0 +1,89 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
const formatBalance = require('../util').formatBalance |
||||
const generateBalanceObject = require('../util').generateBalanceObject |
||||
const Tooltip = require('./tooltip.js') |
||||
const FiatValue = require('./fiat-value.js') |
||||
|
||||
module.exports = EthBalanceComponent |
||||
|
||||
inherits(EthBalanceComponent, Component) |
||||
function EthBalanceComponent () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
EthBalanceComponent.prototype.render = function () { |
||||
var props = this.props |
||||
let { value } = props |
||||
const { style, width } = props |
||||
var needsParse = this.props.needsParse !== undefined ? this.props.needsParse : true |
||||
value = value ? formatBalance(value, 6, needsParse) : '...' |
||||
|
||||
return ( |
||||
|
||||
h('.ether-balance.ether-balance-amount', { |
||||
style, |
||||
}, [ |
||||
h('div', { |
||||
style: { |
||||
display: 'inline', |
||||
width, |
||||
}, |
||||
}, this.renderBalance(value)), |
||||
]) |
||||
|
||||
) |
||||
} |
||||
EthBalanceComponent.prototype.renderBalance = function (value) { |
||||
var props = this.props |
||||
const { conversionRate, shorten, incoming, currentCurrency } = props |
||||
if (value === 'None') return value |
||||
if (value === '...') return value |
||||
var balanceObj = generateBalanceObject(value, shorten ? 1 : 3) |
||||
var balance |
||||
var splitBalance = value.split(' ') |
||||
var ethNumber = splitBalance[0] |
||||
var ethSuffix = splitBalance[1] |
||||
const showFiat = 'showFiat' in props ? props.showFiat : true |
||||
|
||||
if (shorten) { |
||||
balance = balanceObj.shortBalance |
||||
} else { |
||||
balance = balanceObj.balance |
||||
} |
||||
|
||||
var label = balanceObj.label |
||||
|
||||
return ( |
||||
h(Tooltip, { |
||||
position: 'bottom', |
||||
title: `${ethNumber} ${ethSuffix}`, |
||||
}, h('div.flex-column', [ |
||||
h('.flex-row', { |
||||
style: { |
||||
alignItems: 'flex-end', |
||||
lineHeight: '13px', |
||||
fontFamily: 'Montserrat Light', |
||||
textRendering: 'geometricPrecision', |
||||
}, |
||||
}, [ |
||||
h('div', { |
||||
style: { |
||||
width: '100%', |
||||
textAlign: 'right', |
||||
}, |
||||
}, incoming ? `+${balance}` : balance), |
||||
h('div', { |
||||
style: { |
||||
color: ' #AEAEAE', |
||||
fontSize: '12px', |
||||
marginLeft: '5px', |
||||
}, |
||||
}, label), |
||||
]), |
||||
|
||||
showFiat ? h(FiatValue, { value: props.value, conversionRate, currentCurrency }) : null, |
||||
])) |
||||
) |
||||
} |
@ -0,0 +1,64 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
const formatBalance = require('../util').formatBalance |
||||
|
||||
module.exports = FiatValue |
||||
|
||||
inherits(FiatValue, Component) |
||||
function FiatValue () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
FiatValue.prototype.render = function () { |
||||
const props = this.props |
||||
const { conversionRate, currentCurrency } = props |
||||
const renderedCurrency = currentCurrency || '' |
||||
|
||||
const value = formatBalance(props.value, 6) |
||||
|
||||
if (value === 'None') return value |
||||
var fiatDisplayNumber, fiatTooltipNumber |
||||
var splitBalance = value.split(' ') |
||||
|
||||
if (conversionRate !== 0) { |
||||
fiatTooltipNumber = Number(splitBalance[0]) * conversionRate |
||||
fiatDisplayNumber = fiatTooltipNumber.toFixed(2) |
||||
} else { |
||||
fiatDisplayNumber = 'N/A' |
||||
fiatTooltipNumber = 'Unknown' |
||||
} |
||||
|
||||
return fiatDisplay(fiatDisplayNumber, renderedCurrency.toUpperCase()) |
||||
} |
||||
|
||||
function fiatDisplay (fiatDisplayNumber, fiatSuffix) { |
||||
if (fiatDisplayNumber !== 'N/A') { |
||||
return h('.flex-row', { |
||||
style: { |
||||
alignItems: 'flex-end', |
||||
lineHeight: '13px', |
||||
fontFamily: 'Montserrat Light', |
||||
textRendering: 'geometricPrecision', |
||||
}, |
||||
}, [ |
||||
h('div', { |
||||
style: { |
||||
width: '100%', |
||||
textAlign: 'right', |
||||
fontSize: '12px', |
||||
color: '#333333', |
||||
}, |
||||
}, fiatDisplayNumber), |
||||
h('div', { |
||||
style: { |
||||
color: '#AEAEAE', |
||||
marginLeft: '5px', |
||||
fontSize: '12px', |
||||
}, |
||||
}, fiatSuffix), |
||||
]) |
||||
} else { |
||||
return h('div') |
||||
} |
||||
} |
@ -0,0 +1,154 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
const ethUtil = require('ethereumjs-util') |
||||
const BN = ethUtil.BN |
||||
const extend = require('xtend') |
||||
|
||||
module.exports = HexAsDecimalInput |
||||
|
||||
inherits(HexAsDecimalInput, Component) |
||||
function HexAsDecimalInput () { |
||||
this.state = { invalid: null } |
||||
Component.call(this) |
||||
} |
||||
|
||||
/* Hex as Decimal Input |
||||
* |
||||
* A component for allowing easy, decimal editing |
||||
* of a passed in hex string value. |
||||
* |
||||
* On change, calls back its `onChange` function parameter |
||||
* and passes it an updated hex string. |
||||
*/ |
||||
|
||||
HexAsDecimalInput.prototype.render = function () { |
||||
const props = this.props |
||||
const state = this.state |
||||
|
||||
const { value, onChange, min, max } = props |
||||
|
||||
const toEth = props.toEth |
||||
const suffix = props.suffix |
||||
const decimalValue = decimalize(value, toEth) |
||||
const style = props.style |
||||
|
||||
return ( |
||||
h('.flex-column', [ |
||||
h('.flex-row', { |
||||
style: { |
||||
alignItems: 'flex-end', |
||||
lineHeight: '13px', |
||||
fontFamily: 'Montserrat Light', |
||||
textRendering: 'geometricPrecision', |
||||
}, |
||||
}, [ |
||||
h('input.hex-input', { |
||||
type: 'number', |
||||
required: true, |
||||
min: min, |
||||
max: max, |
||||
style: extend({ |
||||
display: 'block', |
||||
textAlign: 'right', |
||||
backgroundColor: 'transparent', |
||||
border: '1px solid #bdbdbd', |
||||
|
||||
}, style), |
||||
value: parseInt(decimalValue), |
||||
onBlur: (event) => { |
||||
this.updateValidity(event) |
||||
}, |
||||
onChange: (event) => { |
||||
this.updateValidity(event) |
||||
const hexString = (event.target.value === '') ? '' : hexify(event.target.value) |
||||
onChange(hexString) |
||||
}, |
||||
onInvalid: (event) => { |
||||
const msg = this.constructWarning() |
||||
if (msg === state.invalid) { |
||||
return |
||||
} |
||||
this.setState({ invalid: msg }) |
||||
event.preventDefault() |
||||
return false |
||||
}, |
||||
}), |
||||
h('div', { |
||||
style: { |
||||
color: ' #AEAEAE', |
||||
fontSize: '12px', |
||||
marginLeft: '5px', |
||||
marginRight: '6px', |
||||
width: '20px', |
||||
}, |
||||
}, suffix), |
||||
]), |
||||
|
||||
state.invalid ? h('span.error', { |
||||
style: { |
||||
position: 'absolute', |
||||
right: '0px', |
||||
textAlign: 'right', |
||||
transform: 'translateY(26px)', |
||||
padding: '3px', |
||||
background: 'rgba(255,255,255,0.85)', |
||||
zIndex: '1', |
||||
textTransform: 'capitalize', |
||||
border: '2px solid #E20202', |
||||
}, |
||||
}, state.invalid) : null, |
||||
]) |
||||
) |
||||
} |
||||
|
||||
HexAsDecimalInput.prototype.setValid = function (message) { |
||||
this.setState({ invalid: null }) |
||||
} |
||||
|
||||
HexAsDecimalInput.prototype.updateValidity = function (event) { |
||||
const target = event.target |
||||
const value = this.props.value |
||||
const newValue = target.value |
||||
|
||||
if (value === newValue) { |
||||
return |
||||
} |
||||
|
||||
const valid = target.checkValidity() |
||||
if (valid) { |
||||
this.setState({ invalid: null }) |
||||
} |
||||
} |
||||
|
||||
HexAsDecimalInput.prototype.constructWarning = function () { |
||||
const { name, min, max } = this.props |
||||
let message = name ? name + ' ' : '' |
||||
|
||||
if (min && max) { |
||||
message += `must be greater than or equal to ${min} and less than or equal to ${max}.` |
||||
} else if (min) { |
||||
message += `must be greater than or equal to ${min}.` |
||||
} else if (max) { |
||||
message += `must be less than or equal to ${max}.` |
||||
} else { |
||||
message += 'Invalid input.' |
||||
} |
||||
|
||||
return message |
||||
} |
||||
|
||||
function hexify (decimalString) { |
||||
const hexBN = new BN(parseInt(decimalString), 10) |
||||
return '0x' + hexBN.toString('hex') |
||||
} |
||||
|
||||
function decimalize (input, toEth) { |
||||
if (input === '') { |
||||
return '' |
||||
} else { |
||||
const strippedInput = ethUtil.stripHexPrefix(input) |
||||
const inputBN = new BN(strippedInput, 'hex') |
||||
return inputBN.toString(10) |
||||
} |
||||
} |
@ -0,0 +1,74 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
const isNode = require('detect-node') |
||||
const findDOMNode = require('react-dom').findDOMNode |
||||
const jazzicon = require('jazzicon') |
||||
const iconFactoryGen = require('../../lib/icon-factory') |
||||
const iconFactory = iconFactoryGen(jazzicon) |
||||
|
||||
module.exports = IdenticonComponent |
||||
|
||||
inherits(IdenticonComponent, Component) |
||||
function IdenticonComponent () { |
||||
Component.call(this) |
||||
|
||||
this.defaultDiameter = 46 |
||||
} |
||||
|
||||
IdenticonComponent.prototype.render = function () { |
||||
var props = this.props |
||||
var diameter = props.diameter || this.defaultDiameter |
||||
return ( |
||||
h('div', { |
||||
key: 'identicon-' + this.props.address, |
||||
style: { |
||||
display: 'flex', |
||||
alignItems: 'center', |
||||
justifyContent: 'center', |
||||
height: diameter, |
||||
width: diameter, |
||||
borderRadius: diameter / 2, |
||||
overflow: 'hidden', |
||||
}, |
||||
}) |
||||
) |
||||
} |
||||
|
||||
IdenticonComponent.prototype.componentDidMount = function () { |
||||
var props = this.props |
||||
const { address } = props |
||||
|
||||
if (!address) return |
||||
|
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
var container = findDOMNode(this) |
||||
|
||||
var diameter = props.diameter || this.defaultDiameter |
||||
if (!isNode) { |
||||
var img = iconFactory.iconForAddress(address, diameter) |
||||
container.appendChild(img) |
||||
} |
||||
} |
||||
|
||||
IdenticonComponent.prototype.componentDidUpdate = function () { |
||||
var props = this.props |
||||
const { address } = props |
||||
|
||||
if (!address) return |
||||
|
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
var container = findDOMNode(this) |
||||
|
||||
var children = container.children |
||||
for (var i = 0; i < children.length; i++) { |
||||
container.removeChild(children[i]) |
||||
} |
||||
|
||||
var diameter = props.diameter || this.defaultDiameter |
||||
if (!isNode) { |
||||
var img = iconFactory.iconForAddress(address, diameter) |
||||
container.appendChild(img) |
||||
} |
||||
} |
||||
|
@ -0,0 +1,45 @@ |
||||
const inherits = require('util').inherits |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
|
||||
|
||||
inherits(LoadingIndicator, Component) |
||||
module.exports = LoadingIndicator |
||||
|
||||
function LoadingIndicator () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
LoadingIndicator.prototype.render = function () { |
||||
const { isLoading, loadingMessage } = this.props |
||||
|
||||
return ( |
||||
isLoading ? h('.full-flex-height', { |
||||
style: { |
||||
left: '0px', |
||||
zIndex: 10, |
||||
position: 'absolute', |
||||
flexDirection: 'column', |
||||
display: 'flex', |
||||
justifyContent: 'center', |
||||
alignItems: 'center', |
||||
height: '100%', |
||||
width: '100%', |
||||
background: 'rgba(255, 255, 255, 0.8)', |
||||
}, |
||||
}, [ |
||||
h('img', { |
||||
src: 'images/loading.svg', |
||||
}), |
||||
|
||||
h('br'), |
||||
|
||||
showMessageIfAny(loadingMessage), |
||||
]) : null |
||||
) |
||||
} |
||||
|
||||
function showMessageIfAny (loadingMessage) { |
||||
if (!loadingMessage) return null |
||||
return h('span', loadingMessage) |
||||
} |
@ -0,0 +1,59 @@ |
||||
const inherits = require('util').inherits |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const metamaskLogo = require('metamask-logo') |
||||
const debounce = require('debounce') |
||||
|
||||
module.exports = Mascot |
||||
|
||||
inherits(Mascot, Component) |
||||
function Mascot () { |
||||
Component.call(this) |
||||
this.logo = metamaskLogo({ |
||||
followMouse: true, |
||||
pxNotRatio: true, |
||||
width: 200, |
||||
height: 200, |
||||
}) |
||||
|
||||
this.refollowMouse = debounce(this.logo.setFollowMouse.bind(this.logo, true), 1000) |
||||
this.unfollowMouse = this.logo.setFollowMouse.bind(this.logo, false) |
||||
} |
||||
|
||||
Mascot.prototype.render = function () { |
||||
// this is a bit hacky
|
||||
// the event emitter is on `this.props`
|
||||
// and we dont get that until render
|
||||
this.handleAnimationEvents() |
||||
|
||||
return h('#metamask-mascot-container', { |
||||
style: { zIndex: 0 }, |
||||
}) |
||||
} |
||||
|
||||
Mascot.prototype.componentDidMount = function () { |
||||
var targetDivId = 'metamask-mascot-container' |
||||
var container = document.getElementById(targetDivId) |
||||
container.appendChild(this.logo.container) |
||||
} |
||||
|
||||
Mascot.prototype.componentWillUnmount = function () { |
||||
this.animations = this.props.animationEventEmitter |
||||
this.animations.removeAllListeners() |
||||
this.logo.container.remove() |
||||
this.logo.stopAnimation() |
||||
} |
||||
|
||||
Mascot.prototype.handleAnimationEvents = function () { |
||||
// only setup listeners once
|
||||
if (this.animations) return |
||||
this.animations = this.props.animationEventEmitter |
||||
this.animations.on('point', this.lookAt.bind(this)) |
||||
this.animations.on('setFollowMouse', this.logo.setFollowMouse.bind(this.logo)) |
||||
} |
||||
|
||||
Mascot.prototype.lookAt = function (target) { |
||||
this.unfollowMouse() |
||||
this.logo.lookAt(target) |
||||
this.refollowMouse() |
||||
} |
@ -0,0 +1,132 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
const findDOMNode = require('react-dom').findDOMNode |
||||
const ReactCSSTransitionGroup = require('react-addons-css-transition-group') |
||||
|
||||
module.exports = MenuDroppoComponent |
||||
|
||||
|
||||
inherits(MenuDroppoComponent, Component) |
||||
function MenuDroppoComponent () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
MenuDroppoComponent.prototype.render = function () { |
||||
const speed = this.props.speed || '300ms' |
||||
const useCssTransition = this.props.useCssTransition |
||||
const zIndex = ('zIndex' in this.props) ? this.props.zIndex : 0 |
||||
|
||||
this.manageListeners() |
||||
|
||||
const style = this.props.style || {} |
||||
if (!('position' in style)) { |
||||
style.position = 'fixed' |
||||
} |
||||
style.zIndex = zIndex |
||||
|
||||
return ( |
||||
h('.menu-droppo-container', { |
||||
style, |
||||
}, [ |
||||
h('style', ` |
||||
.menu-droppo-enter { |
||||
transition: transform ${speed} ease-in-out; |
||||
transform: translateY(-200%); |
||||
} |
||||
|
||||
.menu-droppo-enter.menu-droppo-enter-active { |
||||
transition: transform ${speed} ease-in-out; |
||||
transform: translateY(0%); |
||||
} |
||||
|
||||
.menu-droppo-leave { |
||||
transition: transform ${speed} ease-in-out; |
||||
transform: translateY(0%); |
||||
} |
||||
|
||||
.menu-droppo-leave.menu-droppo-leave-active { |
||||
transition: transform ${speed} ease-in-out; |
||||
transform: translateY(-200%); |
||||
} |
||||
`),
|
||||
|
||||
useCssTransition |
||||
? h(ReactCSSTransitionGroup, { |
||||
className: 'css-transition-group', |
||||
transitionName: 'menu-droppo', |
||||
transitionEnterTimeout: parseInt(speed), |
||||
transitionLeaveTimeout: parseInt(speed), |
||||
}, this.renderPrimary()) |
||||
: this.renderPrimary(), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
MenuDroppoComponent.prototype.renderPrimary = function () { |
||||
const isOpen = this.props.isOpen |
||||
if (!isOpen) { |
||||
return null |
||||
} |
||||
|
||||
const innerStyle = this.props.innerStyle || {} |
||||
|
||||
return ( |
||||
h('.menu-droppo', { |
||||
key: 'menu-droppo-drawer', |
||||
style: innerStyle, |
||||
}, |
||||
[ this.props.children ]) |
||||
) |
||||
} |
||||
|
||||
MenuDroppoComponent.prototype.manageListeners = function () { |
||||
const isOpen = this.props.isOpen |
||||
const onClickOutside = this.props.onClickOutside |
||||
|
||||
if (isOpen) { |
||||
this.outsideClickHandler = onClickOutside |
||||
} else if (isOpen) { |
||||
this.outsideClickHandler = null |
||||
} |
||||
} |
||||
|
||||
MenuDroppoComponent.prototype.componentDidMount = function () { |
||||
if (this && document.body) { |
||||
this.globalClickHandler = this.globalClickOccurred.bind(this) |
||||
document.body.addEventListener('click', this.globalClickHandler) |
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
var container = findDOMNode(this) |
||||
this.container = container |
||||
} |
||||
} |
||||
|
||||
MenuDroppoComponent.prototype.componentWillUnmount = function () { |
||||
if (this && document.body) { |
||||
document.body.removeEventListener('click', this.globalClickHandler) |
||||
} |
||||
} |
||||
|
||||
MenuDroppoComponent.prototype.globalClickOccurred = function (event) { |
||||
const target = event.target |
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
const container = findDOMNode(this) |
||||
|
||||
if (target !== container && |
||||
!isDescendant(this.container, event.target) && |
||||
this.outsideClickHandler) { |
||||
this.outsideClickHandler(event) |
||||
} |
||||
} |
||||
|
||||
function isDescendant (parent, child) { |
||||
var node = child.parentNode |
||||
while (node !== null) { |
||||
if (node === parent) { |
||||
return true |
||||
} |
||||
node = node.parentNode |
||||
} |
||||
|
||||
return false |
||||
} |
@ -0,0 +1,74 @@ |
||||
const inherits = require('util').inherits |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const Identicon = require('./identicon') |
||||
|
||||
module.exports = AccountPanel |
||||
|
||||
|
||||
inherits(AccountPanel, Component) |
||||
function AccountPanel () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
AccountPanel.prototype.render = function () { |
||||
var props = this.props |
||||
var picOrder = props.picOrder || 'left' |
||||
const { imageSeed } = props |
||||
|
||||
return ( |
||||
|
||||
h('.identity-panel.flex-row.flex-left', { |
||||
style: { |
||||
cursor: props.onClick ? 'pointer' : undefined, |
||||
}, |
||||
onClick: props.onClick, |
||||
}, [ |
||||
|
||||
this.genIcon(imageSeed, picOrder), |
||||
|
||||
h('div.flex-column.flex-justify-center', { |
||||
style: { |
||||
lineHeight: '15px', |
||||
order: 2, |
||||
display: 'flex', |
||||
alignItems: picOrder === 'left' ? 'flex-begin' : 'flex-end', |
||||
}, |
||||
}, this.props.children), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
AccountPanel.prototype.genIcon = function (seed, picOrder) { |
||||
const props = this.props |
||||
|
||||
// When there is no seed value, this is a contract creation.
|
||||
// We then show the contract icon.
|
||||
if (!seed) { |
||||
return h('.identicon-wrapper.flex-column.select-none', { |
||||
style: { |
||||
order: picOrder === 'left' ? 1 : 3, |
||||
}, |
||||
}, [ |
||||
h('i.fa.fa-file-text-o.fa-lg', { |
||||
style: { |
||||
fontSize: '42px', |
||||
transform: 'translate(0px, -16px)', |
||||
}, |
||||
}), |
||||
]) |
||||
} |
||||
|
||||
// If there was a seed, we return an identicon for that address.
|
||||
return h('.identicon-wrapper.flex-column.select-none', { |
||||
style: { |
||||
order: picOrder === 'left' ? 1 : 3, |
||||
}, |
||||
}, [ |
||||
h(Identicon, { |
||||
address: seed, |
||||
imageify: props.imageifyIdenticons, |
||||
}), |
||||
]) |
||||
} |
||||
|
@ -0,0 +1,129 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
|
||||
module.exports = Network |
||||
|
||||
inherits(Network, Component) |
||||
|
||||
function Network () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
Network.prototype.render = function () { |
||||
const props = this.props |
||||
const networkNumber = props.network |
||||
let providerName |
||||
try { |
||||
providerName = props.provider.type |
||||
} catch (e) { |
||||
providerName = null |
||||
} |
||||
let iconName, hoverText |
||||
|
||||
if (networkNumber === 'loading') { |
||||
return h('span.pointer', { |
||||
style: { |
||||
display: 'flex', |
||||
alignItems: 'center', |
||||
flexDirection: 'row', |
||||
}, |
||||
onClick: (event) => this.props.onClick(event), |
||||
}, [ |
||||
h('img', { |
||||
title: 'Attempting to connect to blockchain.', |
||||
style: { |
||||
width: '27px', |
||||
}, |
||||
src: 'images/loading.svg', |
||||
}), |
||||
h('i.fa.fa-caret-down'), |
||||
]) |
||||
} else if (providerName === 'mainnet') { |
||||
hoverText = 'Main Ethereum Network' |
||||
iconName = 'ethereum-network' |
||||
} else if (providerName === 'ropsten') { |
||||
hoverText = 'Ropsten Test Network' |
||||
iconName = 'ropsten-test-network' |
||||
} else if (parseInt(networkNumber) === 3) { |
||||
hoverText = 'Ropsten Test Network' |
||||
iconName = 'ropsten-test-network' |
||||
} else if (providerName === 'kovan') { |
||||
hoverText = 'Kovan Test Network' |
||||
iconName = 'kovan-test-network' |
||||
} else if (providerName === 'rinkeby') { |
||||
hoverText = 'Rinkeby Test Network' |
||||
iconName = 'rinkeby-test-network' |
||||
} else { |
||||
hoverText = 'Unknown Private Network' |
||||
iconName = 'unknown-private-network' |
||||
} |
||||
|
||||
return ( |
||||
h('#network_component.pointer', { |
||||
title: hoverText, |
||||
onClick: (event) => this.props.onClick(event), |
||||
}, [ |
||||
(function () { |
||||
switch (iconName) { |
||||
case 'ethereum-network': |
||||
return h('.network-indicator', [ |
||||
h('.menu-icon.diamond'), |
||||
h('.network-name', { |
||||
style: { |
||||
color: '#039396', |
||||
}}, |
||||
'Main Network'), |
||||
h('i.fa.fa-caret-down.fa-lg'), |
||||
]) |
||||
case 'ropsten-test-network': |
||||
return h('.network-indicator', [ |
||||
h('.menu-icon.red-dot'), |
||||
h('.network-name', { |
||||
style: { |
||||
color: '#ff6666', |
||||
}}, |
||||
'Ropsten Test Net'), |
||||
h('i.fa.fa-caret-down.fa-lg'), |
||||
]) |
||||
case 'kovan-test-network': |
||||
return h('.network-indicator', [ |
||||
h('.menu-icon.hollow-diamond'), |
||||
h('.network-name', { |
||||
style: { |
||||
color: '#690496', |
||||
}}, |
||||
'Kovan Test Net'), |
||||
h('i.fa.fa-caret-down.fa-lg'), |
||||
]) |
||||
case 'rinkeby-test-network': |
||||
return h('.network-indicator', [ |
||||
h('.menu-icon.golden-square'), |
||||
h('.network-name', { |
||||
style: { |
||||
color: '#e7a218', |
||||
}}, |
||||
'Rinkeby Test Net'), |
||||
h('i.fa.fa-caret-down.fa-lg'), |
||||
]) |
||||
default: |
||||
return h('.network-indicator', [ |
||||
h('i.fa.fa-question-circle.fa-lg', { |
||||
style: { |
||||
margin: '10px', |
||||
color: 'rgb(125, 128, 130)', |
||||
}, |
||||
}), |
||||
|
||||
h('.network-name', { |
||||
style: { |
||||
color: '#AEAEAE', |
||||
}}, |
||||
'Private Network'), |
||||
h('i.fa.fa-caret-down.fa-lg'), |
||||
]) |
||||
} |
||||
})(), |
||||
]) |
||||
) |
||||
} |
@ -0,0 +1,132 @@ |
||||
const inherits = require('util').inherits |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const ReactMarkdown = require('react-markdown') |
||||
const linker = require('extension-link-enabler') |
||||
const findDOMNode = require('react-dom').findDOMNode |
||||
|
||||
module.exports = Notice |
||||
|
||||
inherits(Notice, Component) |
||||
function Notice () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
Notice.prototype.render = function () { |
||||
const { notice, onConfirm } = this.props |
||||
const { title, date, body } = notice |
||||
const state = this.state || { disclaimerDisabled: true } |
||||
const disabled = state.disclaimerDisabled |
||||
|
||||
return ( |
||||
h('.flex-column.flex-center.flex-grow', { |
||||
style: { |
||||
width: '100%', |
||||
}, |
||||
}, [ |
||||
h('h3.flex-center.text-transform-uppercase.terms-header', { |
||||
style: { |
||||
background: '#EBEBEB', |
||||
color: '#AEAEAE', |
||||
width: '100%', |
||||
fontSize: '20px', |
||||
textAlign: 'center', |
||||
padding: 6, |
||||
}, |
||||
}, [ |
||||
title, |
||||
]), |
||||
|
||||
h('h5.flex-center.text-transform-uppercase.terms-header', { |
||||
style: { |
||||
background: '#EBEBEB', |
||||
color: '#AEAEAE', |
||||
marginBottom: 24, |
||||
width: '100%', |
||||
fontSize: '20px', |
||||
textAlign: 'center', |
||||
padding: 6, |
||||
}, |
||||
}, [ |
||||
date, |
||||
]), |
||||
|
||||
h('style', ` |
||||
|
||||
.markdown { |
||||
overflow-x: hidden; |
||||
} |
||||
|
||||
.markdown h1, .markdown h2, .markdown h3 { |
||||
margin: 10px 0; |
||||
font-weight: bold; |
||||
} |
||||
|
||||
.markdown strong { |
||||
font-weight: bold; |
||||
} |
||||
.markdown em { |
||||
font-style: italic; |
||||
} |
||||
|
||||
.markdown p { |
||||
margin: 10px 0; |
||||
} |
||||
|
||||
.markdown a { |
||||
color: #df6b0e; |
||||
} |
||||
|
||||
`),
|
||||
|
||||
h('div.markdown', { |
||||
onScroll: (e) => { |
||||
var object = e.currentTarget |
||||
if (object.offsetHeight + object.scrollTop + 100 >= object.scrollHeight) { |
||||
this.setState({disclaimerDisabled: false}) |
||||
} |
||||
}, |
||||
style: { |
||||
background: 'rgb(235, 235, 235)', |
||||
height: '310px', |
||||
padding: '6px', |
||||
width: '90%', |
||||
overflowY: 'scroll', |
||||
scroll: 'auto', |
||||
}, |
||||
}, [ |
||||
h(ReactMarkdown, { |
||||
className: 'notice-box', |
||||
source: body, |
||||
skipHtml: true, |
||||
}), |
||||
]), |
||||
|
||||
h('button', { |
||||
disabled, |
||||
onClick: () => { |
||||
this.setState({disclaimerDisabled: true}) |
||||
onConfirm() |
||||
}, |
||||
style: { |
||||
marginTop: '18px', |
||||
}, |
||||
}, 'Accept'), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
Notice.prototype.componentDidMount = function () { |
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
var node = findDOMNode(this) |
||||
linker.setupListener(node) |
||||
if (document.getElementsByClassName('notice-box')[0].clientHeight < 310) { |
||||
this.setState({disclaimerDisabled: false}) |
||||
} |
||||
} |
||||
|
||||
Notice.prototype.componentWillUnmount = function () { |
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
var node = findDOMNode(this) |
||||
linker.teardownListener(node) |
||||
} |
@ -0,0 +1,50 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
|
||||
const AccountPanel = require('./account-panel') |
||||
|
||||
module.exports = PendingMsgDetails |
||||
|
||||
inherits(PendingMsgDetails, Component) |
||||
function PendingMsgDetails () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
PendingMsgDetails.prototype.render = function () { |
||||
var state = this.props |
||||
var msgData = state.txData |
||||
|
||||
var msgParams = msgData.msgParams || {} |
||||
var address = msgParams.from || state.selectedAddress |
||||
var identity = state.identities[address] || { address: address } |
||||
var account = state.accounts[address] || { address: address } |
||||
|
||||
return ( |
||||
h('div', { |
||||
key: msgData.id, |
||||
style: { |
||||
margin: '10px 20px', |
||||
}, |
||||
}, [ |
||||
|
||||
// account that will sign
|
||||
h(AccountPanel, { |
||||
showFullAddress: true, |
||||
identity: identity, |
||||
account: account, |
||||
imageifyIdenticons: state.imageifyIdenticons, |
||||
}), |
||||
|
||||
// message data
|
||||
h('.tx-data.flex-column.flex-justify-center.flex-grow.select-none', [ |
||||
h('.flex-column.flex-space-between', [ |
||||
h('label.font-small', 'MESSAGE'), |
||||
h('span.font-small', msgParams.data), |
||||
]), |
||||
]), |
||||
|
||||
]) |
||||
) |
||||
} |
||||
|
@ -0,0 +1,70 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
const PendingTxDetails = require('./pending-msg-details') |
||||
|
||||
module.exports = PendingMsg |
||||
|
||||
inherits(PendingMsg, Component) |
||||
function PendingMsg () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
PendingMsg.prototype.render = function () { |
||||
var state = this.props |
||||
var msgData = state.txData |
||||
|
||||
return ( |
||||
|
||||
h('div', { |
||||
key: msgData.id, |
||||
style: { |
||||
maxWidth: '350px', |
||||
}, |
||||
}, [ |
||||
|
||||
// header
|
||||
h('h3', { |
||||
style: { |
||||
fontWeight: 'bold', |
||||
textAlign: 'center', |
||||
}, |
||||
}, 'Sign Message'), |
||||
|
||||
h('.error', { |
||||
style: { |
||||
margin: '10px', |
||||
}, |
||||
}, [ |
||||
`Signing this message can have
|
||||
dangerous side effects. Only sign messages from |
||||
sites you fully trust with your entire account. |
||||
This dangerous method will be removed in a future version. `,
|
||||
h('a', { |
||||
href: 'https://medium.com/metamask/the-new-secure-way-to-sign-data-in-your-browser-6af9dd2a1527', |
||||
style: { color: 'rgb(247, 134, 28)' }, |
||||
onClick: (event) => { |
||||
event.preventDefault() |
||||
const url = 'https://medium.com/metamask/the-new-secure-way-to-sign-data-in-your-browser-6af9dd2a1527' |
||||
global.platform.openWindow({ url }) |
||||
}, |
||||
}, 'Read more here.'), |
||||
]), |
||||
|
||||
// message details
|
||||
h(PendingTxDetails, state), |
||||
|
||||
// sign + cancel
|
||||
h('.flex-row.flex-space-around', [ |
||||
h('button', { |
||||
onClick: state.cancelMessage, |
||||
}, 'Cancel'), |
||||
h('button', { |
||||
onClick: state.signMessage, |
||||
}, 'Sign'), |
||||
]), |
||||
]) |
||||
|
||||
) |
||||
} |
||||
|
@ -0,0 +1,60 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
|
||||
const AccountPanel = require('./account-panel') |
||||
const BinaryRenderer = require('./binary-renderer') |
||||
|
||||
module.exports = PendingMsgDetails |
||||
|
||||
inherits(PendingMsgDetails, Component) |
||||
function PendingMsgDetails () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
PendingMsgDetails.prototype.render = function () { |
||||
var state = this.props |
||||
var msgData = state.txData |
||||
|
||||
var msgParams = msgData.msgParams || {} |
||||
var address = msgParams.from || state.selectedAddress |
||||
var identity = state.identities[address] || { address: address } |
||||
var account = state.accounts[address] || { address: address } |
||||
|
||||
var { data } = msgParams |
||||
|
||||
return ( |
||||
h('div', { |
||||
key: msgData.id, |
||||
style: { |
||||
margin: '10px 20px', |
||||
}, |
||||
}, [ |
||||
|
||||
// account that will sign
|
||||
h(AccountPanel, { |
||||
showFullAddress: true, |
||||
identity: identity, |
||||
account: account, |
||||
imageifyIdenticons: state.imageifyIdenticons, |
||||
}), |
||||
|
||||
// message data
|
||||
h('div', { |
||||
style: { |
||||
height: '260px', |
||||
}, |
||||
}, [ |
||||
h('label.font-small', { style: { display: 'block' } }, 'MESSAGE'), |
||||
h(BinaryRenderer, { |
||||
value: data, |
||||
style: { |
||||
height: '215px', |
||||
}, |
||||
}), |
||||
]), |
||||
|
||||
]) |
||||
) |
||||
} |
||||
|
@ -0,0 +1,47 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
const PendingTxDetails = require('./pending-personal-msg-details') |
||||
|
||||
module.exports = PendingMsg |
||||
|
||||
inherits(PendingMsg, Component) |
||||
function PendingMsg () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
PendingMsg.prototype.render = function () { |
||||
var state = this.props |
||||
var msgData = state.txData |
||||
|
||||
return ( |
||||
|
||||
h('div', { |
||||
key: msgData.id, |
||||
}, [ |
||||
|
||||
// header
|
||||
h('h3', { |
||||
style: { |
||||
fontWeight: 'bold', |
||||
textAlign: 'center', |
||||
}, |
||||
}, 'Sign Message'), |
||||
|
||||
// message details
|
||||
h(PendingTxDetails, state), |
||||
|
||||
// sign + cancel
|
||||
h('.flex-row.flex-space-around', [ |
||||
h('button', { |
||||
onClick: state.cancelPersonalMessage, |
||||
}, 'Cancel'), |
||||
h('button', { |
||||
onClick: state.signPersonalMessage, |
||||
}, 'Sign'), |
||||
]), |
||||
]) |
||||
|
||||
) |
||||
} |
||||
|
@ -0,0 +1,510 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
const actions = require('../../../ui/app/actions') |
||||
const clone = require('clone') |
||||
|
||||
const ethUtil = require('ethereumjs-util') |
||||
const BN = ethUtil.BN |
||||
const hexToBn = require('../../../app/scripts/lib/hex-to-bn') |
||||
const util = require('../util') |
||||
const MiniAccountPanel = require('./mini-account-panel') |
||||
const Copyable = require('./copyable') |
||||
const EthBalance = require('./eth-balance') |
||||
const addressSummary = util.addressSummary |
||||
const nameForAddress = require('../../lib/contract-namer') |
||||
const BNInput = require('./bn-as-decimal-input') |
||||
|
||||
// corresponds with 0.1 GWEI
|
||||
const MIN_GAS_PRICE_BN = new BN('100000000') |
||||
const MIN_GAS_LIMIT_BN = new BN('21000') |
||||
|
||||
module.exports = PendingTx |
||||
inherits(PendingTx, Component) |
||||
function PendingTx () { |
||||
Component.call(this) |
||||
this.state = { |
||||
valid: true, |
||||
txData: null, |
||||
submitting: false, |
||||
} |
||||
} |
||||
|
||||
PendingTx.prototype.render = function () { |
||||
const props = this.props |
||||
const { currentCurrency, blockGasLimit } = props |
||||
|
||||
const conversionRate = props.conversionRate |
||||
const txMeta = this.gatherTxMeta() |
||||
const txParams = txMeta.txParams || {} |
||||
|
||||
// Allow retry txs
|
||||
const { lastGasPrice } = txMeta |
||||
let forceGasMin |
||||
if (lastGasPrice) { |
||||
const stripped = ethUtil.stripHexPrefix(lastGasPrice) |
||||
const lastGas = new BN(stripped, 16) |
||||
const priceBump = lastGas.divn('10') |
||||
forceGasMin = lastGas.add(priceBump) |
||||
} |
||||
|
||||
// Account Details
|
||||
const address = txParams.from || props.selectedAddress |
||||
const identity = props.identities[address] || { address: address } |
||||
const account = props.accounts[address] |
||||
const balance = account ? account.balance : '0x0' |
||||
|
||||
// recipient check
|
||||
const isValidAddress = !txParams.to || util.isValidAddress(txParams.to) |
||||
|
||||
// Gas
|
||||
const gas = txParams.gas |
||||
const gasBn = hexToBn(gas) |
||||
const gasLimit = new BN(parseInt(blockGasLimit)) |
||||
const safeGasLimitBN = this.bnMultiplyByFraction(gasLimit, 19, 20) |
||||
const saferGasLimitBN = this.bnMultiplyByFraction(gasLimit, 18, 20) |
||||
const safeGasLimit = safeGasLimitBN.toString(10) |
||||
|
||||
// Gas Price
|
||||
const gasPrice = txParams.gasPrice || MIN_GAS_PRICE_BN.toString(16) |
||||
const gasPriceBn = hexToBn(gasPrice) |
||||
|
||||
const txFeeBn = gasBn.mul(gasPriceBn) |
||||
const valueBn = hexToBn(txParams.value) |
||||
const maxCost = txFeeBn.add(valueBn) |
||||
|
||||
const dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0 |
||||
|
||||
const balanceBn = hexToBn(balance) |
||||
const insufficientBalance = balanceBn.lt(maxCost) |
||||
const dangerousGasLimit = gasBn.gte(saferGasLimitBN) |
||||
const gasLimitSpecified = txMeta.gasLimitSpecified |
||||
const buyDisabled = insufficientBalance || !this.state.valid || !isValidAddress || this.state.submitting |
||||
const showRejectAll = props.unconfTxListLength > 1 |
||||
|
||||
this.inputs = [] |
||||
|
||||
return ( |
||||
|
||||
h('div', { |
||||
key: txMeta.id, |
||||
}, [ |
||||
|
||||
h('form#pending-tx-form', { |
||||
onSubmit: this.onSubmit.bind(this), |
||||
|
||||
}, [ |
||||
|
||||
// tx info
|
||||
h('div', [ |
||||
|
||||
h('.flex-row.flex-center', { |
||||
style: { |
||||
maxWidth: '100%', |
||||
}, |
||||
}, [ |
||||
|
||||
h(MiniAccountPanel, { |
||||
imageSeed: address, |
||||
picOrder: 'right', |
||||
}, [ |
||||
h('span.font-small', { |
||||
style: { |
||||
fontFamily: 'Montserrat Bold, Montserrat, sans-serif', |
||||
}, |
||||
}, identity.name), |
||||
|
||||
h(Copyable, { |
||||
value: ethUtil.toChecksumAddress(address), |
||||
}, [ |
||||
h('span.font-small', { |
||||
style: { |
||||
fontFamily: 'Montserrat Light, Montserrat, sans-serif', |
||||
}, |
||||
}, addressSummary(address, 6, 4, false)), |
||||
]), |
||||
|
||||
h('span.font-small', { |
||||
style: { |
||||
fontFamily: 'Montserrat Light, Montserrat, sans-serif', |
||||
}, |
||||
}, [ |
||||
h(EthBalance, { |
||||
value: balance, |
||||
conversionRate, |
||||
currentCurrency, |
||||
inline: true, |
||||
labelColor: '#F7861C', |
||||
}), |
||||
]), |
||||
]), |
||||
|
||||
forwardCarrat(), |
||||
|
||||
this.miniAccountPanelForRecipient(), |
||||
]), |
||||
|
||||
h('style', ` |
||||
.table-box { |
||||
margin: 7px 0px 0px 0px; |
||||
width: 100%; |
||||
} |
||||
.table-box .row { |
||||
margin: 0px; |
||||
background: rgb(236,236,236); |
||||
display: flex; |
||||
justify-content: space-between; |
||||
font-family: Montserrat Light, sans-serif; |
||||
font-size: 13px; |
||||
padding: 5px 25px; |
||||
} |
||||
.table-box .row .value { |
||||
font-family: Montserrat Regular; |
||||
} |
||||
`),
|
||||
|
||||
h('.table-box', [ |
||||
|
||||
// Ether Value
|
||||
// Currently not customizable, but easily modified
|
||||
// in the way that gas and gasLimit currently are.
|
||||
h('.row', [ |
||||
h('.cell.label', 'Amount'), |
||||
h(EthBalance, { value: txParams.value, currentCurrency, conversionRate }), |
||||
]), |
||||
|
||||
// Gas Limit (customizable)
|
||||
h('.cell.row', [ |
||||
h('.cell.label', 'Gas Limit'), |
||||
h('.cell.value', { |
||||
}, [ |
||||
h(BNInput, { |
||||
name: 'Gas Limit', |
||||
value: gasBn, |
||||
precision: 0, |
||||
scale: 0, |
||||
// The hard lower limit for gas.
|
||||
min: MIN_GAS_LIMIT_BN, |
||||
max: safeGasLimit, |
||||
suffix: 'UNITS', |
||||
style: { |
||||
position: 'relative', |
||||
top: '5px', |
||||
}, |
||||
onChange: this.gasLimitChanged.bind(this), |
||||
|
||||
ref: (hexInput) => { this.inputs.push(hexInput) }, |
||||
}), |
||||
]), |
||||
]), |
||||
|
||||
// Gas Price (customizable)
|
||||
h('.cell.row', [ |
||||
h('.cell.label', 'Gas Price'), |
||||
h('.cell.value', { |
||||
}, [ |
||||
h(BNInput, { |
||||
name: 'Gas Price', |
||||
value: gasPriceBn, |
||||
precision: 9, |
||||
scale: 9, |
||||
suffix: 'GWEI', |
||||
min: forceGasMin || MIN_GAS_PRICE_BN, |
||||
style: { |
||||
position: 'relative', |
||||
top: '5px', |
||||
}, |
||||
onChange: this.gasPriceChanged.bind(this), |
||||
ref: (hexInput) => { this.inputs.push(hexInput) }, |
||||
}), |
||||
]), |
||||
]), |
||||
|
||||
// Max Transaction Fee (calculated)
|
||||
h('.cell.row', [ |
||||
h('.cell.label', 'Max Transaction Fee'), |
||||
h(EthBalance, { value: txFeeBn.toString(16), currentCurrency, conversionRate }), |
||||
]), |
||||
|
||||
h('.cell.row', { |
||||
style: { |
||||
fontFamily: 'Montserrat Regular', |
||||
background: 'white', |
||||
padding: '10px 25px', |
||||
}, |
||||
}, [ |
||||
h('.cell.label', 'Max Total'), |
||||
h('.cell.value', { |
||||
style: { |
||||
display: 'flex', |
||||
alignItems: 'center', |
||||
}, |
||||
}, [ |
||||
h(EthBalance, { |
||||
value: maxCost.toString(16), |
||||
currentCurrency, |
||||
conversionRate, |
||||
inline: true, |
||||
labelColor: 'black', |
||||
fontSize: '16px', |
||||
}), |
||||
]), |
||||
]), |
||||
|
||||
// Data size row:
|
||||
h('.cell.row', { |
||||
style: { |
||||
background: '#f7f7f7', |
||||
paddingBottom: '0px', |
||||
}, |
||||
}, [ |
||||
h('.cell.label'), |
||||
h('.cell.value', { |
||||
style: { |
||||
fontFamily: 'Montserrat Light', |
||||
fontSize: '11px', |
||||
}, |
||||
}, `Data included: ${dataLength} bytes`), |
||||
]), |
||||
]), // End of Table
|
||||
|
||||
]), |
||||
|
||||
h('style', ` |
||||
.conf-buttons button { |
||||
margin-left: 10px; |
||||
text-transform: uppercase; |
||||
} |
||||
`),
|
||||
h('.cell.row', { |
||||
style: { |
||||
textAlign: 'center', |
||||
}, |
||||
}, [ |
||||
txMeta.simulationFails ? |
||||
h('.error', { |
||||
style: { |
||||
fontSize: '0.9em', |
||||
}, |
||||
}, 'Transaction Error. Exception thrown in contract code.') |
||||
: null, |
||||
|
||||
!isValidAddress ? |
||||
h('.error', { |
||||
style: { |
||||
fontSize: '0.9em', |
||||
}, |
||||
}, 'Recipient address is invalid. Sending this transaction will result in a loss of ETH.') |
||||
: null, |
||||
|
||||
insufficientBalance ? |
||||
h('span.error', { |
||||
style: { |
||||
fontSize: '0.9em', |
||||
}, |
||||
}, 'Insufficient balance for transaction') |
||||
: null, |
||||
|
||||
(dangerousGasLimit && !gasLimitSpecified) ? |
||||
h('span.error', { |
||||
style: { |
||||
fontSize: '0.9em', |
||||
}, |
||||
}, 'Gas limit set dangerously high. Approving this transaction is likely to fail.') |
||||
: null, |
||||
]), |
||||
|
||||
|
||||
// send + cancel
|
||||
h('.flex-row.flex-space-around.conf-buttons', { |
||||
style: { |
||||
display: 'flex', |
||||
justifyContent: 'flex-end', |
||||
margin: '14px 25px', |
||||
}, |
||||
}, [ |
||||
h('button', { |
||||
onClick: (event) => { |
||||
this.resetGasFields() |
||||
event.preventDefault() |
||||
}, |
||||
}, 'Reset'), |
||||
|
||||
// Accept Button or Buy Button
|
||||
insufficientBalance ? h('button.btn-green', { onClick: props.buyEth }, 'Buy Ether') : |
||||
h('input.confirm.btn-green', { |
||||
type: 'submit', |
||||
value: 'SUBMIT', |
||||
style: { marginLeft: '10px' }, |
||||
disabled: buyDisabled, |
||||
}), |
||||
|
||||
h('button.cancel.btn-red', { |
||||
onClick: props.cancelTransaction, |
||||
}, 'Reject'), |
||||
]), |
||||
showRejectAll ? h('.flex-row.flex-space-around.conf-buttons', { |
||||
style: { |
||||
display: 'flex', |
||||
justifyContent: 'flex-end', |
||||
margin: '14px 25px', |
||||
}, |
||||
}, [ |
||||
h('button.cancel.btn-red', { |
||||
onClick: props.cancelAllTransactions, |
||||
}, 'Reject All'), |
||||
]) : null, |
||||
]), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
PendingTx.prototype.miniAccountPanelForRecipient = function () { |
||||
const props = this.props |
||||
const txData = props.txData |
||||
const txParams = txData.txParams || {} |
||||
const isContractDeploy = !('to' in txParams) |
||||
|
||||
// If it's not a contract deploy, send to the account
|
||||
if (!isContractDeploy) { |
||||
return h(MiniAccountPanel, { |
||||
imageSeed: txParams.to, |
||||
picOrder: 'left', |
||||
}, [ |
||||
|
||||
h('span.font-small', { |
||||
style: { |
||||
fontFamily: 'Montserrat Bold, Montserrat, sans-serif', |
||||
}, |
||||
}, nameForAddress(txParams.to, props.identities)), |
||||
|
||||
h(Copyable, { |
||||
value: ethUtil.toChecksumAddress(txParams.to), |
||||
}, [ |
||||
h('span.font-small', { |
||||
style: { |
||||
fontFamily: 'Montserrat Light, Montserrat, sans-serif', |
||||
}, |
||||
}, addressSummary(txParams.to, 6, 4, false)), |
||||
]), |
||||
|
||||
]) |
||||
} else { |
||||
return h(MiniAccountPanel, { |
||||
picOrder: 'left', |
||||
}, [ |
||||
|
||||
h('span.font-small', { |
||||
style: { |
||||
fontFamily: 'Montserrat Bold, Montserrat, sans-serif', |
||||
}, |
||||
}, 'New Contract'), |
||||
|
||||
]) |
||||
} |
||||
} |
||||
|
||||
PendingTx.prototype.gasPriceChanged = function (newBN, valid) { |
||||
log.info(`Gas price changed to: ${newBN.toString(10)}`) |
||||
const txMeta = this.gatherTxMeta() |
||||
txMeta.txParams.gasPrice = '0x' + newBN.toString('hex') |
||||
this.setState({ |
||||
txData: clone(txMeta), |
||||
valid, |
||||
}) |
||||
} |
||||
|
||||
PendingTx.prototype.gasLimitChanged = function (newBN, valid) { |
||||
log.info(`Gas limit changed to ${newBN.toString(10)}`) |
||||
const txMeta = this.gatherTxMeta() |
||||
txMeta.txParams.gas = '0x' + newBN.toString('hex') |
||||
this.setState({ |
||||
txData: clone(txMeta), |
||||
valid, |
||||
}) |
||||
} |
||||
|
||||
PendingTx.prototype.resetGasFields = function () { |
||||
log.debug(`pending-tx resetGasFields`) |
||||
|
||||
this.inputs.forEach((hexInput) => { |
||||
if (hexInput) { |
||||
hexInput.setValid() |
||||
} |
||||
}) |
||||
|
||||
this.setState({ |
||||
txData: null, |
||||
valid: true, |
||||
}) |
||||
} |
||||
|
||||
PendingTx.prototype.onSubmit = function (event) { |
||||
event.preventDefault() |
||||
const txMeta = this.gatherTxMeta() |
||||
const valid = this.checkValidity() |
||||
this.setState({ valid, submitting: true }) |
||||
if (valid && this.verifyGasParams()) { |
||||
this.props.sendTransaction(txMeta, event) |
||||
} else { |
||||
this.props.dispatch(actions.displayWarning('Invalid Gas Parameters')) |
||||
this.setState({ submitting: false }) |
||||
} |
||||
} |
||||
|
||||
PendingTx.prototype.checkValidity = function () { |
||||
const form = this.getFormEl() |
||||
const valid = form.checkValidity() |
||||
return valid |
||||
} |
||||
|
||||
PendingTx.prototype.getFormEl = function () { |
||||
const form = document.querySelector('form#pending-tx-form') |
||||
// Stub out form for unit tests:
|
||||
if (!form) { |
||||
return { checkValidity () { return true } } |
||||
} |
||||
return form |
||||
} |
||||
|
||||
// After a customizable state value has been updated,
|
||||
PendingTx.prototype.gatherTxMeta = function () { |
||||
log.debug(`pending-tx gatherTxMeta`) |
||||
const props = this.props |
||||
const state = this.state |
||||
const txData = clone(state.txData) || clone(props.txData) |
||||
|
||||
log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`) |
||||
return txData |
||||
} |
||||
|
||||
PendingTx.prototype.verifyGasParams = function () { |
||||
// We call this in case the gas has not been modified at all
|
||||
if (!this.state) { return true } |
||||
return ( |
||||
this._notZeroOrEmptyString(this.state.gas) && |
||||
this._notZeroOrEmptyString(this.state.gasPrice) |
||||
) |
||||
} |
||||
|
||||
PendingTx.prototype._notZeroOrEmptyString = function (obj) { |
||||
return obj !== '' && obj !== '0x0' |
||||
} |
||||
|
||||
PendingTx.prototype.bnMultiplyByFraction = function (targetBN, numerator, denominator) { |
||||
const numBN = new BN(numerator) |
||||
const denomBN = new BN(denominator) |
||||
return targetBN.mul(numBN).div(denomBN) |
||||
} |
||||
|
||||
function forwardCarrat () { |
||||
return ( |
||||
h('img', { |
||||
src: 'images/forward-carrat.svg', |
||||
style: { |
||||
padding: '5px 6px 0px 10px', |
||||
height: '37px', |
||||
}, |
||||
}) |
||||
) |
||||
} |
@ -0,0 +1,59 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
|
||||
const AccountPanel = require('./account-panel') |
||||
const TypedMessageRenderer = require('./typed-message-renderer') |
||||
|
||||
module.exports = PendingMsgDetails |
||||
|
||||
inherits(PendingMsgDetails, Component) |
||||
function PendingMsgDetails () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
PendingMsgDetails.prototype.render = function () { |
||||
var state = this.props |
||||
var msgData = state.txData |
||||
|
||||
var msgParams = msgData.msgParams || {} |
||||
var address = msgParams.from || state.selectedAddress |
||||
var identity = state.identities[address] || { address: address } |
||||
var account = state.accounts[address] || { address: address } |
||||
|
||||
var { data } = msgParams |
||||
|
||||
return ( |
||||
h('div', { |
||||
key: msgData.id, |
||||
style: { |
||||
margin: '10px 20px', |
||||
}, |
||||
}, [ |
||||
|
||||
// account that will sign
|
||||
h(AccountPanel, { |
||||
showFullAddress: true, |
||||
identity: identity, |
||||
account: account, |
||||
imageifyIdenticons: state.imageifyIdenticons, |
||||
}), |
||||
|
||||
// message data
|
||||
h('div', { |
||||
style: { |
||||
height: '260px', |
||||
}, |
||||
}, [ |
||||
h('label.font-small', { style: { display: 'block' } }, 'YOU ARE SIGNING'), |
||||
h(TypedMessageRenderer, { |
||||
value: data, |
||||
style: { |
||||
height: '215px', |
||||
}, |
||||
}), |
||||
]), |
||||
|
||||
]) |
||||
) |
||||
} |
@ -0,0 +1,46 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
const PendingTxDetails = require('./pending-typed-msg-details') |
||||
|
||||
module.exports = PendingMsg |
||||
|
||||
inherits(PendingMsg, Component) |
||||
function PendingMsg () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
PendingMsg.prototype.render = function () { |
||||
var state = this.props |
||||
var msgData = state.txData |
||||
|
||||
return ( |
||||
|
||||
h('div', { |
||||
key: msgData.id, |
||||
}, [ |
||||
|
||||
// header
|
||||
h('h3', { |
||||
style: { |
||||
fontWeight: 'bold', |
||||
textAlign: 'center', |
||||
}, |
||||
}, 'Sign Message'), |
||||
|
||||
// message details
|
||||
h(PendingTxDetails, state), |
||||
|
||||
// sign + cancel
|
||||
h('.flex-row.flex-space-around', [ |
||||
h('button', { |
||||
onClick: state.cancelTypedMessage, |
||||
}, 'Cancel'), |
||||
h('button', { |
||||
onClick: state.signTypedMessage, |
||||
}, 'Sign'), |
||||
]), |
||||
]) |
||||
|
||||
) |
||||
} |
@ -0,0 +1,80 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const qrCode = require('qrcode-npm').qrcode |
||||
const inherits = require('util').inherits |
||||
const connect = require('react-redux').connect |
||||
const isHexPrefixed = require('ethereumjs-util').isHexPrefixed |
||||
const CopyButton = require('./copyButton') |
||||
|
||||
module.exports = connect(mapStateToProps)(QrCodeView) |
||||
|
||||
function mapStateToProps (state) { |
||||
return { |
||||
Qr: state.appState.Qr, |
||||
buyView: state.appState.buyView, |
||||
warning: state.appState.warning, |
||||
} |
||||
} |
||||
|
||||
inherits(QrCodeView, Component) |
||||
|
||||
function QrCodeView () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
QrCodeView.prototype.render = function () { |
||||
const props = this.props |
||||
const Qr = props.Qr |
||||
console.log(`QrCodeView Qr`, Qr); |
||||
const address = `${isHexPrefixed(Qr.data) ? 'ethereum:' : ''}${Qr.data}` |
||||
const qrImage = qrCode(4, 'M') |
||||
qrImage.addData(address) |
||||
qrImage.make() |
||||
return h('.main-container.flex-column', { |
||||
key: 'qr', |
||||
style: { |
||||
justifyContent: 'center', |
||||
paddingBottom: '45px', |
||||
paddingLeft: '45px', |
||||
paddingRight: '45px', |
||||
alignItems: 'center', |
||||
}, |
||||
}, [ |
||||
Array.isArray(Qr.message) ? h('.message-container', this.renderMultiMessage()) : h('.qr-header', Qr.message), |
||||
|
||||
this.props.warning ? this.props.warning && h('span.error.flex-center', { |
||||
style: { |
||||
textAlign: 'center', |
||||
width: '229px', |
||||
height: '82px', |
||||
}, |
||||
}, |
||||
this.props.warning) : null, |
||||
|
||||
h('#qr-container.flex-column', { |
||||
style: { |
||||
marginTop: '25px', |
||||
marginBottom: '15px', |
||||
}, |
||||
dangerouslySetInnerHTML: { |
||||
__html: qrImage.createTableTag(4), |
||||
}, |
||||
}), |
||||
h('.flex-row', [ |
||||
h('h3.ellip-address', { |
||||
style: { |
||||
width: '247px', |
||||
}, |
||||
}, Qr.data), |
||||
h(CopyButton, { |
||||
value: Qr.data, |
||||
}), |
||||
]), |
||||
]) |
||||
} |
||||
|
||||
QrCodeView.prototype.renderMultiMessage = function () { |
||||
var Qr = this.props.Qr |
||||
var multiMessage = Qr.message.map((message) => h('.qr-message', message)) |
||||
return multiMessage |
||||
} |
@ -0,0 +1,58 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
|
||||
module.exports = RangeSlider |
||||
|
||||
inherits(RangeSlider, Component) |
||||
function RangeSlider () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
RangeSlider.prototype.render = function () { |
||||
const state = this.state || {} |
||||
const props = this.props |
||||
const onInput = props.onInput || function () {} |
||||
const name = props.name |
||||
const { |
||||
min = 0, |
||||
max = 100, |
||||
increment = 1, |
||||
defaultValue = 50, |
||||
mirrorInput = false, |
||||
} = this.props.options |
||||
const {container, input, range} = props.style |
||||
|
||||
return ( |
||||
h('.flex-row', { |
||||
style: container, |
||||
}, [ |
||||
h('input', { |
||||
type: 'range', |
||||
name: name, |
||||
min: min, |
||||
max: max, |
||||
step: increment, |
||||
style: range, |
||||
value: state.value || defaultValue, |
||||
onChange: mirrorInput ? this.mirrorInputs.bind(this, event) : onInput, |
||||
}), |
||||
|
||||
// Mirrored input for range
|
||||
mirrorInput ? h('input.large-input', { |
||||
type: 'number', |
||||
name: `${name}Mirror`, |
||||
min: min, |
||||
max: max, |
||||
value: state.value || defaultValue, |
||||
step: increment, |
||||
style: input, |
||||
onChange: this.mirrorInputs.bind(this, event), |
||||
}) : null, |
||||
]) |
||||
) |
||||
} |
||||
|
||||
RangeSlider.prototype.mirrorInputs = function (event) { |
||||
this.setState({value: event.target.value}) |
||||
} |
@ -0,0 +1,308 @@ |
||||
const PersistentForm = require('../../lib/persistent-form') |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
const connect = require('react-redux').connect |
||||
const actions = require('../../../ui/app/actions') |
||||
const Qr = require('./qr-code') |
||||
const isValidAddress = require('../util').isValidAddress |
||||
module.exports = connect(mapStateToProps)(ShapeshiftForm) |
||||
|
||||
function mapStateToProps (state) { |
||||
return { |
||||
warning: state.appState.warning, |
||||
isSubLoading: state.appState.isSubLoading, |
||||
qrRequested: state.appState.qrRequested, |
||||
} |
||||
} |
||||
|
||||
inherits(ShapeshiftForm, PersistentForm) |
||||
|
||||
function ShapeshiftForm () { |
||||
PersistentForm.call(this) |
||||
this.persistentFormParentId = 'shapeshift-buy-form' |
||||
} |
||||
|
||||
ShapeshiftForm.prototype.render = function () { |
||||
return this.props.qrRequested ? h(Qr, {key: 'qr'}) : this.renderMain() |
||||
} |
||||
|
||||
ShapeshiftForm.prototype.renderMain = function () { |
||||
const marketinfo = this.props.buyView.formView.marketinfo |
||||
const coinOptions = this.props.buyView.formView.coinOptions |
||||
var coin = marketinfo.pair.split('_')[0].toUpperCase() |
||||
|
||||
return h('.flex-column', { |
||||
style: { |
||||
position: 'relative', |
||||
padding: '25px', |
||||
paddingTop: '5px', |
||||
width: '90%', |
||||
minHeight: '215px', |
||||
alignItems: 'center', |
||||
overflowY: 'auto', |
||||
}, |
||||
}, [ |
||||
h('.flex-row', { |
||||
style: { |
||||
justifyContent: 'center', |
||||
alignItems: 'baseline', |
||||
height: '42px', |
||||
}, |
||||
}, [ |
||||
h('img', { |
||||
src: coinOptions[coin].image, |
||||
width: '25px', |
||||
height: '25px', |
||||
style: { |
||||
marginRight: '5px', |
||||
}, |
||||
}), |
||||
|
||||
h('.input-container', { |
||||
position: 'relative', |
||||
}, [ |
||||
h('input#fromCoin.buy-inputs.ex-coins', { |
||||
type: 'text', |
||||
list: 'coinList', |
||||
autoFocus: true, |
||||
dataset: { |
||||
persistentFormId: 'input-coin', |
||||
}, |
||||
style: { |
||||
boxSizing: 'border-box', |
||||
}, |
||||
onChange: this.handleLiveInput.bind(this), |
||||
defaultValue: 'BTC', |
||||
}), |
||||
|
||||
this.renderCoinList(), |
||||
|
||||
h('i.fa.fa-pencil-square-o.edit-text', { |
||||
style: { |
||||
fontSize: '12px', |
||||
color: '#F7861C', |
||||
position: 'absolute', |
||||
}, |
||||
}), |
||||
]), |
||||
|
||||
h('.icon-control', { |
||||
style: { |
||||
position: 'relative', |
||||
}, |
||||
}, [ |
||||
// Not visible on the screen, can't see it on master.
|
||||
|
||||
// h('i.fa.fa-refresh.fa-4.orange', {
|
||||
// style: {
|
||||
// bottom: '5px',
|
||||
// left: '5px',
|
||||
// color: '#F7861C',
|
||||
// },
|
||||
// onClick: this.updateCoin.bind(this),
|
||||
// }),
|
||||
h('i.fa.fa-chevron-right.fa-4.orange', { |
||||
style: { |
||||
position: 'absolute', |
||||
bottom: '35%', |
||||
left: '0%', |
||||
color: '#F7861C', |
||||
}, |
||||
onClick: this.updateCoin.bind(this), |
||||
}), |
||||
]), |
||||
|
||||
h('#toCoin.ex-coins', marketinfo.pair.split('_')[1].toUpperCase()), |
||||
|
||||
h('img', { |
||||
src: coinOptions[marketinfo.pair.split('_')[1].toUpperCase()].image, |
||||
width: '25px', |
||||
height: '25px', |
||||
style: { |
||||
marginLeft: '5px', |
||||
}, |
||||
}), |
||||
]), |
||||
|
||||
h('.flex-column', { |
||||
style: { |
||||
marginTop: '1%', |
||||
alignItems: 'flex-start', |
||||
}, |
||||
}, [ |
||||
this.props.warning ? |
||||
this.props.warning && |
||||
h('span.error.flex-center', { |
||||
style: { |
||||
textAlign: 'center', |
||||
width: '229px', |
||||
height: '82px', |
||||
}, |
||||
}, this.props.warning) |
||||
: this.renderInfo(), |
||||
|
||||
this.renderRefundAddressForCoin(coin), |
||||
]), |
||||
|
||||
]) |
||||
} |
||||
|
||||
ShapeshiftForm.prototype.renderRefundAddressForCoin = function (coin) { |
||||
return h(this.activeToggle('.input-container'), { |
||||
style: { |
||||
marginTop: '1%', |
||||
}, |
||||
}, [ |
||||
|
||||
h('div', `${coin} Address:`), |
||||
|
||||
h('input#fromCoinAddress.buy-inputs', { |
||||
type: 'text', |
||||
placeholder: `Your ${coin} Refund Address`, |
||||
dataset: { |
||||
persistentFormId: 'refund-address', |
||||
|
||||
}, |
||||
style: { |
||||
boxSizing: 'border-box', |
||||
width: '227px', |
||||
height: '30px', |
||||
padding: ' 5px ', |
||||
}, |
||||
}), |
||||
|
||||
h('i.fa.fa-pencil-square-o.edit-text', { |
||||
style: { |
||||
fontSize: '12px', |
||||
color: '#F7861C', |
||||
position: 'absolute', |
||||
}, |
||||
}), |
||||
h('div.flex-row', { |
||||
style: { |
||||
justifyContent: 'flex-start', |
||||
}, |
||||
}, [ |
||||
h('button', { |
||||
onClick: this.shift.bind(this), |
||||
style: { |
||||
marginTop: '1%', |
||||
}, |
||||
}, |
||||
'Submit'), |
||||
]), |
||||
]) |
||||
} |
||||
|
||||
ShapeshiftForm.prototype.shift = function () { |
||||
var props = this.props |
||||
var withdrawal = this.props.buyView.buyAddress |
||||
var returnAddress = document.getElementById('fromCoinAddress').value |
||||
var pair = this.props.buyView.formView.marketinfo.pair |
||||
var data = { |
||||
'withdrawal': withdrawal, |
||||
'pair': pair, |
||||
'returnAddress': returnAddress, |
||||
// Public api key
|
||||
'apiKey': '803d1f5df2ed1b1476e4b9e6bcd089e34d8874595dda6a23b67d93c56ea9cc2445e98a6748b219b2b6ad654d9f075f1f1db139abfa93158c04e825db122c14b6', |
||||
} |
||||
var message = [ |
||||
`Deposit Limit: ${props.buyView.formView.marketinfo.limit}`, |
||||
`Deposit Minimum:${props.buyView.formView.marketinfo.minimum}`, |
||||
] |
||||
if (isValidAddress(withdrawal)) { |
||||
this.props.dispatch(actions.coinShiftRquest(data, message)) |
||||
} |
||||
} |
||||
|
||||
ShapeshiftForm.prototype.renderCoinList = function () { |
||||
var list = Object.keys(this.props.buyView.formView.coinOptions).map((item) => { |
||||
return h('option', { |
||||
value: item, |
||||
}, item) |
||||
}) |
||||
|
||||
return h('datalist#coinList', { |
||||
onClick: (event) => { |
||||
event.preventDefault() |
||||
}, |
||||
}, list) |
||||
} |
||||
|
||||
ShapeshiftForm.prototype.updateCoin = function (event) { |
||||
event.preventDefault() |
||||
const props = this.props |
||||
var coinOptions = this.props.buyView.formView.coinOptions |
||||
var coin = document.getElementById('fromCoin').value |
||||
|
||||
if (!coinOptions[coin.toUpperCase()] || coin.toUpperCase() === 'ETH') { |
||||
var message = 'Not a valid coin' |
||||
return props.dispatch(actions.displayWarning(message)) |
||||
} else { |
||||
return props.dispatch(actions.pairUpdate(coin)) |
||||
} |
||||
} |
||||
|
||||
ShapeshiftForm.prototype.handleLiveInput = function () { |
||||
const props = this.props |
||||
var coinOptions = this.props.buyView.formView.coinOptions |
||||
var coin = document.getElementById('fromCoin').value |
||||
|
||||
if (!coinOptions[coin.toUpperCase()] || coin.toUpperCase() === 'ETH') { |
||||
return null |
||||
} else { |
||||
return props.dispatch(actions.pairUpdate(coin)) |
||||
} |
||||
} |
||||
|
||||
ShapeshiftForm.prototype.renderInfo = function () { |
||||
const marketinfo = this.props.buyView.formView.marketinfo |
||||
const coinOptions = this.props.buyView.formView.coinOptions |
||||
var coin = marketinfo.pair.split('_')[0].toUpperCase() |
||||
|
||||
return h('span', { |
||||
style: { |
||||
}, |
||||
}, [ |
||||
h('h3.flex-row.text-transform-uppercase', { |
||||
style: { |
||||
color: '#868686', |
||||
paddingTop: '4px', |
||||
justifyContent: 'space-around', |
||||
textAlign: 'center', |
||||
fontSize: '17px', |
||||
}, |
||||
}, `Market Info for ${marketinfo.pair.replace('_', ' to ').toUpperCase()}:`), |
||||
h('.marketinfo', ['Status : ', `${coinOptions[coin].status}`]), |
||||
h('.marketinfo', ['Exchange Rate: ', `${marketinfo.rate}`]), |
||||
h('.marketinfo', ['Limit: ', `${marketinfo.limit}`]), |
||||
h('.marketinfo', ['Minimum : ', `${marketinfo.minimum}`]), |
||||
]) |
||||
} |
||||
|
||||
ShapeshiftForm.prototype.activeToggle = function (elementType) { |
||||
if (!this.props.buyView.formView.response || this.props.warning) return elementType |
||||
return `${elementType}.inactive` |
||||
} |
||||
|
||||
ShapeshiftForm.prototype.renderLoading = function () { |
||||
return h('span', { |
||||
style: { |
||||
position: 'absolute', |
||||
left: '70px', |
||||
bottom: '194px', |
||||
background: 'transparent', |
||||
width: '229px', |
||||
height: '82px', |
||||
display: 'flex', |
||||
justifyContent: 'center', |
||||
}, |
||||
}, [ |
||||
h('img', { |
||||
style: { |
||||
width: '60px', |
||||
}, |
||||
src: 'images/loading.svg', |
||||
}), |
||||
]) |
||||
} |
@ -0,0 +1,204 @@ |
||||
const inherits = require('util').inherits |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const connect = require('react-redux').connect |
||||
const vreme = new (require('vreme'))() |
||||
const explorerLink = require('etherscan-link').createExplorerLink |
||||
const actions = require('../../../ui/app/actions') |
||||
const addressSummary = require('../util').addressSummary |
||||
|
||||
const CopyButton = require('./copyButton') |
||||
const EthBalance = require('./eth-balance') |
||||
const Tooltip = require('./tooltip') |
||||
|
||||
|
||||
module.exports = connect(mapStateToProps)(ShiftListItem) |
||||
|
||||
function mapStateToProps (state) { |
||||
return { |
||||
conversionRate: state.metamask.conversionRate, |
||||
currentCurrency: state.metamask.currentCurrency, |
||||
} |
||||
} |
||||
|
||||
inherits(ShiftListItem, Component) |
||||
|
||||
function ShiftListItem () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
ShiftListItem.prototype.render = function () { |
||||
return ( |
||||
h('.transaction-list-item.flex-row', { |
||||
style: { |
||||
paddingTop: '20px', |
||||
paddingBottom: '20px', |
||||
justifyContent: 'space-around', |
||||
alignItems: 'center', |
||||
}, |
||||
}, [ |
||||
h('div', { |
||||
style: { |
||||
width: '0px', |
||||
position: 'relative', |
||||
bottom: '19px', |
||||
}, |
||||
}, [ |
||||
h('img', { |
||||
src: 'https://info.shapeshift.io/sites/default/files/logo.png', |
||||
style: { |
||||
height: '35px', |
||||
width: '132px', |
||||
position: 'absolute', |
||||
clip: 'rect(0px,23px,34px,0px)', |
||||
}, |
||||
}), |
||||
]), |
||||
|
||||
this.renderInfo(), |
||||
this.renderUtilComponents(), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
function formatDate (date) { |
||||
return vreme.format(new Date(date), 'March 16 2014 14:30') |
||||
} |
||||
|
||||
ShiftListItem.prototype.renderUtilComponents = function () { |
||||
var props = this.props |
||||
const { conversionRate, currentCurrency } = props |
||||
|
||||
switch (props.response.status) { |
||||
case 'no_deposits': |
||||
return h('.flex-row', [ |
||||
h(CopyButton, { |
||||
value: this.props.depositAddress, |
||||
}), |
||||
h(Tooltip, { |
||||
title: 'QR Code', |
||||
}, [ |
||||
h('i.fa.fa-qrcode.pointer.pop-hover', { |
||||
onClick: () => props.dispatch(actions.reshowQrCode(props.depositAddress, props.depositType)), |
||||
style: { |
||||
margin: '5px', |
||||
marginLeft: '23px', |
||||
marginRight: '12px', |
||||
fontSize: '20px', |
||||
color: '#F7861C', |
||||
}, |
||||
}), |
||||
]), |
||||
]) |
||||
case 'received': |
||||
return h('.flex-row') |
||||
|
||||
case 'complete': |
||||
return h('.flex-row', [ |
||||
h(CopyButton, { |
||||
value: this.props.response.transaction, |
||||
}), |
||||
h(EthBalance, { |
||||
value: `${props.response.outgoingCoin}`, |
||||
conversionRate, |
||||
currentCurrency, |
||||
width: '55px', |
||||
shorten: true, |
||||
needsParse: false, |
||||
incoming: true, |
||||
style: { |
||||
fontSize: '15px', |
||||
color: '#01888C', |
||||
}, |
||||
}), |
||||
]) |
||||
|
||||
case 'failed': |
||||
return '' |
||||
default: |
||||
return '' |
||||
} |
||||
} |
||||
|
||||
ShiftListItem.prototype.renderInfo = function () { |
||||
var props = this.props |
||||
switch (props.response.status) { |
||||
case 'no_deposits': |
||||
return h('.flex-column', { |
||||
style: { |
||||
width: '200px', |
||||
overflow: 'hidden', |
||||
}, |
||||
}, [ |
||||
h('div', { |
||||
style: { |
||||
fontSize: 'x-small', |
||||
color: '#ABA9AA', |
||||
width: '100%', |
||||
}, |
||||
}, `${props.depositType} to ETH via ShapeShift`), |
||||
h('div', 'No deposits received'), |
||||
h('div', { |
||||
style: { |
||||
fontSize: 'x-small', |
||||
color: '#ABA9AA', |
||||
width: '100%', |
||||
}, |
||||
}, formatDate(props.time)), |
||||
]) |
||||
case 'received': |
||||
return h('.flex-column', { |
||||
style: { |
||||
width: '200px', |
||||
overflow: 'hidden', |
||||
}, |
||||
}, [ |
||||
h('div', { |
||||
style: { |
||||
fontSize: 'x-small', |
||||
color: '#ABA9AA', |
||||
width: '100%', |
||||
}, |
||||
}, `${props.depositType} to ETH via ShapeShift`), |
||||
h('div', 'Conversion in progress'), |
||||
h('div', { |
||||
style: { |
||||
fontSize: 'x-small', |
||||
color: '#ABA9AA', |
||||
width: '100%', |
||||
}, |
||||
}, formatDate(props.time)), |
||||
]) |
||||
case 'complete': |
||||
var url = explorerLink(props.response.transaction, parseInt('1')) |
||||
|
||||
return h('.flex-column.pointer', { |
||||
style: { |
||||
width: '200px', |
||||
overflow: 'hidden', |
||||
}, |
||||
onClick: () => global.platform.openWindow({ url }), |
||||
}, [ |
||||
h('div', { |
||||
style: { |
||||
fontSize: 'x-small', |
||||
color: '#ABA9AA', |
||||
width: '100%', |
||||
}, |
||||
}, 'From ShapeShift'), |
||||
h('div', formatDate(props.time)), |
||||
h('div', { |
||||
style: { |
||||
fontSize: 'x-small', |
||||
color: '#ABA9AA', |
||||
width: '100%', |
||||
}, |
||||
}, addressSummary(props.response.transaction)), |
||||
]) |
||||
|
||||
case 'failed': |
||||
return h('span.error', '(Failed)') |
||||
default: |
||||
return '' |
||||
} |
||||
} |
@ -0,0 +1,37 @@ |
||||
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', |
||||
minHeight: '30px', |
||||
}, |
||||
}, tabs.map((tab) => { |
||||
const { key, content } = tab |
||||
return h(subview === key ? '.activeForm' : '.inactiveForm.pointer', { |
||||
onClick: () => { |
||||
this.setState({ subview: key }) |
||||
tabSelected(key) |
||||
}, |
||||
}, content) |
||||
})) |
||||
) |
||||
} |
||||
|
@ -0,0 +1,18 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
|
||||
module.exports = NewComponent |
||||
|
||||
inherits(NewComponent, Component) |
||||
function NewComponent () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
NewComponent.prototype.render = function () { |
||||
const props = this.props |
||||
|
||||
return ( |
||||
h('span', props.message) |
||||
) |
||||
} |
@ -0,0 +1,72 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
const Identicon = require('./identicon') |
||||
const prefixForNetwork = require('../../lib/etherscan-prefix-for-network') |
||||
|
||||
module.exports = TokenCell |
||||
|
||||
inherits(TokenCell, Component) |
||||
function TokenCell () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
TokenCell.prototype.render = function () { |
||||
const props = this.props |
||||
const { address, symbol, string, network, userAddress } = props |
||||
|
||||
return ( |
||||
h('li.token-cell', { |
||||
style: { cursor: network === '1' ? 'pointer' : 'default' }, |
||||
onClick: this.view.bind(this, address, userAddress, network), |
||||
}, [ |
||||
|
||||
h(Identicon, { |
||||
diameter: 50, |
||||
address, |
||||
network, |
||||
}), |
||||
|
||||
h('h3', `${string || 0} ${symbol}`), |
||||
|
||||
h('span', { style: { flex: '1 0 auto' } }), |
||||
|
||||
/* |
||||
h('button', { |
||||
onClick: this.send.bind(this, address), |
||||
}, 'SEND'), |
||||
*/ |
||||
|
||||
]) |
||||
) |
||||
} |
||||
|
||||
TokenCell.prototype.send = function (address, event) { |
||||
event.preventDefault() |
||||
event.stopPropagation() |
||||
const url = tokenFactoryFor(address) |
||||
if (url) { |
||||
navigateTo(url) |
||||
} |
||||
} |
||||
|
||||
TokenCell.prototype.view = function (address, userAddress, network, event) { |
||||
const url = etherscanLinkFor(address, userAddress, network) |
||||
if (url) { |
||||
navigateTo(url) |
||||
} |
||||
} |
||||
|
||||
function navigateTo (url) { |
||||
global.platform.openWindow({ url }) |
||||
} |
||||
|
||||
function etherscanLinkFor (tokenAddress, address, network) { |
||||
const prefix = prefixForNetwork(network) |
||||
return `https://${prefix}etherscan.io/token/${tokenAddress}?a=${address}` |
||||
} |
||||
|
||||
function tokenFactoryFor (tokenAddress) { |
||||
return `https://tokenfactory.surge.sh/#/token/${tokenAddress}` |
||||
} |
||||
|
@ -0,0 +1,207 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
const TokenTracker = require('eth-token-tracker') |
||||
const TokenCell = require('./token-cell.js') |
||||
|
||||
module.exports = TokenList |
||||
|
||||
inherits(TokenList, Component) |
||||
function TokenList () { |
||||
this.state = { |
||||
tokens: [], |
||||
isLoading: true, |
||||
network: null, |
||||
} |
||||
Component.call(this) |
||||
} |
||||
|
||||
TokenList.prototype.render = function () { |
||||
const state = this.state |
||||
const { tokens, isLoading, error } = state |
||||
const { userAddress, network } = this.props |
||||
|
||||
if (isLoading) { |
||||
return this.message('Loading') |
||||
} |
||||
|
||||
if (error) { |
||||
log.error(error) |
||||
return h('.hotFix', { |
||||
style: { |
||||
padding: '80px', |
||||
}, |
||||
}, [ |
||||
'We had trouble loading your token balances. You can view them ', |
||||
h('span.hotFix', { |
||||
style: { |
||||
color: 'rgba(247, 134, 28, 1)', |
||||
cursor: 'pointer', |
||||
}, |
||||
onClick: () => { |
||||
global.platform.openWindow({ |
||||
url: `https://ethplorer.io/address/${userAddress}`, |
||||
}) |
||||
}, |
||||
}, 'here'), |
||||
]) |
||||
} |
||||
|
||||
const tokenViews = tokens.map((tokenData) => { |
||||
tokenData.network = network |
||||
tokenData.userAddress = userAddress |
||||
return h(TokenCell, tokenData) |
||||
}) |
||||
|
||||
return h('.full-flex-height', [ |
||||
this.renderTokenStatusBar(), |
||||
|
||||
h('ol.full-flex-height.flex-column', { |
||||
style: { |
||||
overflowY: 'auto', |
||||
display: 'flex', |
||||
flexDirection: 'column', |
||||
}, |
||||
}, [ |
||||
h('style', ` |
||||
|
||||
li.token-cell { |
||||
display: flex; |
||||
flex-direction: row; |
||||
align-items: center; |
||||
padding: 10px; |
||||
min-height: 50px; |
||||
} |
||||
|
||||
li.token-cell > h3 { |
||||
margin-left: 12px; |
||||
} |
||||
|
||||
li.token-cell:hover { |
||||
background: white; |
||||
cursor: pointer; |
||||
} |
||||
|
||||
`),
|
||||
...tokenViews, |
||||
h('.flex-grow'), |
||||
]), |
||||
]) |
||||
} |
||||
|
||||
TokenList.prototype.renderTokenStatusBar = function () { |
||||
const { tokens } = this.state |
||||
|
||||
let msg |
||||
if (tokens.length === 1) { |
||||
msg = `You own 1 token` |
||||
} else if (tokens.length > 1) { |
||||
msg = `You own ${tokens.length} tokens` |
||||
} else { |
||||
msg = `No tokens found` |
||||
} |
||||
|
||||
return h('div', { |
||||
style: { |
||||
display: 'flex', |
||||
justifyContent: 'space-between', |
||||
alignItems: 'center', |
||||
minHeight: '70px', |
||||
padding: '10px', |
||||
}, |
||||
}, [ |
||||
h('span', msg), |
||||
h('button', { |
||||
key: 'reveal-account-bar', |
||||
onClick: (event) => { |
||||
event.preventDefault() |
||||
this.props.addToken() |
||||
}, |
||||
style: { |
||||
display: 'flex', |
||||
height: '40px', |
||||
padding: '10px', |
||||
justifyContent: 'center', |
||||
alignItems: 'center', |
||||
}, |
||||
}, [ |
||||
'ADD TOKEN', |
||||
]), |
||||
]) |
||||
} |
||||
|
||||
TokenList.prototype.message = function (body) { |
||||
return h('div', { |
||||
style: { |
||||
display: 'flex', |
||||
height: '250px', |
||||
alignItems: 'center', |
||||
justifyContent: 'center', |
||||
padding: '30px', |
||||
}, |
||||
}, body) |
||||
} |
||||
|
||||
TokenList.prototype.componentDidMount = function () { |
||||
this.createFreshTokenTracker() |
||||
} |
||||
|
||||
TokenList.prototype.createFreshTokenTracker = function () { |
||||
if (this.tracker) { |
||||
// Clean up old trackers when refreshing:
|
||||
this.tracker.stop() |
||||
this.tracker.removeListener('update', this.balanceUpdater) |
||||
this.tracker.removeListener('error', this.showError) |
||||
} |
||||
|
||||
if (!global.ethereumProvider) return |
||||
const { userAddress } = this.props |
||||
this.tracker = new TokenTracker({ |
||||
userAddress, |
||||
provider: global.ethereumProvider, |
||||
tokens: this.props.tokens, |
||||
pollingInterval: 8000, |
||||
}) |
||||
|
||||
|
||||
// Set up listener instances for cleaning up
|
||||
this.balanceUpdater = this.updateBalances.bind(this) |
||||
this.showError = (error) => { |
||||
this.setState({ error, isLoading: false }) |
||||
} |
||||
this.tracker.on('update', this.balanceUpdater) |
||||
this.tracker.on('error', this.showError) |
||||
|
||||
this.tracker.updateBalances() |
||||
.then(() => { |
||||
this.updateBalances(this.tracker.serialize()) |
||||
}) |
||||
.catch((reason) => { |
||||
log.error(`Problem updating balances`, reason) |
||||
this.setState({ isLoading: false }) |
||||
}) |
||||
} |
||||
|
||||
TokenList.prototype.componentWillUpdate = function (nextProps) { |
||||
if (nextProps.network === 'loading') return |
||||
const oldNet = this.props.network |
||||
const newNet = nextProps.network |
||||
|
||||
if (oldNet && newNet && newNet !== oldNet) { |
||||
this.setState({ isLoading: true }) |
||||
this.createFreshTokenTracker() |
||||
} |
||||
} |
||||
|
||||
TokenList.prototype.updateBalances = function (tokens) { |
||||
const heldTokens = tokens.filter(token => { |
||||
return token.balance !== '0' && token.string !== '0.000' |
||||
}) |
||||
this.setState({ tokens: heldTokens, isLoading: false }) |
||||
} |
||||
|
||||
TokenList.prototype.componentWillUnmount = function () { |
||||
if (!this.tracker) return |
||||
this.tracker.stop() |
||||
} |
||||
|
@ -0,0 +1,22 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
const ReactTooltip = require('react-tooltip-component') |
||||
|
||||
module.exports = Tooltip |
||||
|
||||
inherits(Tooltip, Component) |
||||
function Tooltip () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
Tooltip.prototype.render = function () { |
||||
const props = this.props |
||||
const { position, title, children } = props |
||||
|
||||
return h(ReactTooltip, { |
||||
position: position || 'left', |
||||
title, |
||||
fixed: true, |
||||
}, children) |
||||
} |
@ -0,0 +1,68 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
const Tooltip = require('./tooltip') |
||||
|
||||
const Identicon = require('./identicon') |
||||
|
||||
module.exports = TransactionIcon |
||||
|
||||
inherits(TransactionIcon, Component) |
||||
function TransactionIcon () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
TransactionIcon.prototype.render = function () { |
||||
const { transaction, txParams, isMsg } = this.props |
||||
switch (transaction.status) { |
||||
case 'unapproved': |
||||
return h(!isMsg ? '.unapproved-tx-icon' : 'i.fa.fa-certificate.fa-lg') |
||||
|
||||
case 'rejected': |
||||
return h('i.fa.fa-exclamation-triangle.fa-lg.warning', { |
||||
style: { |
||||
width: '24px', |
||||
}, |
||||
}) |
||||
|
||||
case 'failed': |
||||
return h('i.fa.fa-exclamation-triangle.fa-lg.error', { |
||||
style: { |
||||
width: '24px', |
||||
}, |
||||
}) |
||||
|
||||
case 'submitted': |
||||
return h(Tooltip, { |
||||
title: 'Pending', |
||||
position: 'right', |
||||
}, [ |
||||
h('i.fa.fa-ellipsis-h', { |
||||
style: { |
||||
fontSize: '27px', |
||||
}, |
||||
}), |
||||
]) |
||||
} |
||||
|
||||
if (isMsg) { |
||||
return h('i.fa.fa-certificate.fa-lg', { |
||||
style: { |
||||
width: '24px', |
||||
}, |
||||
}) |
||||
} |
||||
|
||||
if (txParams.to) { |
||||
return h(Identicon, { |
||||
diameter: 24, |
||||
address: txParams.to || transaction.hash, |
||||
}) |
||||
} else { |
||||
return h('i.fa.fa-file-text-o.fa-lg', { |
||||
style: { |
||||
width: '24px', |
||||
}, |
||||
}) |
||||
} |
||||
} |
@ -0,0 +1,175 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
|
||||
const EthBalance = require('./eth-balance') |
||||
const addressSummary = require('../util').addressSummary |
||||
const explorerLink = require('etherscan-link').createExplorerLink |
||||
const CopyButton = require('./copyButton') |
||||
const vreme = new (require('vreme'))() |
||||
const Tooltip = require('./tooltip') |
||||
const numberToBN = require('number-to-bn') |
||||
|
||||
const TransactionIcon = require('./transaction-list-item-icon') |
||||
const ShiftListItem = require('./shift-list-item') |
||||
module.exports = TransactionListItem |
||||
|
||||
inherits(TransactionListItem, Component) |
||||
function TransactionListItem () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
TransactionListItem.prototype.render = function () { |
||||
const { transaction, network, conversionRate, currentCurrency } = this.props |
||||
if (transaction.key === 'shapeshift') { |
||||
if (network === '1') return h(ShiftListItem, transaction) |
||||
} |
||||
var date = formatDate(transaction.time) |
||||
|
||||
let isLinkable = false |
||||
const numericNet = parseInt(network) |
||||
isLinkable = numericNet === 1 || numericNet === 3 || numericNet === 4 || numericNet === 42 |
||||
|
||||
var isMsg = ('msgParams' in transaction) |
||||
var isTx = ('txParams' in transaction) |
||||
var isPending = transaction.status === 'unapproved' |
||||
let txParams |
||||
if (isTx) { |
||||
txParams = transaction.txParams |
||||
} else if (isMsg) { |
||||
txParams = transaction.msgParams |
||||
} |
||||
|
||||
const nonce = txParams.nonce ? numberToBN(txParams.nonce).toString(10) : '' |
||||
|
||||
const isClickable = ('hash' in transaction && isLinkable) || isPending |
||||
return ( |
||||
h(`.transaction-list-item.flex-row.flex-space-between${isClickable ? '.pointer' : ''}`, { |
||||
onClick: (event) => { |
||||
if (isPending) { |
||||
this.props.showTx(transaction.id) |
||||
} |
||||
event.stopPropagation() |
||||
if (!transaction.hash || !isLinkable) return |
||||
var url = explorerLink(transaction.hash, parseInt(network)) |
||||
global.platform.openWindow({ url }) |
||||
}, |
||||
style: { |
||||
padding: '20px 0', |
||||
display: 'flex', |
||||
justifyContent: 'space-between', |
||||
}, |
||||
}, [ |
||||
|
||||
h('.identicon-wrapper.flex-column.flex-center.select-none', [ |
||||
h(TransactionIcon, { txParams, transaction, isTx, isMsg }), |
||||
]), |
||||
|
||||
h(Tooltip, { |
||||
title: 'Transaction Number', |
||||
position: 'right', |
||||
}, [ |
||||
h('span', { |
||||
style: { |
||||
display: 'flex', |
||||
cursor: 'normal', |
||||
flexDirection: 'column', |
||||
alignItems: 'center', |
||||
justifyContent: 'center', |
||||
}, |
||||
}, nonce), |
||||
]), |
||||
|
||||
h('.flex-column', {style: {width: '150px', overflow: 'hidden'}}, [ |
||||
domainField(txParams), |
||||
h('div', date), |
||||
recipientField(txParams, transaction, isTx, isMsg), |
||||
]), |
||||
|
||||
// Places a copy button if tx is successful, else places a placeholder empty div.
|
||||
transaction.hash ? h(CopyButton, { value: transaction.hash }) : h('div', {style: { display: 'flex', alignItems: 'center', width: '26px' }}), |
||||
|
||||
isTx ? h(EthBalance, { |
||||
value: txParams.value, |
||||
conversionRate, |
||||
currentCurrency, |
||||
shorten: true, |
||||
showFiat: false, |
||||
style: {fontSize: '15px'}, |
||||
}) : h('.flex-column'), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
function domainField (txParams) { |
||||
return h('div', { |
||||
style: { |
||||
fontSize: 'x-small', |
||||
color: '#ABA9AA', |
||||
overflow: 'hidden', |
||||
textOverflow: 'ellipsis', |
||||
width: '100%', |
||||
}, |
||||
}, [ |
||||
txParams.origin, |
||||
]) |
||||
} |
||||
|
||||
function recipientField (txParams, transaction, isTx, isMsg) { |
||||
let message |
||||
|
||||
if (isMsg) { |
||||
message = 'Signature Requested' |
||||
} else if (txParams.to) { |
||||
message = addressSummary(txParams.to) |
||||
} else { |
||||
message = 'Contract Published' |
||||
} |
||||
|
||||
return h('div', { |
||||
style: { |
||||
fontSize: 'x-small', |
||||
color: '#ABA9AA', |
||||
}, |
||||
}, [ |
||||
message, |
||||
renderErrorOrWarning(transaction), |
||||
]) |
||||
} |
||||
|
||||
function formatDate (date) { |
||||
return vreme.format(new Date(date), 'March 16 2014 14:30') |
||||
} |
||||
|
||||
function renderErrorOrWarning (transaction) { |
||||
const { status, err, warning } = transaction |
||||
|
||||
// show rejected
|
||||
if (status === 'rejected') { |
||||
return h('span.error', ' (Rejected)') |
||||
} |
||||
|
||||
// show error
|
||||
if (err) { |
||||
const message = err.message || '' |
||||
return ( |
||||
h(Tooltip, { |
||||
title: message, |
||||
position: 'bottom', |
||||
}, [ |
||||
h(`span.error`, ` (Failed)`), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
// show warning
|
||||
if (warning) { |
||||
const message = warning.message |
||||
return h(Tooltip, { |
||||
title: message, |
||||
position: 'bottom', |
||||
}, [ |
||||
h(`span.warning`, ` (Warning)`), |
||||
]) |
||||
} |
||||
} |
@ -0,0 +1,87 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
|
||||
const TransactionListItem = require('./transaction-list-item') |
||||
|
||||
module.exports = TransactionList |
||||
|
||||
|
||||
inherits(TransactionList, Component) |
||||
function TransactionList () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
TransactionList.prototype.render = function () { |
||||
const { transactions, network, unapprovedMsgs, conversionRate } = this.props |
||||
|
||||
var shapeShiftTxList |
||||
if (network === '1') { |
||||
shapeShiftTxList = this.props.shapeShiftTxList |
||||
} |
||||
const txsToRender = !shapeShiftTxList ? transactions.concat(unapprovedMsgs) : transactions.concat(unapprovedMsgs, shapeShiftTxList) |
||||
.sort((a, b) => b.time - a.time) |
||||
|
||||
return ( |
||||
|
||||
h('section.transaction-list.full-flex-height', { |
||||
style: { |
||||
justifyContent: 'center', |
||||
}, |
||||
}, [ |
||||
|
||||
h('style', ` |
||||
.transaction-list .transaction-list-item:not(:last-of-type) { |
||||
border-bottom: 1px solid #D4D4D4; |
||||
} |
||||
.transaction-list .transaction-list-item .ether-balance-label { |
||||
display: block !important; |
||||
font-size: small; |
||||
} |
||||
`),
|
||||
|
||||
h('.tx-list', { |
||||
style: { |
||||
overflowY: 'auto', |
||||
height: '100%', |
||||
padding: '0 25px 0 15px', |
||||
textAlign: 'center', |
||||
}, |
||||
}, [ |
||||
|
||||
txsToRender.length |
||||
? txsToRender.map((transaction, i) => { |
||||
let key |
||||
switch (transaction.key) { |
||||
case 'shapeshift': |
||||
const { depositAddress, time } = transaction |
||||
key = `shift-tx-${depositAddress}-${time}-${i}` |
||||
break |
||||
default: |
||||
key = `tx-${transaction.id}-${i}` |
||||
} |
||||
return h(TransactionListItem, { |
||||
transaction, i, network, key, |
||||
conversionRate, |
||||
showTx: (txId) => { |
||||
this.props.viewPendingTx(txId) |
||||
}, |
||||
}) |
||||
}) |
||||
: h('.flex-center.full-flex-height', { |
||||
style: { |
||||
flexDirection: 'column', |
||||
justifyContent: 'center', |
||||
}, |
||||
}, [ |
||||
h('p', { |
||||
style: { |
||||
marginTop: '50px', |
||||
}, |
||||
}, 'No transaction history.'), |
||||
]), |
||||
]), |
||||
]) |
||||
) |
||||
} |
||||
|
@ -0,0 +1,42 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
const extend = require('xtend') |
||||
|
||||
module.exports = TypedMessageRenderer |
||||
|
||||
inherits(TypedMessageRenderer, Component) |
||||
function TypedMessageRenderer () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
TypedMessageRenderer.prototype.render = function () { |
||||
const props = this.props |
||||
const { value, style } = props |
||||
const text = renderTypedData(value) |
||||
|
||||
const defaultStyle = extend({ |
||||
width: '315px', |
||||
maxHeight: '210px', |
||||
resize: 'none', |
||||
border: 'none', |
||||
background: 'white', |
||||
padding: '3px', |
||||
overflow: 'scroll', |
||||
}, style) |
||||
|
||||
return ( |
||||
h('div.font-small', { |
||||
style: defaultStyle, |
||||
}, text) |
||||
) |
||||
} |
||||
|
||||
function renderTypedData (values) { |
||||
return values.map(function (value) { |
||||
return h('div', {}, [ |
||||
h('strong', {style: {display: 'block', fontWeight: 'bold'}}, String(value.name) + ':'), |
||||
h('div', {}, value.value), |
||||
]) |
||||
}) |
||||
} |
@ -0,0 +1,235 @@ |
||||
const inherits = require('util').inherits |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const connect = require('react-redux').connect |
||||
const actions = require('../../ui/app/actions') |
||||
const NetworkIndicator = require('./components/network') |
||||
const txHelper = require('../lib/tx-helper') |
||||
const isPopupOrNotification = require('../../app/scripts/lib/is-popup-or-notification') |
||||
|
||||
const PendingTx = require('./components/pending-tx') |
||||
const PendingMsg = require('./components/pending-msg') |
||||
const PendingPersonalMsg = require('./components/pending-personal-msg') |
||||
const PendingTypedMsg = require('./components/pending-typed-msg') |
||||
const Loading = require('./components/loading') |
||||
|
||||
module.exports = connect(mapStateToProps)(ConfirmTxScreen) |
||||
|
||||
function mapStateToProps (state) { |
||||
return { |
||||
identities: state.metamask.identities, |
||||
accounts: state.metamask.accounts, |
||||
selectedAddress: state.metamask.selectedAddress, |
||||
unapprovedTxs: state.metamask.unapprovedTxs, |
||||
unapprovedMsgs: state.metamask.unapprovedMsgs, |
||||
unapprovedPersonalMsgs: state.metamask.unapprovedPersonalMsgs, |
||||
unapprovedTypedMessages: state.metamask.unapprovedTypedMessages, |
||||
index: state.appState.currentView.context, |
||||
warning: state.appState.warning, |
||||
network: state.metamask.network, |
||||
provider: state.metamask.provider, |
||||
conversionRate: state.metamask.conversionRate, |
||||
currentCurrency: state.metamask.currentCurrency, |
||||
blockGasLimit: state.metamask.currentBlockGasLimit, |
||||
computedBalances: state.metamask.computedBalances, |
||||
} |
||||
} |
||||
|
||||
inherits(ConfirmTxScreen, Component) |
||||
function ConfirmTxScreen () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
ConfirmTxScreen.prototype.render = function () { |
||||
const props = this.props |
||||
const { network, provider, unapprovedTxs, currentCurrency, computedBalances, |
||||
unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, conversionRate, blockGasLimit } = props |
||||
|
||||
var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, network) |
||||
|
||||
var txData = unconfTxList[props.index] || {} |
||||
var txParams = txData.params || {} |
||||
var isNotification = isPopupOrNotification() === 'notification' |
||||
|
||||
log.info(`rendering a combined ${unconfTxList.length} unconf msg & txs`) |
||||
if (unconfTxList.length === 0) return h(Loading, { isLoading: true }) |
||||
|
||||
const unconfTxListLength = unconfTxList.length |
||||
|
||||
return ( |
||||
|
||||
h('.flex-column.flex-grow', [ |
||||
|
||||
// subtitle and nav
|
||||
h('.section-title.flex-row.flex-center', [ |
||||
!isNotification ? h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', { |
||||
onClick: this.goHome.bind(this), |
||||
}) : null, |
||||
h('h2.page-subtitle', 'Confirm Transaction'), |
||||
isNotification ? h(NetworkIndicator, { |
||||
network: network, |
||||
provider: provider, |
||||
}) : null, |
||||
]), |
||||
|
||||
h('h3', { |
||||
style: { |
||||
alignSelf: 'center', |
||||
display: unconfTxList.length > 1 ? 'block' : 'none', |
||||
}, |
||||
}, [ |
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', { |
||||
style: { |
||||
display: props.index === 0 ? 'none' : 'inline-block', |
||||
}, |
||||
onClick: () => props.dispatch(actions.previousTx()), |
||||
}), |
||||
` ${props.index + 1} of ${unconfTxList.length} `, |
||||
h('i.fa.fa-arrow-right.fa-lg.cursor-pointer', { |
||||
style: { |
||||
display: props.index + 1 === unconfTxList.length ? 'none' : 'inline-block', |
||||
}, |
||||
onClick: () => props.dispatch(actions.nextTx()), |
||||
}), |
||||
]), |
||||
|
||||
warningIfExists(props.warning), |
||||
|
||||
currentTxView({ |
||||
// Properties
|
||||
txData: txData, |
||||
key: txData.id, |
||||
selectedAddress: props.selectedAddress, |
||||
accounts: props.accounts, |
||||
identities: props.identities, |
||||
conversionRate, |
||||
currentCurrency, |
||||
blockGasLimit, |
||||
unconfTxListLength, |
||||
computedBalances, |
||||
// Actions
|
||||
buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress), |
||||
sendTransaction: this.sendTransaction.bind(this), |
||||
cancelTransaction: this.cancelTransaction.bind(this, txData), |
||||
cancelAllTransactions: this.cancelAllTransactions.bind(this, unconfTxList), |
||||
signMessage: this.signMessage.bind(this, txData), |
||||
signPersonalMessage: this.signPersonalMessage.bind(this, txData), |
||||
signTypedMessage: this.signTypedMessage.bind(this, txData), |
||||
cancelMessage: this.cancelMessage.bind(this, txData), |
||||
cancelPersonalMessage: this.cancelPersonalMessage.bind(this, txData), |
||||
cancelTypedMessage: this.cancelTypedMessage.bind(this, txData), |
||||
}), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
function currentTxView (opts) { |
||||
log.info('rendering current tx view') |
||||
const { txData } = opts |
||||
const { txParams, msgParams, type } = txData |
||||
|
||||
if (txParams) { |
||||
log.debug('txParams detected, rendering pending tx') |
||||
return h(PendingTx, opts) |
||||
} else if (msgParams) { |
||||
log.debug('msgParams detected, rendering pending msg') |
||||
|
||||
if (type === 'eth_sign') { |
||||
log.debug('rendering eth_sign message') |
||||
return h(PendingMsg, opts) |
||||
} else if (type === 'personal_sign') { |
||||
log.debug('rendering personal_sign message') |
||||
return h(PendingPersonalMsg, opts) |
||||
} else if (type === 'eth_signTypedData') { |
||||
log.debug('rendering eth_signTypedData message') |
||||
return h(PendingTypedMsg, opts) |
||||
} |
||||
} |
||||
} |
||||
|
||||
ConfirmTxScreen.prototype.buyEth = function (address, event) { |
||||
event.preventDefault() |
||||
this.props.dispatch(actions.buyEthView(address)) |
||||
} |
||||
|
||||
ConfirmTxScreen.prototype.sendTransaction = function (txData, event) { |
||||
this.stopPropagation(event) |
||||
this.props.dispatch(actions.updateAndApproveTx(txData)) |
||||
} |
||||
|
||||
ConfirmTxScreen.prototype.cancelTransaction = function (txData, event) { |
||||
this.stopPropagation(event) |
||||
event.preventDefault() |
||||
this.props.dispatch(actions.cancelTx(txData)) |
||||
} |
||||
|
||||
ConfirmTxScreen.prototype.cancelAllTransactions = function (unconfTxList, event) { |
||||
this.stopPropagation(event) |
||||
event.preventDefault() |
||||
this.props.dispatch(actions.cancelAllTx(unconfTxList)) |
||||
} |
||||
|
||||
ConfirmTxScreen.prototype.signMessage = function (msgData, event) { |
||||
log.info('conf-tx.js: signing message') |
||||
var params = msgData.msgParams |
||||
params.metamaskId = msgData.id |
||||
this.stopPropagation(event) |
||||
this.props.dispatch(actions.signMsg(params)) |
||||
} |
||||
|
||||
ConfirmTxScreen.prototype.stopPropagation = function (event) { |
||||
if (event.stopPropagation) { |
||||
event.stopPropagation() |
||||
} |
||||
} |
||||
|
||||
ConfirmTxScreen.prototype.signPersonalMessage = function (msgData, event) { |
||||
log.info('conf-tx.js: signing personal message') |
||||
var params = msgData.msgParams |
||||
params.metamaskId = msgData.id |
||||
this.stopPropagation(event) |
||||
this.props.dispatch(actions.signPersonalMsg(params)) |
||||
} |
||||
|
||||
ConfirmTxScreen.prototype.signTypedMessage = function (msgData, event) { |
||||
log.info('conf-tx.js: signing typed message') |
||||
var params = msgData.msgParams |
||||
params.metamaskId = msgData.id |
||||
this.stopPropagation(event) |
||||
this.props.dispatch(actions.signTypedMsg(params)) |
||||
} |
||||
|
||||
ConfirmTxScreen.prototype.cancelMessage = function (msgData, event) { |
||||
log.info('canceling message') |
||||
this.stopPropagation(event) |
||||
this.props.dispatch(actions.cancelMsg(msgData)) |
||||
} |
||||
|
||||
ConfirmTxScreen.prototype.cancelPersonalMessage = function (msgData, event) { |
||||
log.info('canceling personal message') |
||||
this.stopPropagation(event) |
||||
this.props.dispatch(actions.cancelPersonalMsg(msgData)) |
||||
} |
||||
|
||||
ConfirmTxScreen.prototype.cancelTypedMessage = function (msgData, event) { |
||||
log.info('canceling typed message') |
||||
this.stopPropagation(event) |
||||
this.props.dispatch(actions.cancelTypedMsg(msgData)) |
||||
} |
||||
|
||||
ConfirmTxScreen.prototype.goHome = function (event) { |
||||
this.stopPropagation(event) |
||||
this.props.dispatch(actions.goHome()) |
||||
} |
||||
|
||||
function warningIfExists (warning) { |
||||
if (warning && |
||||
// Do not display user rejections on this screen:
|
||||
warning.indexOf('User denied transaction signature') === -1) { |
||||
return h('.error', { |
||||
style: { |
||||
margin: 'auto', |
||||
}, |
||||
}, warning) |
||||
} |
||||
} |
@ -0,0 +1,222 @@ |
||||
const inherits = require('util').inherits |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const connect = require('react-redux').connect |
||||
const actions = require('../../ui/app/actions') |
||||
const infuraCurrencies = require('./infura-conversion.json').objects.sort((a, b) => { |
||||
return a.quote.name.toLocaleLowerCase().localeCompare(b.quote.name.toLocaleLowerCase()) |
||||
}) |
||||
const validUrl = require('valid-url') |
||||
const exportAsFile = require('./util').exportAsFile |
||||
const Modal = require('../../ui/app/components/modals/index').Modal |
||||
|
||||
module.exports = connect(mapStateToProps)(ConfigScreen) |
||||
|
||||
function mapStateToProps (state) { |
||||
return { |
||||
metamask: state.metamask, |
||||
warning: state.appState.warning, |
||||
} |
||||
} |
||||
|
||||
inherits(ConfigScreen, Component) |
||||
function ConfigScreen () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
ConfigScreen.prototype.render = function () { |
||||
var state = this.props |
||||
var metamaskState = state.metamask |
||||
var warning = state.warning |
||||
|
||||
return ( |
||||
h('.flex-column.flex-grow', [ |
||||
|
||||
h(Modal, {}, []), |
||||
|
||||
// subtitle and nav
|
||||
h('.section-title.flex-row.flex-center', [ |
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', { |
||||
onClick: (event) => { |
||||
state.dispatch(actions.goHome()) |
||||
}, |
||||
}), |
||||
h('h2.page-subtitle', 'Settings'), |
||||
]), |
||||
|
||||
h('.error', { |
||||
style: { |
||||
display: warning ? 'block' : 'none', |
||||
padding: '0 20px', |
||||
textAlign: 'center', |
||||
}, |
||||
}, warning), |
||||
|
||||
// conf view
|
||||
h('.flex-column.flex-justify-center.flex-grow.select-none', [ |
||||
h('.flex-space-around', { |
||||
style: { |
||||
padding: '20px', |
||||
}, |
||||
}, [ |
||||
|
||||
currentProviderDisplay(metamaskState), |
||||
|
||||
h('div', { style: {display: 'flex'} }, [ |
||||
h('input#new_rpc', { |
||||
placeholder: 'New RPC URL', |
||||
style: { |
||||
width: 'inherit', |
||||
flex: '1 0 auto', |
||||
height: '30px', |
||||
margin: '8px', |
||||
}, |
||||
onKeyPress (event) { |
||||
if (event.key === 'Enter') { |
||||
var element = event.target |
||||
var newRpc = element.value |
||||
rpcValidation(newRpc, state) |
||||
} |
||||
}, |
||||
}), |
||||
h('button', { |
||||
style: { |
||||
alignSelf: 'center', |
||||
}, |
||||
onClick (event) { |
||||
event.preventDefault() |
||||
var element = document.querySelector('input#new_rpc') |
||||
var newRpc = element.value |
||||
rpcValidation(newRpc, state) |
||||
}, |
||||
}, 'Save'), |
||||
]), |
||||
|
||||
h('hr.horizontal-line'), |
||||
|
||||
currentConversionInformation(metamaskState, state), |
||||
|
||||
h('hr.horizontal-line'), |
||||
|
||||
h('div', { |
||||
style: { |
||||
marginTop: '20px', |
||||
}, |
||||
}, [ |
||||
h('p', { |
||||
style: { |
||||
fontFamily: 'Montserrat Light', |
||||
fontSize: '13px', |
||||
}, |
||||
}, `State logs contain your public account addresses and sent transactions.`), |
||||
h('br'), |
||||
h('button', { |
||||
style: { |
||||
alignSelf: 'center', |
||||
}, |
||||
onClick (event) { |
||||
window.logStateString((err, result) => { |
||||
if (err) { |
||||
state.dispatch(actions.displayWarning('Error in retrieving state logs.')) |
||||
} else { |
||||
exportAsFile('MetaMask State Logs.json', result) |
||||
} |
||||
}) |
||||
}, |
||||
}, 'Download State Logs'), |
||||
]), |
||||
|
||||
h('hr.horizontal-line'), |
||||
|
||||
h('div', { |
||||
style: { |
||||
marginTop: '20px', |
||||
}, |
||||
}, [ |
||||
h('button', { |
||||
style: { |
||||
alignSelf: 'center', |
||||
}, |
||||
onClick (event) { |
||||
event.preventDefault() |
||||
state.dispatch(actions.revealSeedConfirmation()) |
||||
}, |
||||
}, 'Reveal Seed Words'), |
||||
]), |
||||
|
||||
]), |
||||
]), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
function rpcValidation (newRpc, state) { |
||||
if (validUrl.isWebUri(newRpc)) { |
||||
state.dispatch(actions.setRpcTarget(newRpc)) |
||||
} else { |
||||
var appendedRpc = `http://${newRpc}` |
||||
if (validUrl.isWebUri(appendedRpc)) { |
||||
state.dispatch(actions.displayWarning('URIs require the appropriate HTTP/HTTPS prefix.')) |
||||
} else { |
||||
state.dispatch(actions.displayWarning('Invalid RPC URI')) |
||||
} |
||||
} |
||||
} |
||||
|
||||
function currentConversionInformation (metamaskState, state) { |
||||
var currentCurrency = metamaskState.currentCurrency |
||||
var conversionDate = metamaskState.conversionDate |
||||
return h('div', [ |
||||
h('span', {style: { fontWeight: 'bold', paddingRight: '10px'}}, 'Current Conversion'), |
||||
h('span', {style: { fontWeight: 'bold', paddingRight: '10px', fontSize: '13px'}}, `Updated ${Date(conversionDate)}`), |
||||
h('select#currentCurrency', { |
||||
onChange (event) { |
||||
event.preventDefault() |
||||
var element = document.getElementById('currentCurrency') |
||||
var newCurrency = element.value |
||||
state.dispatch(actions.setCurrentCurrency(newCurrency)) |
||||
}, |
||||
defaultValue: currentCurrency, |
||||
}, infuraCurrencies.map((currency) => { |
||||
return h('option', {key: currency.quote.code, value: currency.quote.code}, `${currency.quote.code.toUpperCase()} - ${currency.quote.name}`) |
||||
}) |
||||
), |
||||
]) |
||||
} |
||||
|
||||
function currentProviderDisplay (metamaskState) { |
||||
var provider = metamaskState.provider |
||||
var title, value |
||||
|
||||
switch (provider.type) { |
||||
|
||||
case 'mainnet': |
||||
title = 'Current Network' |
||||
value = 'Main Ethereum Network' |
||||
break |
||||
|
||||
case 'ropsten': |
||||
title = 'Current Network' |
||||
value = 'Ropsten Test Network' |
||||
break |
||||
|
||||
case 'kovan': |
||||
title = 'Current Network' |
||||
value = 'Kovan Test Network' |
||||
break |
||||
|
||||
case 'rinkeby': |
||||
title = 'Current Network' |
||||
value = 'Rinkeby Test Network' |
||||
break |
||||
|
||||
default: |
||||
title = 'Current RPC' |
||||
value = metamaskState.provider.rpcTarget |
||||
} |
||||
|
||||
return h('div', [ |
||||
h('span', {style: { fontWeight: 'bold', paddingRight: '10px'}}, title), |
||||
h('span', value), |
||||
]) |
||||
} |
@ -0,0 +1,21 @@ |
||||
/* |
||||
debug / dev |
||||
*/ |
||||
|
||||
#app-content { |
||||
border: 2px solid green; |
||||
} |
||||
|
||||
#design-container { |
||||
position: absolute; |
||||
left: 360px; |
||||
top: -42px; |
||||
width: calc(100vw - 360px); |
||||
height: 100vh; |
||||
overflow: scroll; |
||||
} |
||||
|
||||
#design-container img { |
||||
width: 2000px; |
||||
margin-right: 600px; |
||||
} |
@ -0,0 +1,36 @@ |
||||
@import url(https://fonts.googleapis.com/css?family=Roboto:300,500); |
||||
@import url(https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css); |
||||
|
||||
@font-face { |
||||
font-family: 'Montserrat Regular'; |
||||
src: url('/fonts/Montserrat/Montserrat-Regular.woff') format('woff'); |
||||
src: url('/fonts/Montserrat/Montserrat-Regular.ttf') format('truetype'); |
||||
font-weight: normal; |
||||
font-style: normal; |
||||
font-size: 'small'; |
||||
|
||||
} |
||||
|
||||
@font-face { |
||||
font-family: 'Montserrat Bold'; |
||||
src: url('/fonts/Montserrat/Montserrat-Bold.woff') format('woff'); |
||||
src: url('/fonts/Montserrat/Montserrat-Bold.ttf') format('truetype'); |
||||
font-weight: normal; |
||||
font-style: normal; |
||||
} |
||||
|
||||
@font-face { |
||||
font-family: 'Montserrat Light'; |
||||
src: url('/fonts/Montserrat/Montserrat-Light.woff') format('woff'); |
||||
src: url('/fonts/Montserrat/Montserrat-Light.ttf') format('truetype'); |
||||
font-weight: normal; |
||||
font-style: normal; |
||||
} |
||||
|
||||
@font-face { |
||||
font-family: 'Montserrat UltraLight'; |
||||
src: url('/fonts/Montserrat/Montserrat-UltraLight.woff') format('woff'); |
||||
src: url('/fonts/Montserrat/Montserrat-UltraLight.ttf') format('truetype'); |
||||
font-weight: normal; |
||||
font-style: normal; |
||||
} |
@ -0,0 +1,761 @@ |
||||
/* |
||||
faint orange (textfield shades) #FAF6F0 |
||||
light orange (button shades): #F5C26D |
||||
dark orange (text): #F5A623 |
||||
borders/font/any gray: #4A4A4A |
||||
*/ |
||||
|
||||
/* |
||||
application specific styles |
||||
*/ |
||||
|
||||
* { |
||||
box-sizing: border-box; |
||||
} |
||||
|
||||
html, body { |
||||
font-family: 'Montserrat Regular', Arial; |
||||
color: #4D4D4D; |
||||
font-weight: 300; |
||||
line-height: 1.4em; |
||||
background: #F7F7F7; |
||||
margin: 0; |
||||
padding: 0; |
||||
} |
||||
|
||||
html { |
||||
min-height: 500px; |
||||
} |
||||
|
||||
.app-root { |
||||
overflow: hidden; |
||||
position: relative |
||||
} |
||||
|
||||
.app-primary { |
||||
display: flex; |
||||
} |
||||
|
||||
input:focus, textarea:focus { |
||||
outline: none; |
||||
} |
||||
|
||||
.full-size { |
||||
height: 100%; |
||||
width: 100%; |
||||
} |
||||
|
||||
.full-width { |
||||
width: 100%; |
||||
} |
||||
|
||||
.full-height { |
||||
height: 100%; |
||||
} |
||||
|
||||
.full-flex-height { |
||||
display: flex; |
||||
flex: 1 1 auto; |
||||
flex-direction: column; |
||||
} |
||||
|
||||
#app-content { |
||||
overflow-x: hidden; |
||||
height: 100%; |
||||
display: flex; |
||||
flex-direction: column; |
||||
} |
||||
|
||||
button, input[type="submit"] { |
||||
font-family: 'Montserrat Bold'; |
||||
outline: none; |
||||
cursor: pointer; |
||||
padding: 8px 12px; |
||||
border: none; |
||||
color: white; |
||||
transform-origin: center center; |
||||
transition: transform 50ms ease-in; |
||||
/* default orange */ |
||||
background: rgba(247, 134, 28, 1); |
||||
box-shadow: 0px 3px 6px rgba(247, 134, 28, 0.36); |
||||
} |
||||
|
||||
.btn-green, input[type="submit"].btn-green { |
||||
background: rgba(106, 195, 96, 1); |
||||
box-shadow: 0px 3px 6px rgba(106, 195, 96, 0.36); |
||||
} |
||||
|
||||
.btn-red { |
||||
background: rgba(254, 35, 17, 1); |
||||
box-shadow: 0px 3px 6px rgba(254, 35, 17, 0.36); |
||||
} |
||||
|
||||
button[disabled], input[type="submit"][disabled] { |
||||
cursor: not-allowed; |
||||
background: rgba(197, 197, 197, 1); |
||||
box-shadow: 0px 3px 6px rgba(197, 197, 197, 0.36); |
||||
} |
||||
|
||||
button.spaced { |
||||
margin: 2px; |
||||
} |
||||
|
||||
button:not([disabled]):hover, input[type="submit"]:not([disabled]):hover { |
||||
transform: scale(1.1); |
||||
} |
||||
button:not([disabled]):active, input[type="submit"]:not([disabled]):active { |
||||
transform: scale(0.95); |
||||
} |
||||
|
||||
.grow-on-hover:hover { |
||||
transform: scale(1.05); |
||||
} |
||||
|
||||
a { |
||||
text-decoration: none; |
||||
color: inherit; |
||||
} |
||||
|
||||
a:hover{ |
||||
color: #df6b0e; |
||||
} |
||||
|
||||
/* |
||||
app |
||||
*/ |
||||
|
||||
.active { |
||||
color: #909090; |
||||
} |
||||
|
||||
button.primary { |
||||
padding: 8px 12px; |
||||
background: #F7861C; |
||||
box-shadow: 0px 3px 6px rgba(247, 134, 28, 0.36); |
||||
color: white; |
||||
font-size: 1.1em; |
||||
font-family: 'Montserrat Regular'; |
||||
text-transform: uppercase; |
||||
} |
||||
|
||||
button.btn-thin { |
||||
border: 1px solid; |
||||
border-color: #4D4D4D; |
||||
color: #4D4D4D; |
||||
background: rgb(255, 174, 41); |
||||
border-radius: 4px; |
||||
min-width: 200px; |
||||
margin: 12px 0; |
||||
padding: 6px; |
||||
font-size: 13px; |
||||
} |
||||
|
||||
.app-header { |
||||
padding: 6px 8px; |
||||
} |
||||
|
||||
.app-header h1 { |
||||
font-family: 'Montserrat Regular'; |
||||
text-transform: uppercase; |
||||
color: #AEAEAE; |
||||
} |
||||
|
||||
h2.page-subtitle { |
||||
font-family: 'Montserrat Regular'; |
||||
text-transform: uppercase; |
||||
color: #AEAEAE; |
||||
font-size: 1em; |
||||
margin: 12px; |
||||
} |
||||
|
||||
.app-footer { |
||||
padding-bottom: 10px; |
||||
align-items: center; |
||||
} |
||||
|
||||
.identicon { |
||||
height: 46px; |
||||
width: 46px; |
||||
background-size: cover; |
||||
border-radius: 100%; |
||||
border: 3px solid gray; |
||||
} |
||||
|
||||
textarea.twelve-word-phrase { |
||||
padding: 12px; |
||||
width: 300px; |
||||
height: 140px; |
||||
font-size: 16px; |
||||
background: white; |
||||
resize: none; |
||||
} |
||||
|
||||
.network-indicator { |
||||
display: flex; |
||||
align-items: center; |
||||
font-size: 0.6em; |
||||
|
||||
} |
||||
|
||||
.network-name { |
||||
width: 5.2em; |
||||
line-height: 9px; |
||||
text-rendering: geometricPrecision; |
||||
} |
||||
|
||||
.check { |
||||
margin-left: 12px; |
||||
color: #F7861C; |
||||
flex: 1 0 auto; |
||||
display: flex; |
||||
justify-content: flex-end; |
||||
} |
||||
/* |
||||
app sections |
||||
*/ |
||||
|
||||
/* initialize */ |
||||
|
||||
.initialize-screen hr { |
||||
width: 60px; |
||||
margin: 12px; |
||||
border-color: #F7861C; |
||||
border-style: solid; |
||||
} |
||||
|
||||
.initialize-screen label { |
||||
margin-top: 20px; |
||||
} |
||||
|
||||
.initialize-screen button.create-vault { |
||||
margin-top: 40px; |
||||
} |
||||
|
||||
.initialize-screen .warning { |
||||
font-size: 14px; |
||||
margin: 0 16px; |
||||
} |
||||
|
||||
/* unlock */ |
||||
.error { |
||||
color: #f7861c; |
||||
margin-bottom: 9px; |
||||
} |
||||
|
||||
.warning { |
||||
color: #FFAE00; |
||||
} |
||||
|
||||
.lock { |
||||
width: 50px; |
||||
height: 50px; |
||||
} |
||||
|
||||
.lock.locked { |
||||
transform: scale(1.5); |
||||
opacity: 0.0; |
||||
transition: opacity 400ms ease-in, transform 400ms ease-in; |
||||
} |
||||
.lock.unlocked { |
||||
transform: scale(1); |
||||
opacity: 1; |
||||
transition: opacity 500ms ease-out, transform 500ms ease-out, background 200ms ease-in; |
||||
} |
||||
|
||||
.lock.locked .lock-top { |
||||
transform: scaleX(1) translateX(0); |
||||
transition: transform 250ms ease-in; |
||||
} |
||||
.lock.unlocked .lock-top { |
||||
transform: scaleX(-1) translateX(-12px); |
||||
transition: transform 250ms ease-in; |
||||
} |
||||
.lock.unlocked:hover { |
||||
border-radius: 4px; |
||||
background: #e5e5e5; |
||||
border: 1px solid #b1b1b1; |
||||
} |
||||
.lock.unlocked:active { |
||||
background: #c3c3c3; |
||||
} |
||||
|
||||
.section-title .fa-arrow-left { |
||||
margin: -2px 8px 0px -8px; |
||||
} |
||||
|
||||
.unlock-screen #metamask-mascot-container { |
||||
margin-top: 24px; |
||||
} |
||||
|
||||
.unlock-screen h1 { |
||||
margin-top: -28px; |
||||
margin-bottom: 42px; |
||||
} |
||||
|
||||
.unlock-screen input[type=password] { |
||||
width: 260px; |
||||
/*height: 36px; |
||||
margin-bottom: 24px; |
||||
padding: 8px;*/ |
||||
} |
||||
|
||||
.sizing-input{ |
||||
font-size: 14px; |
||||
height: 30px; |
||||
padding-left: 5px; |
||||
} |
||||
.editable-label{ |
||||
display: flex; |
||||
} |
||||
/* Webkit */ |
||||
.unlock-screen input::-webkit-input-placeholder { |
||||
text-align: center; |
||||
font-size: 1.2em; |
||||
} |
||||
/* Firefox 18- */ |
||||
.unlock-screen input:-moz-placeholder { |
||||
text-align: center; |
||||
font-size: 1.2em; |
||||
} |
||||
/* Firefox 19+ */ |
||||
.unlock-screen input::-moz-placeholder { |
||||
text-align: center; |
||||
font-size: 1.2em; |
||||
} |
||||
/* IE */ |
||||
.unlock-screen input:-ms-input-placeholder { |
||||
text-align: center; |
||||
font-size: 1.2em; |
||||
} |
||||
|
||||
input.large-input, textarea.large-input { |
||||
/*margin-bottom: 24px;*/ |
||||
padding: 8px; |
||||
} |
||||
|
||||
input.large-input { |
||||
height: 36px; |
||||
} |
||||
|
||||
.letter-spacey { |
||||
letter-spacing: 0.1em; |
||||
} |
||||
|
||||
|
||||
|
||||
/* accounts */ |
||||
|
||||
.accounts-section { |
||||
margin: 0 0px; |
||||
} |
||||
|
||||
.accounts-section .horizontal-line { |
||||
margin: 0px 18px; |
||||
} |
||||
|
||||
.accounts-list-option { |
||||
height: 120px; |
||||
} |
||||
|
||||
.accounts-list-option .identicon-wrapper { |
||||
width: 100px; |
||||
} |
||||
|
||||
.unconftx-link { |
||||
margin-top: 24px; |
||||
cursor: pointer; |
||||
} |
||||
|
||||
.unconftx-link .fa-arrow-right { |
||||
margin: 0px -8px 0px 8px; |
||||
} |
||||
|
||||
/* identity panel */ |
||||
|
||||
.identity-panel { |
||||
font-weight: 500; |
||||
} |
||||
|
||||
.identity-panel .identicon-wrapper { |
||||
margin: 4px; |
||||
margin-top: 8px; |
||||
display: flex; |
||||
align-items: center; |
||||
} |
||||
|
||||
.identity-panel .identicon-wrapper span { |
||||
margin: 0 auto; |
||||
} |
||||
|
||||
.identity-panel .identity-data { |
||||
margin: 8px 8px 8px 18px; |
||||
} |
||||
|
||||
.identity-panel i { |
||||
margin-top: 32px; |
||||
margin-right: 6px; |
||||
color: #B9B9B9; |
||||
} |
||||
|
||||
.identity-panel .arrow-right { |
||||
padding-left: 18px; |
||||
width: 42px; |
||||
min-width: 18px; |
||||
height: 100%; |
||||
} |
||||
|
||||
.identity-copy.flex-column { |
||||
flex: 0.25 0 auto; |
||||
justify-content: center; |
||||
} |
||||
|
||||
/* accounts screen */ |
||||
|
||||
.identity-section { |
||||
|
||||
} |
||||
|
||||
.identity-section .identity-panel { |
||||
background: #E9E9E9; |
||||
border-bottom: 1px solid #B1B1B1; |
||||
cursor: pointer; |
||||
} |
||||
|
||||
.identity-section .identity-panel.selected { |
||||
background: white; |
||||
color: #F3C83E; |
||||
} |
||||
|
||||
.identity-section .identity-panel.selected .identicon { |
||||
border-color: orange; |
||||
} |
||||
|
||||
.identity-section .accounts-list-option:hover, |
||||
.identity-section .accounts-list-option.selected { |
||||
background:white; |
||||
} |
||||
|
||||
/* account detail screen */ |
||||
|
||||
.account-detail-section { |
||||
display: flex; |
||||
flex-wrap: wrap; |
||||
overflow-y: auto; |
||||
flex-direction: inherit; |
||||
|
||||
.name-label { |
||||
margin-left: 15px; |
||||
} |
||||
} |
||||
|
||||
.grow-tenx { |
||||
flex-grow: 10; |
||||
} |
||||
|
||||
.name-label{ |
||||
|
||||
} |
||||
|
||||
.unapproved-tx-icon { |
||||
height: 16px; |
||||
width: 16px; |
||||
background: rgb(47, 174, 244); |
||||
border-color: #AEAEAE; |
||||
border-radius: 13px; |
||||
} |
||||
|
||||
.edit-text { |
||||
height: 100%; |
||||
visibility: hidden; |
||||
} |
||||
.editing-label { |
||||
display: flex; |
||||
justify-content: flex-start; |
||||
margin-left: 50px; |
||||
margin-bottom: 2px; |
||||
font-size: 11px; |
||||
text-rendering: geometricPrecision; |
||||
color: #F7861C; |
||||
} |
||||
.name-label:hover .edit-text { |
||||
visibility: visible; |
||||
} |
||||
/* tx confirm */ |
||||
|
||||
.unconftx-section input[type=password] { |
||||
height: 22px; |
||||
padding: 2px; |
||||
margin: 12px; |
||||
margin-bottom: 24px; |
||||
border-radius: 4px; |
||||
border: 2px solid #F3C83E; |
||||
background: #FAF6F0; |
||||
} |
||||
|
||||
/* Send Screen */ |
||||
|
||||
.send-screen { |
||||
|
||||
} |
||||
|
||||
.send-screen section { |
||||
margin: 8px 16px; |
||||
} |
||||
|
||||
.send-screen input { |
||||
width: 100%; |
||||
font-size: 12px; |
||||
} |
||||
|
||||
/* Ether Balance Widget */ |
||||
|
||||
.ether-balance-amount { |
||||
color: #F7861C; |
||||
} |
||||
|
||||
.ether-balance-label { |
||||
color: #ABA9AA; |
||||
} |
||||
|
||||
/* Info screen */ |
||||
.info-gray{ |
||||
font-family: 'Montserrat Regular'; |
||||
text-transform: uppercase; |
||||
color: #AEAEAE; |
||||
} |
||||
|
||||
.icon-size{ |
||||
width: 20px; |
||||
} |
||||
|
||||
.info{ |
||||
font-family: 'Montserrat Regular', Arial; |
||||
padding-bottom: 10px; |
||||
display: inline-block; |
||||
padding-left: 5px; |
||||
} |
||||
|
||||
/* buy eth warning screen */ |
||||
.custom-radios { |
||||
justify-content: space-around; |
||||
align-items: center; |
||||
} |
||||
|
||||
|
||||
.custom-radio-selected { |
||||
width: 17px; |
||||
height: 17px; |
||||
border: solid; |
||||
border-style: double; |
||||
border-radius: 15px; |
||||
border-width: 5px; |
||||
background: rgba(247, 134, 28, 1); |
||||
border-color: #F7F7F7; |
||||
} |
||||
|
||||
.custom-radio-inactive { |
||||
width: 14px; |
||||
height: 14px; |
||||
border: solid; |
||||
border-width: 1px; |
||||
border-radius: 24px; |
||||
border-color: #AEAEAE; |
||||
} |
||||
|
||||
.radio-titles { |
||||
color: rgba(247, 134, 28, 1); |
||||
} |
||||
|
||||
.radio-titles-subtext { |
||||
|
||||
} |
||||
|
||||
.selected-exchange { |
||||
|
||||
} |
||||
|
||||
.buy-radio { |
||||
|
||||
} |
||||
|
||||
.eth-warning{ |
||||
transition: opacity 400ms ease-in, transform 400ms ease-in; |
||||
} |
||||
|
||||
.buy-subview{ |
||||
transition: opacity 400ms ease-in, transform 400ms ease-in; |
||||
} |
||||
|
||||
.input-container:hover .edit-text{ |
||||
visibility: visible; |
||||
} |
||||
|
||||
.buy-inputs{ |
||||
font-family: 'Montserrat Light'; |
||||
font-size: 13px; |
||||
height: 20px; |
||||
background: transparent; |
||||
box-sizing: border-box; |
||||
border: solid; |
||||
border-color: transparent; |
||||
border-width: 0.5px; |
||||
border-radius: 2px; |
||||
|
||||
} |
||||
.input-container:hover .buy-inputs{ |
||||
box-sizing: inherit; |
||||
border: solid; |
||||
border-color: #F7861C; |
||||
border-width: 0.5px; |
||||
border-radius: 2px; |
||||
} |
||||
|
||||
.buy-inputs:focus{ |
||||
border: solid; |
||||
border-color: #F7861C; |
||||
border-width: 0.5px; |
||||
border-radius: 2px; |
||||
} |
||||
|
||||
.activeForm { |
||||
background: #F7F7F7; |
||||
border: none; |
||||
border-radius: 8px 8px 0px 0px; |
||||
width: 50%; |
||||
text-align: center; |
||||
padding-bottom: 4px; |
||||
|
||||
} |
||||
|
||||
.inactiveForm { |
||||
border: none; |
||||
border-radius: 8px 8px 0px 0px; |
||||
width: 50%; |
||||
text-align: center; |
||||
padding-bottom: 4px; |
||||
} |
||||
|
||||
.ex-coins { |
||||
font-family: 'Montserrat Regular'; |
||||
text-transform: uppercase; |
||||
text-align: center; |
||||
font-size: 33px; |
||||
width: 118px; |
||||
height: 42px; |
||||
padding: 1px; |
||||
color: #4D4D4D; |
||||
} |
||||
|
||||
.marketinfo{ |
||||
font-family: 'Montserrat light'; |
||||
color: #AEAEAE; |
||||
font-size: 15px; |
||||
line-height: 17px; |
||||
} |
||||
|
||||
#fromCoin::-webkit-calendar-picker-indicator { |
||||
display: none; |
||||
} |
||||
|
||||
#coinList { |
||||
width: 400px; |
||||
height: 500px; |
||||
overflow: scroll; |
||||
} |
||||
|
||||
.icon-control .fa-refresh{ |
||||
visibility: hidden; |
||||
} |
||||
|
||||
.icon-control:hover .fa-refresh{ |
||||
visibility: visible; |
||||
} |
||||
|
||||
.icon-control:hover .fa-chevron-right{ |
||||
visibility: hidden; |
||||
} |
||||
|
||||
.inactive { |
||||
color: #AEAEAE; |
||||
} |
||||
|
||||
.inactive button{ |
||||
background: #AEAEAE; |
||||
color: white; |
||||
} |
||||
|
||||
.ellip-address { |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; |
||||
width: 5em; |
||||
font-size: 14px; |
||||
font-family: "Montserrat Light"; |
||||
margin-left: 5px; |
||||
} |
||||
|
||||
.qr-header { |
||||
font-size: 25px; |
||||
margin-top: 40px; |
||||
} |
||||
|
||||
.qr-message { |
||||
font-size: 12px; |
||||
color: #F7861C; |
||||
} |
||||
|
||||
div.message-container > div:first-child { |
||||
margin-top: 18px; |
||||
font-size: 15px; |
||||
color: #4D4D4D; |
||||
} |
||||
|
||||
.pop-hover:hover { |
||||
transform: scale(1.1); |
||||
} |
||||
|
||||
//Notification Modal |
||||
|
||||
.notification-modal-wrapper { |
||||
display: flex; |
||||
flex-direction: column; |
||||
justify-content: flex-start; |
||||
align-items: center; |
||||
position: relative; |
||||
border: 1px solid #dedede; |
||||
box-shadow: 0 0 2px 2px #dedede; |
||||
font-family: Roboto; |
||||
} |
||||
|
||||
.notification-modal-header { |
||||
background: #f6f6f6; |
||||
width: 100%; |
||||
display: flex; |
||||
justify-content: center; |
||||
padding: 30px; |
||||
font-size: 22px; |
||||
color: #1b344d; |
||||
height: 79px; |
||||
} |
||||
|
||||
.notification-modal-message { |
||||
padding: 20px; |
||||
} |
||||
|
||||
.notification-modal-message { |
||||
width: 100%; |
||||
display: flex; |
||||
justify-content: center; |
||||
font-size: 17px; |
||||
color: #1b344d; |
||||
} |
||||
|
||||
.modal-close-x::after { |
||||
content: '\00D7'; |
||||
font-size: 2em; |
||||
color: #9b9b9b; |
||||
position: absolute; |
||||
top: 25px; |
||||
right: 17.5px; |
||||
font-family: sans-serif; |
||||
cursor: pointer; |
||||
} |
@ -0,0 +1,306 @@ |
||||
/* color */ |
||||
|
||||
.color-orange { |
||||
color: #F7861C; |
||||
} |
||||
|
||||
.color-forest { |
||||
color: #0A5448; |
||||
} |
||||
|
||||
/* lib */ |
||||
|
||||
.full-width { |
||||
width: 100%; |
||||
} |
||||
|
||||
.full-height { |
||||
height: 100%; |
||||
} |
||||
|
||||
.flex-column { |
||||
display: flex; |
||||
flex-direction: column; |
||||
} |
||||
|
||||
.space-between { |
||||
justify-content: space-between; |
||||
} |
||||
|
||||
.space-around { |
||||
justify-content: space-around; |
||||
} |
||||
|
||||
.flex-column-bottom { |
||||
display: flex; |
||||
flex-direction: column-reverse; |
||||
} |
||||
|
||||
.flex-row { |
||||
display: flex; |
||||
flex-direction: row; |
||||
} |
||||
|
||||
.flex-space-between { |
||||
justify-content: space-between; |
||||
} |
||||
|
||||
.flex-space-around { |
||||
justify-content: space-around; |
||||
} |
||||
|
||||
.flex-right { |
||||
display: flex; |
||||
flex-direction: row; |
||||
justify-content: flex-end; |
||||
} |
||||
|
||||
.flex-left { |
||||
display: flex; |
||||
flex-direction: row; |
||||
justify-content: flex-start; |
||||
} |
||||
|
||||
.flex-fixed { |
||||
flex: none; |
||||
} |
||||
|
||||
.flex-basis-auto { |
||||
flex-basis: auto; |
||||
} |
||||
|
||||
.flex-grow { |
||||
flex: 1 1 auto; |
||||
} |
||||
|
||||
.flex-wrap { |
||||
flex-wrap: wrap; |
||||
} |
||||
|
||||
.flex-center { |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
} |
||||
|
||||
.flex-justify-center { |
||||
justify-content: center; |
||||
} |
||||
|
||||
.flex-align-center { |
||||
align-items: center; |
||||
} |
||||
|
||||
.flex-self-end { |
||||
align-self: flex-end; |
||||
} |
||||
|
||||
.flex-self-stretch { |
||||
align-self: stretch; |
||||
} |
||||
|
||||
.flex-vertical { |
||||
flex-direction: column; |
||||
} |
||||
|
||||
.z-bump { |
||||
z-index: 1; |
||||
} |
||||
|
||||
.select-none { |
||||
cursor: inherit; |
||||
-moz-user-select: none; |
||||
-webkit-user-select: none; |
||||
-ms-user-select: none; |
||||
user-select: none; |
||||
} |
||||
|
||||
.pointer { |
||||
cursor: pointer; |
||||
} |
||||
.cursor-pointer { |
||||
cursor: pointer; |
||||
transform-origin: center center; |
||||
transition: transform 50ms ease-in-out; |
||||
} |
||||
.cursor-pointer:hover { |
||||
transform: scale(1.1); |
||||
} |
||||
.cursor-pointer:active { |
||||
transform: scale(0.95); |
||||
} |
||||
|
||||
.cursor-disabled { |
||||
cursor: not-allowed; |
||||
} |
||||
|
||||
.margin-bottom-sml { |
||||
margin-bottom: 20px; |
||||
} |
||||
|
||||
.margin-bottom-med { |
||||
margin-bottom: 40px; |
||||
} |
||||
|
||||
.margin-right-left { |
||||
margin: 0 20px; |
||||
} |
||||
|
||||
.bold { |
||||
font-weight: bold; |
||||
} |
||||
|
||||
.text-transform-uppercase { |
||||
text-transform: uppercase; |
||||
} |
||||
|
||||
.font-small { |
||||
font-size: 12px; |
||||
} |
||||
|
||||
.font-medium { |
||||
font-size: 1.2em; |
||||
} |
||||
|
||||
hr.horizontal-line { |
||||
display: block; |
||||
height: 1px; |
||||
border: 0; |
||||
border-top: 1px solid #ccc; |
||||
margin: 1em 0; |
||||
padding: 0; |
||||
} |
||||
|
||||
.hover-white:hover { |
||||
background: white; |
||||
} |
||||
|
||||
.red-dot { |
||||
background: #E91550; |
||||
color: white; |
||||
border-radius: 10px; |
||||
} |
||||
|
||||
.diamond { |
||||
transform: rotate(45deg); |
||||
background: #038789; |
||||
} |
||||
|
||||
.hollow-diamond { |
||||
transform: rotate(45deg); |
||||
border: 3px solid #690496; |
||||
} |
||||
|
||||
.golden-square { |
||||
background: #EBB33F; |
||||
} |
||||
|
||||
.pending-dot { |
||||
background: red; |
||||
left: 14px; |
||||
top: 14px; |
||||
color: white; |
||||
border-radius: 10px; |
||||
height: 20px; |
||||
min-width: 20px; |
||||
position: relative; |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
padding: 4px; |
||||
z-index: 1; |
||||
} |
||||
|
||||
.keyring-label { |
||||
z-index: 1; |
||||
font-size: 11px; |
||||
background: rgba(255,0,0,0.8); |
||||
color: white; |
||||
bottom: 0px; |
||||
left: -8px; |
||||
border-radius: 10px; |
||||
height: 20px; |
||||
min-width: 20px; |
||||
position: absolute; |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
padding: 4px; |
||||
} |
||||
|
||||
.ether-balance { |
||||
display: flex; |
||||
align-items: center; |
||||
} |
||||
|
||||
.tabSection { |
||||
min-width: 350px; |
||||
} |
||||
|
||||
.menu-icon { |
||||
display: inline-block; |
||||
height: 12px; |
||||
min-width: 12px; |
||||
margin: 13px; |
||||
} |
||||
|
||||
i.fa.fa-question-circle.fa-lg.menu-icon { |
||||
font-size: 18px; |
||||
} |
||||
|
||||
.ether-icon { |
||||
background: rgb(0, 163, 68); |
||||
border-radius: 20px; |
||||
} |
||||
.testnet-icon { |
||||
background: #2465E1; |
||||
} |
||||
|
||||
.drop-menu-item { |
||||
display: flex; |
||||
align-items: center; |
||||
} |
||||
|
||||
.invisible { |
||||
visibility: hidden; |
||||
} |
||||
|
||||
.one-line-concat { |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; |
||||
white-space: nowrap; |
||||
} |
||||
|
||||
.critical-error { |
||||
text-align: center; |
||||
margin-top: 20px; |
||||
color: red; |
||||
} |
||||
|
||||
/* |
||||
Hacky breakpoint fix for account + tab sections |
||||
Resolves issue from @frankiebee in |
||||
https://github.com/MetaMask/metamask-extension/pull/1835 |
||||
Please remove this when integrating new designs |
||||
*/ |
||||
|
||||
@media screen and (min-width: 575px) and (max-width: 800px) { |
||||
.account-data-subsection { |
||||
flex: 0 0 auto !important; // reset flex |
||||
margin-left: 10px !important; // create additional horizontal space |
||||
margin-right: 10px !important; |
||||
width: 40%; |
||||
} |
||||
|
||||
.tabSection { |
||||
flex: 0 0 auto !important; |
||||
margin-left: 10px !important; |
||||
margin-right: 10px !important; |
||||
min-width: 285px; |
||||
width: 49%; |
||||
} |
||||
|
||||
.name-label { |
||||
width: 80%; |
||||
} |
||||
} |
File diff suppressed because one or more lines are too long
@ -0,0 +1,48 @@ |
||||
/* http://meyerweb.com/eric/tools/css/reset/ |
||||
v2.0 | 20110126 |
||||
License: none (public domain) |
||||
*/ |
||||
|
||||
html, body, div, span, applet, object, iframe, |
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre, |
||||
a, abbr, acronym, address, big, cite, code, |
||||
del, dfn, em, img, ins, kbd, q, s, samp, |
||||
small, strike, strong, sub, sup, tt, var, |
||||
b, u, i, center, |
||||
dl, dt, dd, ol, ul, li, |
||||
fieldset, form, label, legend, |
||||
table, caption, tbody, tfoot, thead, tr, th, td, |
||||
article, aside, canvas, details, embed, |
||||
figure, figcaption, footer, header, hgroup, |
||||
menu, nav, output, ruby, section, summary, |
||||
time, mark, audio, video { |
||||
margin: 0; |
||||
padding: 0; |
||||
border: 0; |
||||
font-size: 100%; |
||||
font: inherit; |
||||
vertical-align: baseline; |
||||
} |
||||
/* HTML5 display-role reset for older browsers */ |
||||
article, aside, details, figcaption, figure, |
||||
footer, header, hgroup, menu, nav, section { |
||||
display: block; |
||||
} |
||||
body { |
||||
line-height: 1; |
||||
} |
||||
ol, ul { |
||||
list-style: none; |
||||
} |
||||
blockquote, q { |
||||
quotes: none; |
||||
} |
||||
blockquote:before, blockquote:after, |
||||
q:before, q:after { |
||||
content: ''; |
||||
content: none; |
||||
} |
||||
table { |
||||
border-collapse: collapse; |
||||
border-spacing: 0; |
||||
} |
@ -0,0 +1,42 @@ |
||||
/* universal */ |
||||
.app-primary .main-enter { |
||||
position: absolute; |
||||
width: 100%; |
||||
} |
||||
|
||||
/* center position */ |
||||
.app-primary.from-right .main-enter-active, |
||||
.app-primary.from-left .main-enter-active { |
||||
overflow-x: hidden; |
||||
transform: translateX(0px); |
||||
transition: transform 300ms ease-in; |
||||
} |
||||
|
||||
/* exited positions */ |
||||
.app-primary.from-left .main-leave-active { |
||||
transform: translateX(360px); |
||||
transition: transform 300ms ease-in; |
||||
} |
||||
.app-primary.from-right .main-leave-active { |
||||
transform: translateX(-360px); |
||||
transition: transform 300ms ease-in; |
||||
} |
||||
|
||||
/* loader transitions */ |
||||
.loader-enter, .loader-leave-active { |
||||
opacity: 0.0; |
||||
transition: opacity 150 ease-in; |
||||
} |
||||
.loader-enter-active, .loader-leave { |
||||
opacity: 1.0; |
||||
transition: opacity 150 ease-in; |
||||
} |
||||
|
||||
/* entering positions */ |
||||
.app-primary.from-right .main-enter:not(.main-enter-active) { |
||||
transform: translateX(360px); |
||||
} |
||||
.app-primary.from-left .main-enter:not(.main-enter-active) { |
||||
transform: translateX(-360px); |
||||
} |
||||
|
@ -0,0 +1,179 @@ |
||||
const inherits = require('util').inherits |
||||
const EventEmitter = require('events').EventEmitter |
||||
const Component = require('react').Component |
||||
const connect = require('react-redux').connect |
||||
const h = require('react-hyperscript') |
||||
const Mascot = require('../components/mascot') |
||||
const actions = require('../../../ui/app/actions') |
||||
const Tooltip = require('../components/tooltip') |
||||
const getCaretCoordinates = require('textarea-caret') |
||||
|
||||
module.exports = connect(mapStateToProps)(InitializeMenuScreen) |
||||
|
||||
inherits(InitializeMenuScreen, Component) |
||||
function InitializeMenuScreen () { |
||||
Component.call(this) |
||||
this.animationEventEmitter = new EventEmitter() |
||||
} |
||||
|
||||
function mapStateToProps (state) { |
||||
return { |
||||
// state from plugin
|
||||
currentView: state.appState.currentView, |
||||
warning: state.appState.warning, |
||||
} |
||||
} |
||||
|
||||
InitializeMenuScreen.prototype.render = function () { |
||||
var state = this.props |
||||
|
||||
switch (state.currentView.name) { |
||||
|
||||
default: |
||||
return this.renderMenu(state) |
||||
|
||||
} |
||||
} |
||||
|
||||
// InitializeMenuScreen.prototype.componentDidMount = function(){
|
||||
// document.getElementById('password-box').focus()
|
||||
// }
|
||||
|
||||
InitializeMenuScreen.prototype.renderMenu = function (state) { |
||||
return ( |
||||
|
||||
h('.initialize-screen.flex-column.flex-center.flex-grow', [ |
||||
|
||||
h(Mascot, { |
||||
animationEventEmitter: this.animationEventEmitter, |
||||
}), |
||||
|
||||
h('h1', { |
||||
style: { |
||||
fontSize: '1.3em', |
||||
textTransform: 'uppercase', |
||||
color: '#7F8082', |
||||
marginBottom: 10, |
||||
}, |
||||
}, 'MetaMask'), |
||||
|
||||
|
||||
h('div', [ |
||||
h('h3', { |
||||
style: { |
||||
fontSize: '0.8em', |
||||
color: '#7F8082', |
||||
display: 'inline', |
||||
}, |
||||
}, 'Encrypt your new DEN'), |
||||
|
||||
h(Tooltip, { |
||||
title: 'Your DEN is your password-encrypted storage within MetaMask.', |
||||
}, [ |
||||
h('i.fa.fa-question-circle.pointer', { |
||||
style: { |
||||
fontSize: '18px', |
||||
position: 'relative', |
||||
color: 'rgb(247, 134, 28)', |
||||
top: '2px', |
||||
marginLeft: '4px', |
||||
}, |
||||
}), |
||||
]), |
||||
]), |
||||
|
||||
h('span.in-progress-notification', state.warning), |
||||
|
||||
// password
|
||||
h('input.large-input.letter-spacey', { |
||||
type: 'password', |
||||
id: 'password-box', |
||||
placeholder: 'New Password (min 8 chars)', |
||||
onInput: this.inputChanged.bind(this), |
||||
style: { |
||||
width: 260, |
||||
marginTop: 12, |
||||
}, |
||||
}), |
||||
|
||||
// confirm password
|
||||
h('input.large-input.letter-spacey', { |
||||
type: 'password', |
||||
id: 'password-box-confirm', |
||||
placeholder: 'Confirm Password', |
||||
onKeyPress: this.createVaultOnEnter.bind(this), |
||||
onInput: this.inputChanged.bind(this), |
||||
style: { |
||||
width: 260, |
||||
marginTop: 16, |
||||
}, |
||||
}), |
||||
|
||||
|
||||
h('button.primary', { |
||||
onClick: this.createNewVaultAndKeychain.bind(this), |
||||
style: { |
||||
margin: 12, |
||||
}, |
||||
}, 'Create'), |
||||
|
||||
h('.flex-row.flex-center.flex-grow', [ |
||||
h('p.pointer', { |
||||
onClick: this.showRestoreVault.bind(this), |
||||
style: { |
||||
fontSize: '0.8em', |
||||
color: 'rgb(247, 134, 28)', |
||||
textDecoration: 'underline', |
||||
}, |
||||
}, 'Import Existing DEN'), |
||||
]), |
||||
|
||||
]) |
||||
) |
||||
} |
||||
|
||||
InitializeMenuScreen.prototype.createVaultOnEnter = function (event) { |
||||
if (event.key === 'Enter') { |
||||
event.preventDefault() |
||||
this.createNewVaultAndKeychain() |
||||
} |
||||
} |
||||
|
||||
InitializeMenuScreen.prototype.componentDidMount = function () { |
||||
document.getElementById('password-box').focus() |
||||
} |
||||
|
||||
InitializeMenuScreen.prototype.showRestoreVault = function () { |
||||
this.props.dispatch(actions.showRestoreVault()) |
||||
} |
||||
|
||||
InitializeMenuScreen.prototype.createNewVaultAndKeychain = function () { |
||||
var passwordBox = document.getElementById('password-box') |
||||
var password = passwordBox.value |
||||
var passwordConfirmBox = document.getElementById('password-box-confirm') |
||||
var passwordConfirm = passwordConfirmBox.value |
||||
|
||||
if (password.length < 8) { |
||||
this.warning = 'password not long enough' |
||||
this.props.dispatch(actions.displayWarning(this.warning)) |
||||
return |
||||
} |
||||
if (password !== passwordConfirm) { |
||||
this.warning = 'passwords don\'t match' |
||||
this.props.dispatch(actions.displayWarning(this.warning)) |
||||
return |
||||
} |
||||
|
||||
this.props.dispatch(actions.createNewVaultAndKeychain(password)) |
||||
} |
||||
|
||||
InitializeMenuScreen.prototype.inputChanged = function (event) { |
||||
// tell mascot to look at page action
|
||||
var element = event.target |
||||
var boundingRect = element.getBoundingClientRect() |
||||
var coordinates = getCaretCoordinates(element, element.selectionEnd) |
||||
this.animationEventEmitter.emit('point', { |
||||
x: boundingRect.left + coordinates.left - element.scrollLeft, |
||||
y: boundingRect.top + coordinates.top - element.scrollTop, |
||||
}) |
||||
} |
After Width: | Height: | Size: 138 KiB |
After Width: | Height: | Size: 380 KiB |
@ -0,0 +1,155 @@ |
||||
const inherits = require('util').inherits |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const connect = require('react-redux').connect |
||||
const actions = require('../../ui/app/actions') |
||||
|
||||
module.exports = connect(mapStateToProps)(InfoScreen) |
||||
|
||||
function mapStateToProps (state) { |
||||
return {} |
||||
} |
||||
|
||||
inherits(InfoScreen, Component) |
||||
function InfoScreen () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
InfoScreen.prototype.render = function () { |
||||
const state = this.props |
||||
const version = global.platform.getVersion() |
||||
|
||||
return ( |
||||
h('.flex-column.flex-grow', { |
||||
style: { |
||||
maxWidth: '400px', |
||||
}, |
||||
}, [ |
||||
|
||||
// subtitle and nav
|
||||
h('.section-title.flex-row.flex-center', [ |
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', { |
||||
onClick: (event) => { |
||||
state.dispatch(actions.goHome()) |
||||
}, |
||||
}), |
||||
h('h2.page-subtitle', 'Info'), |
||||
]), |
||||
|
||||
// main view
|
||||
h('.flex-column.flex-justify-center.flex-grow.select-none', [ |
||||
h('.flex-space-around', { |
||||
style: { |
||||
padding: '20px', |
||||
}, |
||||
}, [ |
||||
// current version number
|
||||
|
||||
h('.info.info-gray', [ |
||||
h('div', 'Metamask'), |
||||
h('div', { |
||||
style: { |
||||
marginBottom: '10px', |
||||
}, |
||||
}, `Version: ${version}`), |
||||
]), |
||||
|
||||
h('div', { |
||||
style: { |
||||
marginBottom: '5px', |
||||
}}, |
||||
[ |
||||
h('div', [ |
||||
h('a', { |
||||
href: 'https://metamask.io/privacy.html', |
||||
target: '_blank', |
||||
onClick (event) { this.navigateTo(event.target.href) }, |
||||
}, [ |
||||
h('div.info', 'Privacy Policy'), |
||||
]), |
||||
]), |
||||
h('div', [ |
||||
h('a', { |
||||
href: 'https://metamask.io/terms.html', |
||||
target: '_blank', |
||||
onClick (event) { this.navigateTo(event.target.href) }, |
||||
}, [ |
||||
h('div.info', 'Terms of Use'), |
||||
]), |
||||
]), |
||||
h('div', [ |
||||
h('a', { |
||||
href: 'https://metamask.io/attributions.html', |
||||
target: '_blank', |
||||
onClick (event) { this.navigateTo(event.target.href) }, |
||||
}, [ |
||||
h('div.info', 'Attributions'), |
||||
]), |
||||
]), |
||||
] |
||||
), |
||||
|
||||
h('hr', { |
||||
style: { |
||||
margin: '10px 0 ', |
||||
width: '7em', |
||||
}, |
||||
}), |
||||
|
||||
h('div', { |
||||
style: { |
||||
paddingLeft: '30px', |
||||
}}, |
||||
[ |
||||
h('div.fa.fa-support', [ |
||||
h('a.info', { |
||||
href: 'https://support.metamask.io', |
||||
target: '_blank', |
||||
}, 'Visit our Support Center'), |
||||
]), |
||||
|
||||
h('div', [ |
||||
h('a', { |
||||
href: 'https://metamask.io/', |
||||
target: '_blank', |
||||
}, [ |
||||
h('img.icon-size', { |
||||
src: 'images/icon-128.png', |
||||
style: { |
||||
// IE6-9
|
||||
filter: 'grayscale(100%)', |
||||
// Microsoft Edge and Firefox 35+
|
||||
WebkitFilter: 'grayscale(100%)', |
||||
}, |
||||
}), |
||||
h('div.info', 'Visit our web site'), |
||||
]), |
||||
]), |
||||
|
||||
h('div', [ |
||||
h('.fa.fa-twitter', [ |
||||
h('a.info', { |
||||
href: 'https://twitter.com/metamask_io', |
||||
target: '_blank', |
||||
}, 'Follow us on Twitter'), |
||||
]), |
||||
]), |
||||
|
||||
h('div.fa.fa-envelope', [ |
||||
h('a.info', { |
||||
target: '_blank', |
||||
style: { width: '85vw' }, |
||||
href: 'mailto:help@metamask.io?subject=Feedback', |
||||
}, 'Email us!'), |
||||
]), |
||||
]), |
||||
]), |
||||
]), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
InfoScreen.prototype.navigateTo = function (url) { |
||||
global.platform.openWindow({ url }) |
||||
} |
||||
|
@ -0,0 +1,653 @@ |
||||
{ |
||||
"objects": [ |
||||
{ |
||||
"symbol": "ethaud", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "aud", |
||||
"name": "Australian Dollar" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethhkd", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "hkd", |
||||
"name": "Hong Kong Dollar" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethsgd", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "sgd", |
||||
"name": "Singapore Dollar" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethidr", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "idr", |
||||
"name": "Indonesian Rupiah" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethphp", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "php", |
||||
"name": "Philippine Peso" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "eth1st", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "1st", |
||||
"name": "FirstBlood" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethadt", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "adt", |
||||
"name": "adToken" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethadx", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "adx", |
||||
"name": "AdEx" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethant", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "ant", |
||||
"name": "Aragon" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethbat", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "bat", |
||||
"name": "Basic Attention Token" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethbnt", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "bnt", |
||||
"name": "Bancor" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethbtc", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "btc", |
||||
"name": "Bitcoin" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethcad", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "cad", |
||||
"name": "Canadian Dollar" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethcfi", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "cfi", |
||||
"name": "Cofound.it" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethcrb", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "crb", |
||||
"name": "CreditBit" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethcvc", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "cvc", |
||||
"name": "Civic" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethdash", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "dash", |
||||
"name": "Dash" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethdgd", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "dgd", |
||||
"name": "DigixDAO" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethetc", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "etc", |
||||
"name": "Ethereum Classic" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "etheur", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "eur", |
||||
"name": "Euro" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethfun", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "fun", |
||||
"name": "FunFair" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethgbp", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "gbp", |
||||
"name": "Pound Sterling" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethgno", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "gno", |
||||
"name": "Gnosis" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethgnt", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "gnt", |
||||
"name": "Golem" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethgup", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "gup", |
||||
"name": "Matchpool" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethhmq", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "hmq", |
||||
"name": "Humaniq" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethjpy", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "jpy", |
||||
"name": "Japanese Yen" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethlgd", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "lgd", |
||||
"name": "Legends Room" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethlsk", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "lsk", |
||||
"name": "Lisk" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethltc", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "ltc", |
||||
"name": "Litecoin" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethlun", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "lun", |
||||
"name": "Lunyr" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethmco", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "mco", |
||||
"name": "Monaco" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethmtl", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "mtl", |
||||
"name": "Metal" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethmyst", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "myst", |
||||
"name": "Mysterium" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethnmr", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "nmr", |
||||
"name": "Numeraire" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethomg", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "omg", |
||||
"name": "OmiseGO" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethpay", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "pay", |
||||
"name": "TenX" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethptoy", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "ptoy", |
||||
"name": "Patientory" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethqrl", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "qrl", |
||||
"name": "Quantum-Resistant Ledger" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethqtum", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "qtum", |
||||
"name": "Qtum" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethrep", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "rep", |
||||
"name": "Augur" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethrlc", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "rlc", |
||||
"name": "iEx.ec" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethrub", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "rub", |
||||
"name": "Russian Ruble" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethsc", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "sc", |
||||
"name": "Siacoin" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethsngls", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "sngls", |
||||
"name": "SingularDTV" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethsnt", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "snt", |
||||
"name": "Status" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethsteem", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "steem", |
||||
"name": "Steem" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethstorj", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "storj", |
||||
"name": "Storj" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethtime", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "time", |
||||
"name": "ChronoBank" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethtkn", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "tkn", |
||||
"name": "TokenCard" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethtrst", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "trst", |
||||
"name": "WeTrust" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethuah", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "uah", |
||||
"name": "Ukrainian Hryvnia" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethusd", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "usd", |
||||
"name": "United States Dollar" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethwings", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "wings", |
||||
"name": "Wings" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethxem", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "xem", |
||||
"name": "NEM" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethxlm", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "xlm", |
||||
"name": "Stellar Lumen" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethxmr", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "xmr", |
||||
"name": "Monero" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethxrp", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "xrp", |
||||
"name": "Ripple" |
||||
} |
||||
}, |
||||
{ |
||||
"symbol": "ethzec", |
||||
"base": { |
||||
"code": "eth", |
||||
"name": "Ethereum" |
||||
}, |
||||
"quote": { |
||||
"code": "zec", |
||||
"name": "Zcash" |
||||
} |
||||
} |
||||
] |
||||
} |
@ -0,0 +1,91 @@ |
||||
const inherits = require('util').inherits |
||||
const Component = require('react').Component |
||||
const connect = require('react-redux').connect |
||||
const h = require('react-hyperscript') |
||||
const actions = require('../../../../ui/app/actions') |
||||
const exportAsFile = require('../../util').exportAsFile |
||||
|
||||
module.exports = connect(mapStateToProps)(CreateVaultCompleteScreen) |
||||
|
||||
inherits(CreateVaultCompleteScreen, Component) |
||||
function CreateVaultCompleteScreen () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
function mapStateToProps (state) { |
||||
return { |
||||
seed: state.appState.currentView.seedWords, |
||||
cachedSeed: state.metamask.seedWords, |
||||
} |
||||
} |
||||
|
||||
CreateVaultCompleteScreen.prototype.render = function () { |
||||
var state = this.props |
||||
var seed = state.seed || state.cachedSeed || '' |
||||
|
||||
return ( |
||||
|
||||
h('.initialize-screen.flex-column.flex-center.flex-grow', [ |
||||
|
||||
// // subtitle and nav
|
||||
// h('.section-title.flex-row.flex-center', [
|
||||
// h('h2.page-subtitle', 'Vault Created'),
|
||||
// ]),
|
||||
|
||||
h('h3.flex-center.text-transform-uppercase', { |
||||
style: { |
||||
background: '#EBEBEB', |
||||
color: '#AEAEAE', |
||||
marginTop: 36, |
||||
marginBottom: 8, |
||||
width: '100%', |
||||
fontSize: '20px', |
||||
padding: 6, |
||||
}, |
||||
}, [ |
||||
'Vault Created', |
||||
]), |
||||
|
||||
h('div', { |
||||
style: { |
||||
fontSize: '1em', |
||||
marginTop: '10px', |
||||
textAlign: 'center', |
||||
}, |
||||
}, [ |
||||
h('span.error', 'These 12 words are the only way to restore your MetaMask accounts.\nSave them somewhere safe and secret.'), |
||||
]), |
||||
|
||||
h('textarea.twelve-word-phrase', { |
||||
readOnly: true, |
||||
value: seed, |
||||
}), |
||||
|
||||
h('button.primary', { |
||||
onClick: () => this.confirmSeedWords() |
||||
.then(account => this.showAccountDetail(account)), |
||||
style: { |
||||
margin: '24px', |
||||
fontSize: '0.9em', |
||||
marginBottom: '10px', |
||||
}, |
||||
}, 'I\'ve copied it somewhere safe'), |
||||
|
||||
h('button.primary', { |
||||
onClick: () => exportAsFile(`MetaMask Seed Words`, seed), |
||||
style: { |
||||
margin: '10px', |
||||
fontSize: '0.9em', |
||||
}, |
||||
}, 'Save Seed Words As File'), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
CreateVaultCompleteScreen.prototype.confirmSeedWords = function () { |
||||
return this.props.dispatch(actions.confirmSeedWords()) |
||||
} |
||||
|
||||
CreateVaultCompleteScreen.prototype.showAccountDetail = function (account) { |
||||
return this.props.dispatch(actions.showAccountDetail(account)) |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue