Merge branch 'e2e-tests' of https://github.com/tmashuang/metamask-extension into e2e-tests
commit
025d8e7983
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.7 KiB |
@ -0,0 +1,92 @@ |
||||
# Transaction Controller |
||||
|
||||
Transaction Controller is an aggregate of sub-controllers and trackers |
||||
exposed to the MetaMask controller. |
||||
|
||||
- txStateManager |
||||
responsible for the state of a transaction and |
||||
storing the transaction |
||||
- pendingTxTracker |
||||
watching blocks for transactions to be include |
||||
and emitting confirmed events |
||||
- txGasUtil |
||||
gas calculations and safety buffering |
||||
- nonceTracker |
||||
calculating nonces |
||||
|
||||
## Flow diagram of processing a transaction |
||||
|
||||
![transaction-flow](../../../../docs/transaction-flow.png) |
||||
|
||||
## txMeta's & txParams |
||||
|
||||
A txMeta is the "meta" object it has all the random bits of info we need about a transaction on it. txParams are sacred every thing on txParams gets signed so it must |
||||
be a valid key and be hex prefixed except for the network number. Extra stuff must go on the txMeta! |
||||
|
||||
Here is a txMeta too look at: |
||||
|
||||
```js |
||||
txMeta = { |
||||
"id": 2828415030114568, // unique id for this txMeta used for look ups |
||||
"time": 1524094064821, // time of creation |
||||
"status": "confirmed", |
||||
"metamaskNetworkId": "1524091532133", //the network id for the transaction |
||||
"loadingDefaults": false, // used to tell the ui when we are done calculatyig gass defaults |
||||
"txParams": { // the txParams object |
||||
"from": "0x8acce2391c0d510a6c5e5d8f819a678f79b7e675", |
||||
"to": "0x8acce2391c0d510a6c5e5d8f819a678f79b7e675", |
||||
"value": "0x0", |
||||
"gasPrice": "0x3b9aca00", |
||||
"gas": "0x7b0c", |
||||
"nonce": "0x0" |
||||
}, |
||||
"history": [{ //debug |
||||
"id": 2828415030114568, |
||||
"time": 1524094064821, |
||||
"status": "unapproved", |
||||
"metamaskNetworkId": "1524091532133", |
||||
"loadingDefaults": true, |
||||
"txParams": { |
||||
"from": "0x8acce2391c0d510a6c5e5d8f819a678f79b7e675", |
||||
"to": "0x8acce2391c0d510a6c5e5d8f819a678f79b7e675", |
||||
"value": "0x0" |
||||
} |
||||
}, |
||||
[ |
||||
{ |
||||
"op": "add", |
||||
"path": "/txParams/gasPrice", |
||||
"value": "0x3b9aca00" |
||||
}, |
||||
...], // I've removed most of history for this |
||||
"gasPriceSpecified": false, //whether or not the user/dapp has specified gasPrice |
||||
"gasLimitSpecified": false, //whether or not the user/dapp has specified gas |
||||
"estimatedGas": "5208", |
||||
"origin": "MetaMask", //debug |
||||
"nonceDetails": { |
||||
"params": { |
||||
"highestLocallyConfirmed": 0, |
||||
"highestSuggested": 0, |
||||
"nextNetworkNonce": 0 |
||||
}, |
||||
"local": { |
||||
"name": "local", |
||||
"nonce": 0, |
||||
"details": { |
||||
"startPoint": 0, |
||||
"highest": 0 |
||||
} |
||||
}, |
||||
"network": { |
||||
"name": "network", |
||||
"nonce": 0, |
||||
"details": { |
||||
"baseCount": 0 |
||||
} |
||||
} |
||||
}, |
||||
"rawTx": "0xf86980843b9aca00827b0c948acce2391c0d510a6c5e5d8f819a678f79b7e67580808602c5b5de66eea05c01a320b96ac730cb210ca56d2cb71fa360e1fc2c21fa5cf333687d18eb323fa02ed05987a6e5fd0f2459fcff80710b76b83b296454ad9a37594a0ccb4643ea90", // used for rebroadcast |
||||
"hash": "0xa45ba834b97c15e6ff4ed09badd04ecd5ce884b455eb60192cdc73bcc583972a", |
||||
"submittedTime": 1524094077902 // time of the attempt to submit the raw tx to the network, used in the ui to show the retry button |
||||
} |
||||
``` |
@ -0,0 +1,99 @@ |
||||
const { |
||||
addHexPrefix, |
||||
isValidAddress, |
||||
} = require('ethereumjs-util') |
||||
|
||||
/** |
||||
@module |
||||
*/ |
||||
module.exports = { |
||||
normalizeTxParams, |
||||
validateTxParams, |
||||
validateFrom, |
||||
validateRecipient, |
||||
getFinalStates, |
||||
} |
||||
|
||||
|
||||
// functions that handle normalizing of that key in txParams
|
||||
const normalizers = { |
||||
from: from => addHexPrefix(from).toLowerCase(), |
||||
to: to => addHexPrefix(to).toLowerCase(), |
||||
nonce: nonce => addHexPrefix(nonce), |
||||
value: value => addHexPrefix(value), |
||||
data: data => addHexPrefix(data), |
||||
gas: gas => addHexPrefix(gas), |
||||
gasPrice: gasPrice => addHexPrefix(gasPrice), |
||||
} |
||||
|
||||
/** |
||||
normalizes txParams |
||||
@param txParams {object} |
||||
@returns {object} normalized txParams |
||||
*/ |
||||
function normalizeTxParams (txParams) { |
||||
// apply only keys in the normalizers
|
||||
const normalizedTxParams = {} |
||||
for (const key in normalizers) { |
||||
if (txParams[key]) normalizedTxParams[key] = normalizers[key](txParams[key]) |
||||
} |
||||
return normalizedTxParams |
||||
} |
||||
|
||||
/** |
||||
validates txParams |
||||
@param txParams {object} |
||||
*/ |
||||
function validateTxParams (txParams) { |
||||
validateFrom(txParams) |
||||
validateRecipient(txParams) |
||||
if ('value' in txParams) { |
||||
const value = txParams.value.toString() |
||||
if (value.includes('-')) { |
||||
throw new Error(`Invalid transaction value of ${txParams.value} not a positive number.`) |
||||
} |
||||
|
||||
if (value.includes('.')) { |
||||
throw new Error(`Invalid transaction value of ${txParams.value} number must be in wei`) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
validates the from field in txParams |
||||
@param txParams {object} |
||||
*/ |
||||
function validateFrom (txParams) { |
||||
if (!(typeof txParams.from === 'string')) throw new Error(`Invalid from address ${txParams.from} not a string`) |
||||
if (!isValidAddress(txParams.from)) throw new Error('Invalid from address') |
||||
} |
||||
|
||||
/** |
||||
validates the to field in txParams |
||||
@param txParams {object} |
||||
*/ |
||||
function validateRecipient (txParams) { |
||||
if (txParams.to === '0x' || txParams.to === null) { |
||||
if (txParams.data) { |
||||
delete txParams.to |
||||
} else { |
||||
throw new Error('Invalid recipient address') |
||||
} |
||||
} else if (txParams.to !== undefined && !isValidAddress(txParams.to)) { |
||||
throw new Error('Invalid recipient address') |
||||
} |
||||
return txParams |
||||
} |
||||
|
||||
/** |
||||
@returns an {array} of states that can be considered final |
||||
*/ |
||||
function getFinalStates () { |
||||
return [ |
||||
'rejected', // the user has responded no!
|
||||
'confirmed', // the tx has been included in a block.
|
||||
'failed', // the tx failed for some reason, included on tx data.
|
||||
'dropped', // the tx nonce was already used
|
||||
] |
||||
} |
||||
|
@ -0,0 +1,49 @@ |
||||
const fs = require('fs') |
||||
const { SourceMapConsumer } = require('source-map') |
||||
|
||||
//
|
||||
// Utility to help check if sourcemaps are working
|
||||
//
|
||||
// searches `dist/chrome/inpage.js` for "new Error" statements
|
||||
// and prints their source lines using the sourcemaps.
|
||||
// if not working it may error or print minified garbage
|
||||
//
|
||||
|
||||
start() |
||||
|
||||
async function start() { |
||||
const rawBuild = fs.readFileSync(__dirname + '/../dist/chrome/inpage.js', 'utf8') |
||||
const rawSourceMap = fs.readFileSync(__dirname + '/../dist/sourcemaps/inpage.js.map', 'utf8') |
||||
const consumer = await new SourceMapConsumer(rawSourceMap) |
||||
|
||||
console.log('hasContentsOfAllSources:', consumer.hasContentsOfAllSources(), '\n') |
||||
console.log('sources:') |
||||
consumer.sources.map((sourcePath) => console.log(sourcePath)) |
||||
|
||||
console.log('\nexamining "new Error" statements:\n') |
||||
const sourceLines = rawBuild.split('\n') |
||||
sourceLines.map(line => indicesOf('new Error', line)) |
||||
.forEach((errorIndices, lineIndex) => { |
||||
// if (errorIndex === null) return console.log('line does not contain "new Error"')
|
||||
errorIndices.forEach((errorIndex) => { |
||||
const position = { line: lineIndex + 1, column: errorIndex } |
||||
const result = consumer.originalPositionFor(position) |
||||
if (!result.source) return console.warn(`!! missing source for position: ${position}`) |
||||
// filter out deps distributed minified without sourcemaps
|
||||
if (result.source === 'node_modules/browserify/node_modules/browser-pack/_prelude.js') return // minified mess
|
||||
if (result.source === 'node_modules/web3/dist/web3.min.js') return // minified mess
|
||||
const sourceContent = consumer.sourceContentFor(result.source) |
||||
const sourceLines = sourceContent.split('\n') |
||||
const line = sourceLines[result.line-1] |
||||
console.log(`\n========================== ${result.source} ====================================\n`) |
||||
console.log(line) |
||||
console.log(`\n==============================================================================\n`) |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
function indicesOf(substring, string) { |
||||
var a=[],i=-1; |
||||
while((i=string.indexOf(substring,i+1)) >= 0) a.push(i); |
||||
return a; |
||||
} |
After Width: | Height: | Size: 138 KiB |
@ -1,6 +1,6 @@ |
||||
MetaMask is beta software. |
||||
|
||||
When you log in to MetaMask, your current account is visible to every new site you visit. |
||||
When you log in to MetaMask, your current account's address is visible to every new site you visit. This can be used to look up your account balances of Ether and other tokens. |
||||
|
||||
For your privacy, for now, please sign out of MetaMask when you're done using a site. |
||||
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -1,14 +1,77 @@ |
||||
const assert = require('assert') |
||||
const TxGasUtils = require('../../app/scripts/lib/tx-gas-utils') |
||||
const { createTestProviderTools } = require('../stub/provider') |
||||
|
||||
describe('Tx Gas Util', function () { |
||||
let txGasUtil, provider, providerResultStub |
||||
beforeEach(function () { |
||||
providerResultStub = {} |
||||
provider = createTestProviderTools({ scaffold: providerResultStub }).provider |
||||
txGasUtil = new TxGasUtils({ |
||||
provider, |
||||
const Transaction = require('ethereumjs-tx') |
||||
const BN = require('bn.js') |
||||
|
||||
|
||||
const { hexToBn, bnToHex } = require('../../app/scripts/lib/util') |
||||
const TxUtils = require('../../app/scripts/controllers/transactions/tx-gas-utils') |
||||
|
||||
|
||||
describe('txUtils', function () { |
||||
let txUtils |
||||
|
||||
before(function () { |
||||
txUtils = new TxUtils(new Proxy({}, { |
||||
get: (obj, name) => { |
||||
return () => {} |
||||
}, |
||||
})) |
||||
}) |
||||
|
||||
describe('chain Id', function () { |
||||
it('prepares a transaction with the provided chainId', function () { |
||||
const txParams = { |
||||
to: '0x70ad465e0bab6504002ad58c744ed89c7da38524', |
||||
from: '0x69ad465e0bab6504002ad58c744ed89c7da38525', |
||||
value: '0x0', |
||||
gas: '0x7b0c', |
||||
gasPrice: '0x199c82cc00', |
||||
data: '0x', |
||||
nonce: '0x3', |
||||
chainId: 42, |
||||
} |
||||
const ethTx = new Transaction(txParams) |
||||
assert.equal(ethTx.getChainId(), 42, 'chainId is set from tx params') |
||||
}) |
||||
}) |
||||
|
||||
describe('addGasBuffer', function () { |
||||
it('multiplies by 1.5, when within block gas limit', function () { |
||||
// naive estimatedGas: 0x16e360 (1.5 mil)
|
||||
const inputHex = '0x16e360' |
||||
// dummy gas limit: 0x3d4c52 (4 mil)
|
||||
const blockGasLimitHex = '0x3d4c52' |
||||
const output = txUtils.addGasBuffer(inputHex, blockGasLimitHex) |
||||
const inputBn = hexToBn(inputHex) |
||||
const outputBn = hexToBn(output) |
||||
const expectedBn = inputBn.muln(1.5) |
||||
assert(outputBn.eq(expectedBn), 'returns 1.5 the input value') |
||||
}) |
||||
|
||||
it('uses original estimatedGas, when above block gas limit', function () { |
||||
// naive estimatedGas: 0x16e360 (1.5 mil)
|
||||
const inputHex = '0x16e360' |
||||
// dummy gas limit: 0x0f4240 (1 mil)
|
||||
const blockGasLimitHex = '0x0f4240' |
||||
const output = txUtils.addGasBuffer(inputHex, blockGasLimitHex) |
||||
// const inputBn = hexToBn(inputHex)
|
||||
const outputBn = hexToBn(output) |
||||
const expectedBn = hexToBn(inputHex) |
||||
assert(outputBn.eq(expectedBn), 'returns the original estimatedGas value') |
||||
}) |
||||
|
||||
it('buffers up to recommend gas limit recommended ceiling', function () { |
||||
// naive estimatedGas: 0x16e360 (1.5 mil)
|
||||
const inputHex = '0x16e360' |
||||
// dummy gas limit: 0x1e8480 (2 mil)
|
||||
const blockGasLimitHex = '0x1e8480' |
||||
const blockGasLimitBn = hexToBn(blockGasLimitHex) |
||||
const ceilGasLimitBn = blockGasLimitBn.muln(0.9) |
||||
const output = txUtils.addGasBuffer(inputHex, blockGasLimitHex) |
||||
// const inputBn = hexToBn(inputHex)
|
||||
// const outputBn = hexToBn(output)
|
||||
const expectedHex = bnToHex(ceilGasLimitBn) |
||||
assert.equal(output, expectedHex, 'returns the gas limit recommended ceiling value') |
||||
}) |
||||
}) |
||||
}) |
||||
|
@ -1,77 +1,98 @@ |
||||
const assert = require('assert') |
||||
const Transaction = require('ethereumjs-tx') |
||||
const BN = require('bn.js') |
||||
|
||||
|
||||
const { hexToBn, bnToHex } = require('../../app/scripts/lib/util') |
||||
const TxUtils = require('../../app/scripts/lib/tx-gas-utils') |
||||
const txUtils = require('../../app/scripts/controllers/transactions/lib/util') |
||||
|
||||
|
||||
describe('txUtils', function () { |
||||
let txUtils |
||||
|
||||
before(function () { |
||||
txUtils = new TxUtils(new Proxy({}, { |
||||
get: (obj, name) => { |
||||
return () => {} |
||||
}, |
||||
})) |
||||
}) |
||||
describe('#validateTxParams', function () { |
||||
it('does not throw for positive values', function () { |
||||
var sample = { |
||||
from: '0x1678a085c290ebd122dc42cba69373b5953b831d', |
||||
value: '0x01', |
||||
} |
||||
txUtils.validateTxParams(sample) |
||||
}) |
||||
|
||||
describe('chain Id', function () { |
||||
it('prepares a transaction with the provided chainId', function () { |
||||
const txParams = { |
||||
to: '0x70ad465e0bab6504002ad58c744ed89c7da38524', |
||||
from: '0x69ad465e0bab6504002ad58c744ed89c7da38525', |
||||
value: '0x0', |
||||
gas: '0x7b0c', |
||||
gasPrice: '0x199c82cc00', |
||||
data: '0x', |
||||
nonce: '0x3', |
||||
chainId: 42, |
||||
it('returns error for negative values', function () { |
||||
var sample = { |
||||
from: '0x1678a085c290ebd122dc42cba69373b5953b831d', |
||||
value: '-0x01', |
||||
} |
||||
try { |
||||
txUtils.validateTxParams(sample) |
||||
} catch (err) { |
||||
assert.ok(err, 'error') |
||||
} |
||||
const ethTx = new Transaction(txParams) |
||||
assert.equal(ethTx.getChainId(), 42, 'chainId is set from tx params') |
||||
}) |
||||
}) |
||||
|
||||
describe('addGasBuffer', function () { |
||||
it('multiplies by 1.5, when within block gas limit', function () { |
||||
// naive estimatedGas: 0x16e360 (1.5 mil)
|
||||
const inputHex = '0x16e360' |
||||
// dummy gas limit: 0x3d4c52 (4 mil)
|
||||
const blockGasLimitHex = '0x3d4c52' |
||||
const output = txUtils.addGasBuffer(inputHex, blockGasLimitHex) |
||||
const inputBn = hexToBn(inputHex) |
||||
const outputBn = hexToBn(output) |
||||
const expectedBn = inputBn.muln(1.5) |
||||
assert(outputBn.eq(expectedBn), 'returns 1.5 the input value') |
||||
describe('#normalizeTxParams', () => { |
||||
it('should normalize txParams', () => { |
||||
let txParams = { |
||||
chainId: '0x1', |
||||
from: 'a7df1beDBF813f57096dF77FCd515f0B3900e402', |
||||
to: null, |
||||
data: '68656c6c6f20776f726c64', |
||||
random: 'hello world', |
||||
} |
||||
|
||||
let normalizedTxParams = txUtils.normalizeTxParams(txParams) |
||||
|
||||
assert(!normalizedTxParams.chainId, 'their should be no chainId') |
||||
assert(!normalizedTxParams.to, 'their should be no to address if null') |
||||
assert.equal(normalizedTxParams.from.slice(0, 2), '0x', 'from should be hexPrefixd') |
||||
assert.equal(normalizedTxParams.data.slice(0, 2), '0x', 'data should be hexPrefixd') |
||||
assert(!('random' in normalizedTxParams), 'their should be no random key in normalizedTxParams') |
||||
|
||||
txParams.to = 'a7df1beDBF813f57096dF77FCd515f0B3900e402' |
||||
normalizedTxParams = txUtils.normalizeTxParams(txParams) |
||||
assert.equal(normalizedTxParams.to.slice(0, 2), '0x', 'to should be hexPrefixd') |
||||
|
||||
}) |
||||
}) |
||||
|
||||
it('uses original estimatedGas, when above block gas limit', function () { |
||||
// naive estimatedGas: 0x16e360 (1.5 mil)
|
||||
const inputHex = '0x16e360' |
||||
// dummy gas limit: 0x0f4240 (1 mil)
|
||||
const blockGasLimitHex = '0x0f4240' |
||||
const output = txUtils.addGasBuffer(inputHex, blockGasLimitHex) |
||||
// const inputBn = hexToBn(inputHex)
|
||||
const outputBn = hexToBn(output) |
||||
const expectedBn = hexToBn(inputHex) |
||||
assert(outputBn.eq(expectedBn), 'returns the original estimatedGas value') |
||||
describe('#validateRecipient', () => { |
||||
it('removes recipient for txParams with 0x when contract data is provided', function () { |
||||
const zeroRecipientandDataTxParams = { |
||||
from: '0x1678a085c290ebd122dc42cba69373b5953b831d', |
||||
to: '0x', |
||||
data: 'bytecode', |
||||
} |
||||
const sanitizedTxParams = txUtils.validateRecipient(zeroRecipientandDataTxParams) |
||||
assert.deepEqual(sanitizedTxParams, { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', data: 'bytecode' }, 'no recipient with 0x') |
||||
}) |
||||
|
||||
it('buffers up to recommend gas limit recommended ceiling', function () { |
||||
// naive estimatedGas: 0x16e360 (1.5 mil)
|
||||
const inputHex = '0x16e360' |
||||
// dummy gas limit: 0x1e8480 (2 mil)
|
||||
const blockGasLimitHex = '0x1e8480' |
||||
const blockGasLimitBn = hexToBn(blockGasLimitHex) |
||||
const ceilGasLimitBn = blockGasLimitBn.muln(0.9) |
||||
const output = txUtils.addGasBuffer(inputHex, blockGasLimitHex) |
||||
// const inputBn = hexToBn(inputHex)
|
||||
// const outputBn = hexToBn(output)
|
||||
const expectedHex = bnToHex(ceilGasLimitBn) |
||||
assert.equal(output, expectedHex, 'returns the gas limit recommended ceiling value') |
||||
it('should error when recipient is 0x', function () { |
||||
const zeroRecipientTxParams = { |
||||
from: '0x1678a085c290ebd122dc42cba69373b5953b831d', |
||||
to: '0x', |
||||
} |
||||
assert.throws(() => { txUtils.validateRecipient(zeroRecipientTxParams) }, Error, 'Invalid recipient address') |
||||
}) |
||||
}) |
||||
}) |
||||
|
||||
|
||||
describe('#validateFrom', () => { |
||||
it('should error when from is not a hex string', function () { |
||||
|
||||
// where from is undefined
|
||||
const txParams = {} |
||||
assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`) |
||||
|
||||
// where from is array
|
||||
txParams.from = [] |
||||
assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`) |
||||
|
||||
// where from is a object
|
||||
txParams.from = {} |
||||
assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`) |
||||
|
||||
// where from is a invalid address
|
||||
txParams.from = 'im going to fail' |
||||
assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address`) |
||||
|
||||
// should run
|
||||
txParams.from ='0x1678a085c290ebd122dc42cba69373b5953b831d' |
||||
txUtils.validateFrom(txParams) |
||||
}) |
||||
}) |
||||
}) |
||||
|
@ -0,0 +1,45 @@ |
||||
const { Component } = require('react') |
||||
const PropTypes = require('prop-types') |
||||
const h = require('react-hyperscript') |
||||
const copyToClipboard = require('copy-to-clipboard') |
||||
const { exportAsFile } = require('../../util') |
||||
|
||||
class ExportTextContainer extends Component { |
||||
render () { |
||||
const { text = '', filename = '' } = this.props |
||||
const { t } = this.context |
||||
|
||||
return ( |
||||
h('.export-text-container', [ |
||||
h('.export-text-container__text-container', [ |
||||
h('.export-text-container__text', text), |
||||
]), |
||||
h('.export-text-container__buttons-container', [ |
||||
h('.export-text-container__button.export-text-container__button--copy', { |
||||
onClick: () => copyToClipboard(text), |
||||
}, [ |
||||
h('img', { src: 'images/copy-to-clipboard.svg' }), |
||||
h('.export-text-container__button-text', t('copyToClipboard')), |
||||
]), |
||||
h('.export-text-container__button', { |
||||
onClick: () => exportAsFile(filename, text), |
||||
}, [ |
||||
h('img', { src: 'images/download.svg' }), |
||||
h('.export-text-container__button-text', t('saveAsCsvFile')), |
||||
]), |
||||
]), |
||||
]) |
||||
) |
||||
} |
||||
} |
||||
|
||||
ExportTextContainer.propTypes = { |
||||
text: PropTypes.string, |
||||
filename: PropTypes.string, |
||||
} |
||||
|
||||
ExportTextContainer.contextTypes = { |
||||
t: PropTypes.func, |
||||
} |
||||
|
||||
module.exports = ExportTextContainer |
@ -0,0 +1,52 @@ |
||||
.export-text-container { |
||||
display: flex; |
||||
justify-content: center; |
||||
flex-direction: column; |
||||
align-items: center; |
||||
border: 1px solid $alto; |
||||
border-radius: 4px; |
||||
font-weight: 400; |
||||
|
||||
&__text-container { |
||||
width: 100%; |
||||
display: flex; |
||||
justify-content: center; |
||||
padding: 20px; |
||||
border-radius: 4px; |
||||
background: $alabaster; |
||||
} |
||||
|
||||
&__text { |
||||
resize: none; |
||||
border: none; |
||||
background: $alabaster; |
||||
font-size: 20px; |
||||
text-align: center; |
||||
} |
||||
|
||||
&__buttons-container { |
||||
display: flex; |
||||
flex-direction: row; |
||||
border-top: 1px solid $alto; |
||||
width: 100%; |
||||
} |
||||
|
||||
&__button { |
||||
padding: 10px; |
||||
flex: 1; |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
font-size: 14px; |
||||
cursor: pointer; |
||||
color: $curious-blue; |
||||
|
||||
&--copy { |
||||
border-right: 1px solid $alto; |
||||
} |
||||
} |
||||
|
||||
&__button-text { |
||||
padding-left: 10px; |
||||
} |
||||
} |
@ -0,0 +1,2 @@ |
||||
const ExportTextContainer = require('./export-text-container.component') |
||||
module.exports = ExportTextContainer |
@ -0,0 +1,2 @@ |
||||
const LoadingScreen = require('./loading-screen.component') |
||||
module.exports = LoadingScreen |
@ -0,0 +1,2 @@ |
||||
const Spinner = require('./spinner.component') |
||||
module.exports = Spinner |
@ -0,0 +1,78 @@ |
||||
import React from 'react' |
||||
import PropTypes from 'prop-types' |
||||
|
||||
const Spinner = ({ className = '', color = '#000000' }) => { |
||||
return ( |
||||
<div className={`spinner ${className}`}> |
||||
<svg className="lds-spinner" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" style={{background: 'none'}}> |
||||
<g transform="rotate(0 50 50)"> |
||||
<rect x={45} y={0} rx={0} ry={0} width={10} height={30} fill={color}> |
||||
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.9166666666666666s" repeatCount="indefinite" /> |
||||
</rect> |
||||
</g> |
||||
<g transform="rotate(30 50 50)"> |
||||
<rect x={45} y={0} rx={0} ry={0} width={10} height={30} fill={color}> |
||||
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.8333333333333334s" repeatCount="indefinite" /> |
||||
</rect> |
||||
</g> |
||||
<g transform="rotate(60 50 50)"> |
||||
<rect x={45} y={0} rx={0} ry={0} width={10} height={30} fill={color}> |
||||
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.75s" repeatCount="indefinite" /> |
||||
</rect> |
||||
</g> |
||||
<g transform="rotate(90 50 50)"> |
||||
<rect x={45} y={0} rx={0} ry={0} width={10} height={30} fill={color}> |
||||
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.6666666666666666s" repeatCount="indefinite" /> |
||||
</rect> |
||||
</g> |
||||
<g transform="rotate(120 50 50)"> |
||||
<rect x={45} y={0} rx={0} ry={0} width={10} height={30} fill={color}> |
||||
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.5833333333333334s" repeatCount="indefinite" /> |
||||
</rect> |
||||
</g> |
||||
<g transform="rotate(150 50 50)"> |
||||
<rect x={45} y={0} rx={0} ry={0} width={10} height={30} fill={color}> |
||||
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.5s" repeatCount="indefinite" /> |
||||
</rect> |
||||
</g> |
||||
<g transform="rotate(180 50 50)"> |
||||
<rect x={45} y={0} rx={0} ry={0} width={10} height={30} fill={color}> |
||||
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.4166666666666667s" repeatCount="indefinite" /> |
||||
</rect> |
||||
</g> |
||||
<g transform="rotate(210 50 50)"> |
||||
<rect x={45} y={0} rx={0} ry={0} width={10} height={30} fill={color}> |
||||
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.3333333333333333s" repeatCount="indefinite" /> |
||||
</rect> |
||||
</g> |
||||
<g transform="rotate(240 50 50)"> |
||||
<rect x={45} y={0} rx={0} ry={0} width={10} height={30} fill={color}> |
||||
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.25s" repeatCount="indefinite" /> |
||||
</rect> |
||||
</g> |
||||
<g transform="rotate(270 50 50)"> |
||||
<rect x={45} y={0} rx={0} ry={0} width={10} height={30} fill={color}> |
||||
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.16666666666666666s" repeatCount="indefinite" /> |
||||
</rect> |
||||
</g> |
||||
<g transform="rotate(300 50 50)"> |
||||
<rect x={45} y={0} rx={0} ry={0} width={10} height={30} fill={color}> |
||||
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.08333333333333333s" repeatCount="indefinite" /> |
||||
</rect> |
||||
</g> |
||||
<g transform="rotate(330 50 50)"> |
||||
<rect x={45} y={0} rx={0} ry={0} width={10} height={30} fill={color}> |
||||
<animate attributeName="opacity" values="1;0" dur="1s" begin="0s" repeatCount="indefinite" /> |
||||
</rect> |
||||
</g> |
||||
</svg> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
Spinner.propTypes = { |
||||
className: PropTypes.string, |
||||
color: PropTypes.string, |
||||
} |
||||
|
||||
module.exports = Spinner |
@ -1 +1,3 @@ |
||||
@import './unlock.scss'; |
||||
|
||||
@import './reveal-seed.scss'; |
||||
|
@ -0,0 +1,17 @@ |
||||
.reveal-seed { |
||||
&__content { |
||||
padding: 20px; |
||||
} |
||||
|
||||
&__label { |
||||
padding-bottom: 10px; |
||||
font-weight: 400; |
||||
display: inline-block; |
||||
} |
||||
|
||||
&__error { |
||||
color: $crimson; |
||||
font-size: 14px; |
||||
padding-top: 5px; |
||||
} |
||||
} |
@ -1,138 +0,0 @@ |
||||
const inherits = require('util').inherits |
||||
const Component = require('react').Component |
||||
const PropTypes = require('prop-types') |
||||
const connect = require('react-redux').connect |
||||
const h = require('react-hyperscript') |
||||
const actions = require('../../../actions') |
||||
const { withRouter } = require('react-router-dom') |
||||
const { compose } = require('recompose') |
||||
const { |
||||
DEFAULT_ROUTE, |
||||
INITIALIZE_BACKUP_PHRASE_ROUTE, |
||||
} = require('../../../routes') |
||||
|
||||
RevealSeedConfirmation.contextTypes = { |
||||
t: PropTypes.func, |
||||
} |
||||
|
||||
module.exports = compose( |
||||
withRouter, |
||||
connect(mapStateToProps) |
||||
)(RevealSeedConfirmation) |
||||
|
||||
|
||||
inherits(RevealSeedConfirmation, Component) |
||||
function RevealSeedConfirmation () { |
||||
Component.call(this) |
||||
} |
||||
|
||||
function mapStateToProps (state) { |
||||
return { |
||||
warning: state.appState.warning, |
||||
} |
||||
} |
||||
|
||||
RevealSeedConfirmation.prototype.render = function () { |
||||
const props = this.props |
||||
|
||||
return ( |
||||
|
||||
h('.initialize-screen.flex-column.flex-center.flex-grow', { |
||||
style: { maxWidth: '420px' }, |
||||
}, [ |
||||
|
||||
h('h3.flex-center.text-transform-uppercase', { |
||||
style: { |
||||
background: '#EBEBEB', |
||||
color: '#AEAEAE', |
||||
marginBottom: 24, |
||||
width: '100%', |
||||
fontSize: '20px', |
||||
padding: 6, |
||||
}, |
||||
}, [ |
||||
'Reveal Seed Words', |
||||
]), |
||||
|
||||
h('.div', { |
||||
style: { |
||||
display: 'flex', |
||||
flexDirection: 'column', |
||||
padding: '20px', |
||||
justifyContent: 'center', |
||||
}, |
||||
}, [ |
||||
|
||||
h('h4', this.context.t('revealSeedWordsWarning')), |
||||
|
||||
// confirmation
|
||||
h('input.large-input.letter-spacey', { |
||||
type: 'password', |
||||
id: 'password-box', |
||||
placeholder: this.context.t('enterPasswordConfirm'), |
||||
onKeyPress: this.checkConfirmation.bind(this), |
||||
style: { |
||||
width: 260, |
||||
marginTop: '12px', |
||||
}, |
||||
}), |
||||
|
||||
h('.flex-row.flex-start', { |
||||
style: { |
||||
marginTop: 30, |
||||
width: '50%', |
||||
}, |
||||
}, [ |
||||
// cancel
|
||||
h('button.primary', { |
||||
onClick: this.goHome.bind(this), |
||||
}, 'CANCEL'), |
||||
|
||||
// submit
|
||||
h('button.primary', { |
||||
style: { marginLeft: '10px' }, |
||||
onClick: this.revealSeedWords.bind(this), |
||||
}, 'OK'), |
||||
|
||||
]), |
||||
|
||||
(props.warning) && ( |
||||
h('span.error', { |
||||
style: { |
||||
margin: '20px', |
||||
}, |
||||
}, props.warning.split('-')) |
||||
), |
||||
|
||||
props.inProgress && ( |
||||
h('span.in-progress-notification', this.context.t('generatingSeed')) |
||||
), |
||||
]), |
||||
]) |
||||
) |
||||
} |
||||
|
||||
RevealSeedConfirmation.prototype.componentDidMount = function () { |
||||
document.getElementById('password-box').focus() |
||||
} |
||||
|
||||
RevealSeedConfirmation.prototype.goHome = function () { |
||||
this.props.dispatch(actions.showConfigPage(false)) |
||||
this.props.dispatch(actions.confirmSeedWords()) |
||||
.then(() => this.props.history.push(DEFAULT_ROUTE)) |
||||
} |
||||
|
||||
// create vault
|
||||
|
||||
RevealSeedConfirmation.prototype.checkConfirmation = function (event) { |
||||
if (event.key === 'Enter') { |
||||
event.preventDefault() |
||||
this.revealSeedWords() |
||||
} |
||||
} |
||||
|
||||
RevealSeedConfirmation.prototype.revealSeedWords = function () { |
||||
var password = document.getElementById('password-box').value |
||||
this.props.dispatch(actions.requestRevealSeed(password)) |
||||
.then(() => this.props.history.push(INITIALIZE_BACKUP_PHRASE_ROUTE)) |
||||
} |
Loading…
Reference in new issue