commit
4598554fea
@ -0,0 +1,6 @@ |
|||||||
|
#!/usr/bin/env bash |
||||||
|
|
||||||
|
echo "Downloading firefox..." |
||||||
|
wget https://ftp.mozilla.org/pub/firefox/releases/58.0/linux-x86_64/en-US/firefox-58.0.tar.bz2 \ |
||||||
|
&& tar xjf firefox-58.0.tar.bz2 |
||||||
|
echo "firefox download complete" |
@ -0,0 +1,8 @@ |
|||||||
|
#!/usr/bin/env bash |
||||||
|
|
||||||
|
echo "Installing firefox..." |
||||||
|
sudo rm -r /opt/firefox |
||||||
|
sudo mv firefox /opt/firefox58 |
||||||
|
sudo mv /usr/bin/firefox /usr/bin/firefox-old |
||||||
|
sudo ln -s /opt/firefox58/firefox /usr/bin/firefox |
||||||
|
echo "Firefox installed." |
@ -0,0 +1,24 @@ |
|||||||
|
const Config = require('./recipient-blacklist.js') |
||||||
|
|
||||||
|
/** @module*/ |
||||||
|
module.exports = { |
||||||
|
checkAccount, |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks if a specified account on a specified network is blacklisted. |
||||||
|
@param networkId {number} |
||||||
|
@param account {string} |
||||||
|
*/ |
||||||
|
function checkAccount (networkId, account) { |
||||||
|
|
||||||
|
const mainnetId = 1 |
||||||
|
if (networkId !== mainnetId) { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
const accountToCheck = account.toLowerCase() |
||||||
|
if (Config.blacklist.includes(accountToCheck)) { |
||||||
|
throw new Error('Recipient is a public account') |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,17 @@ |
|||||||
|
module.exports = { |
||||||
|
'blacklist': [ |
||||||
|
// IDEX phisher
|
||||||
|
'0x9bcb0A9d99d815Bb87ee3191b1399b1Bcc46dc77', |
||||||
|
// Ganache default seed phrases
|
||||||
|
'0x627306090abab3a6e1400e9345bc60c78a8bef57', |
||||||
|
'0xf17f52151ebef6c7334fad080c5704d77216b732', |
||||||
|
'0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef', |
||||||
|
'0x821aea9a577a9b44299b9c15c88cf3087f3b5544', |
||||||
|
'0x0d1d4e623d10f9fba5db95830f7d3839406c6af2', |
||||||
|
'0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e', |
||||||
|
'0x2191ef87e392377ec08e7c08eb105ef5448eced5', |
||||||
|
'0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5', |
||||||
|
'0x6330a553fc93768f612722bb8c2ec78ac90b3bbc', |
||||||
|
'0x5aeda56215b167893e80b4fe645ba6d5bab767de', |
||||||
|
], |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
const WritableStream = require('readable-stream').Writable |
||||||
|
const promiseToCallback = require('promise-to-callback') |
||||||
|
|
||||||
|
module.exports = createStreamSink |
||||||
|
|
||||||
|
|
||||||
|
function createStreamSink(asyncWriteFn, _opts) { |
||||||
|
return new AsyncWritableStream(asyncWriteFn, _opts) |
||||||
|
} |
||||||
|
|
||||||
|
class AsyncWritableStream extends WritableStream { |
||||||
|
|
||||||
|
constructor (asyncWriteFn, _opts) { |
||||||
|
const opts = Object.assign({ objectMode: true }, _opts) |
||||||
|
super(opts) |
||||||
|
this._asyncWriteFn = asyncWriteFn |
||||||
|
} |
||||||
|
|
||||||
|
// write from incomming stream to state
|
||||||
|
_write (chunk, encoding, callback) { |
||||||
|
promiseToCallback(this._asyncWriteFn(chunk, encoding))(callback) |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,234 @@ |
|||||||
|
const fs = require('fs') |
||||||
|
const async = require('async') |
||||||
|
const path = require('path') |
||||||
|
const promisify = require('pify') |
||||||
|
|
||||||
|
// start(/\.selectors.js/, generateSelectorTest).catch(console.error)
|
||||||
|
// start(/\.utils.js/, generateUtilTest).catch(console.error)
|
||||||
|
startContainer(/\.container.js/, generateContainerTest).catch(console.error) |
||||||
|
|
||||||
|
async function getAllFileNames (dirName) { |
||||||
|
const rootPath = path.join(__dirname, dirName) |
||||||
|
const allNames = (await promisify(fs.readdir)(dirName)) |
||||||
|
const fileNames = allNames.filter(name => name.match(/^.+\./)) |
||||||
|
const dirNames = allNames.filter(name => name.match(/^[^.]+$/)) |
||||||
|
|
||||||
|
const fullPathDirNames = dirNames.map(d => `${dirName}/${d}`) |
||||||
|
const subNameArrays = await promisify(async.map)(fullPathDirNames, getAllFileNames) |
||||||
|
let subNames = [] |
||||||
|
subNameArrays.forEach(subNameArray => subNames = [...subNames, ...subNameArray]) |
||||||
|
|
||||||
|
return [ |
||||||
|
...fileNames.map(name => dirName + '/' + name), |
||||||
|
...subNames, |
||||||
|
] |
||||||
|
} |
||||||
|
|
||||||
|
async function start (fileRegEx, testGenerator) { |
||||||
|
const fileNames = await getAllFileNames('./ui/app') |
||||||
|
const sFiles = fileNames.filter(name => name.match(fileRegEx)) |
||||||
|
|
||||||
|
let sFileMethodNames |
||||||
|
let testFilePath |
||||||
|
async.each(sFiles, async (sFile, cb) => { |
||||||
|
let [, sRootPath, sPath] = sFile.match(/^(.+\/)([^/]+)$/) |
||||||
|
sFileMethodNames = Object.keys(require(__dirname + '/' + sFile)) |
||||||
|
|
||||||
|
testFilePath = sPath.replace('.', '-').replace('.', '.test.') |
||||||
|
|
||||||
|
await promisify(fs.writeFile)( |
||||||
|
`${__dirname}/${sRootPath}tests/${testFilePath}`, |
||||||
|
testGenerator(sPath, sFileMethodNames), |
||||||
|
'utf8' |
||||||
|
) |
||||||
|
}, (err) => { |
||||||
|
console.log(err) |
||||||
|
}) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
async function startContainer (fileRegEx, testGenerator) { |
||||||
|
const fileNames = await getAllFileNames('./ui/app') |
||||||
|
const sFiles = fileNames.filter(name => name.match(fileRegEx)) |
||||||
|
|
||||||
|
let sFileMethodNames |
||||||
|
async.each(sFiles, async (sFile, cb) => { |
||||||
|
console.log(`sFile`, sFile); |
||||||
|
let [, sRootPath, sPath] = sFile.match(/^(.+\/)([^/]+)$/) |
||||||
|
|
||||||
|
let testFilePath = sPath.replace('.', '-').replace('.', '.test.') |
||||||
|
|
||||||
|
await promisify(fs.readFile)( |
||||||
|
__dirname + '/' + sFile, |
||||||
|
'utf8', |
||||||
|
async (err, result) => { |
||||||
|
console.log(`result`, result.length); |
||||||
|
const returnObjectStrings = result |
||||||
|
.match(/return\s(\{[\s\S]+?})\n}/g) |
||||||
|
.map(str => { |
||||||
|
return str |
||||||
|
.slice(0, str.length - 1) |
||||||
|
.slice(7) |
||||||
|
.replace(/\n/g, '') |
||||||
|
.replace(/\s\s+/g, ' ') |
||||||
|
|
||||||
|
}) |
||||||
|
const mapStateToPropsAssertionObject = returnObjectStrings[0] |
||||||
|
.replace(/\w+:\s\w+\([\w,\s]+\),/g, str => { |
||||||
|
const strKey = str.match(/^\w+/)[0] |
||||||
|
return strKey + ': \'mock' + str.match(/^\w+/)[0].replace(/^./, c => c.toUpperCase()) + ':mockState\',\n' |
||||||
|
}) |
||||||
|
.replace(/{\s\w.+/, firstLinePair => `{\n ${firstLinePair.slice(2)}`) |
||||||
|
.replace(/\w+:.+,/g, s => ` ${s}`) |
||||||
|
.replace(/}/g, s => ` ${s}`) |
||||||
|
let mapDispatchToPropsMethodNames |
||||||
|
if (returnObjectStrings[1]) { |
||||||
|
mapDispatchToPropsMethodNames = returnObjectStrings[1].match(/\s\w+:\s/g).map(str => str.match(/\w+/)[0]) |
||||||
|
} |
||||||
|
const proxyquireObject = ('{\n ' + result |
||||||
|
.match(/import\s{[\s\S]+?}\sfrom\s.+/g) |
||||||
|
.map(s => s.replace(/\n/g, '')) |
||||||
|
.map((s, i) => { |
||||||
|
const proxyKeys = s.match(/{.+}/)[0].match(/\w+/g) |
||||||
|
return '\'' + s.match(/'(.+)'/)[1] + '\': { ' + (proxyKeys.length > 1 |
||||||
|
? '\n ' + proxyKeys.join(': () => {},\n ') + ': () => {},\n ' |
||||||
|
: proxyKeys[0] + ': () => {},') + ' }' |
||||||
|
}) |
||||||
|
.join(',\n ') + '\n}') |
||||||
|
.replace('{ connect: () => {}, },', `{
|
||||||
|
connect: (ms, md) => { |
||||||
|
mapStateToProps = ms |
||||||
|
mapDispatchToProps = md |
||||||
|
return () => ({}) |
||||||
|
}, |
||||||
|
},`)
|
||||||
|
// console.log(`proxyquireObject`, proxyquireObject);
|
||||||
|
// console.log(`mapStateToPropsAssertionObject`, mapStateToPropsAssertionObject);
|
||||||
|
// console.log(`mapDispatchToPropsMethodNames`, mapDispatchToPropsMethodNames);
|
||||||
|
|
||||||
|
const containerTest = generateContainerTest(sPath, { |
||||||
|
mapStateToPropsAssertionObject, |
||||||
|
mapDispatchToPropsMethodNames, |
||||||
|
proxyquireObject, |
||||||
|
}) |
||||||
|
// console.log(`containerTest`, `${__dirname}/${sRootPath}tests/${testFilePath}`, containerTest);
|
||||||
|
console.log('----') |
||||||
|
console.log(`sRootPath`, sRootPath); |
||||||
|
console.log(`testFilePath`, testFilePath); |
||||||
|
await promisify(fs.writeFile)( |
||||||
|
`${__dirname}/${sRootPath}tests/${testFilePath}`, |
||||||
|
containerTest, |
||||||
|
'utf8' |
||||||
|
) |
||||||
|
} |
||||||
|
) |
||||||
|
}, (err) => { |
||||||
|
console.log('123', err) |
||||||
|
}) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
function generateMethodList (methodArray) { |
||||||
|
return methodArray.map(n => ' ' + n).join(',\n') + ','
|
||||||
|
} |
||||||
|
|
||||||
|
function generateMethodDescribeBlock (methodName, index) { |
||||||
|
const describeBlock = |
||||||
|
`${index ? ' ' : ''}describe('${methodName}()', () => {
|
||||||
|
it('should', () => { |
||||||
|
const state = {} |
||||||
|
|
||||||
|
assert.equal(${methodName}(state), ) |
||||||
|
}) |
||||||
|
})` |
||||||
|
return describeBlock |
||||||
|
} |
||||||
|
|
||||||
|
function generateDispatchMethodDescribeBlock (methodName, index) { |
||||||
|
const describeBlock = |
||||||
|
`${index ? ' ' : ''}describe('${methodName}()', () => {
|
||||||
|
it('should dispatch an action', () => { |
||||||
|
mapDispatchToPropsObject.${methodName}() |
||||||
|
assert(dispatchSpy.calledOnce) |
||||||
|
}) |
||||||
|
})` |
||||||
|
return describeBlock |
||||||
|
} |
||||||
|
|
||||||
|
function generateMethodDescribeBlocks (methodArray) { |
||||||
|
return methodArray |
||||||
|
.map((methodName, index) => generateMethodDescribeBlock(methodName, index)) |
||||||
|
.join('\n\n') |
||||||
|
} |
||||||
|
|
||||||
|
function generateDispatchMethodDescribeBlocks (methodArray) { |
||||||
|
return methodArray |
||||||
|
.map((methodName, index) => generateDispatchMethodDescribeBlock(methodName, index)) |
||||||
|
.join('\n\n') |
||||||
|
} |
||||||
|
|
||||||
|
function generateSelectorTest (name, methodArray) { |
||||||
|
return `import assert from 'assert'
|
||||||
|
import { |
||||||
|
${generateMethodList(methodArray)} |
||||||
|
} from '../${name}' |
||||||
|
|
||||||
|
describe('${name.match(/^[^.]+/)} selectors', () => { |
||||||
|
|
||||||
|
${generateMethodDescribeBlocks(methodArray)} |
||||||
|
|
||||||
|
})` |
||||||
|
} |
||||||
|
|
||||||
|
function generateUtilTest (name, methodArray) { |
||||||
|
return `import assert from 'assert'
|
||||||
|
import { |
||||||
|
${generateMethodList(methodArray)} |
||||||
|
} from '../${name}' |
||||||
|
|
||||||
|
describe('${name.match(/^[^.]+/)} utils', () => { |
||||||
|
|
||||||
|
${generateMethodDescribeBlocks(methodArray)} |
||||||
|
|
||||||
|
})` |
||||||
|
} |
||||||
|
|
||||||
|
function generateContainerTest (sPath, { |
||||||
|
mapStateToPropsAssertionObject, |
||||||
|
mapDispatchToPropsMethodNames, |
||||||
|
proxyquireObject, |
||||||
|
}) { |
||||||
|
return `import assert from 'assert'
|
||||||
|
import proxyquire from 'proxyquire' |
||||||
|
import sinon from 'sinon' |
||||||
|
|
||||||
|
let mapStateToProps |
||||||
|
let mapDispatchToProps |
||||||
|
|
||||||
|
proxyquire('../${sPath}', ${proxyquireObject}) |
||||||
|
|
||||||
|
describe('${sPath.match(/^[^.]+/)} container', () => { |
||||||
|
|
||||||
|
describe('mapStateToProps()', () => { |
||||||
|
|
||||||
|
it('should map the correct properties to props', () => { |
||||||
|
assert.deepEqual(mapStateToProps('mockState'), ${mapStateToPropsAssertionObject}) |
||||||
|
}) |
||||||
|
|
||||||
|
}) |
||||||
|
|
||||||
|
describe('mapDispatchToProps()', () => { |
||||||
|
let dispatchSpy |
||||||
|
let mapDispatchToPropsObject |
||||||
|
|
||||||
|
beforeEach(() => { |
||||||
|
dispatchSpy = sinon.spy() |
||||||
|
mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy) |
||||||
|
}) |
||||||
|
|
||||||
|
${mapDispatchToPropsMethodNames ? generateDispatchMethodDescribeBlocks(mapDispatchToPropsMethodNames) : 'delete'} |
||||||
|
|
||||||
|
}) |
||||||
|
|
||||||
|
})` |
||||||
|
} |
@ -0,0 +1,6 @@ |
|||||||
|
Dear MetaMask Users, |
||||||
|
|
||||||
|
There have been several instances of high-profile legitimate websites such as BTC Manager and Games Workshop that have had their websites temporarily compromised. This involves showing a fake MetaMask window on the page asking for user's seed phrases. MetaMask will never open itself in this way and users are encouraged to report these instances immediately to either [our phishing blacklist](https://github.com/MetaMask/eth-phishing-detect/issues) or our support email at [support@metamask.io](mailto:support@metamask.io). |
||||||
|
|
||||||
|
Please read our full article on this ongoing issue at [https://medium.com/metamask/new-phishing-strategy-becoming-common-1b1123837168](https://medium.com/metamask/new-phishing-strategy-becoming-common-1b1123837168). |
||||||
|
|
@ -1,27 +0,0 @@ |
|||||||
var fs = require('fs') |
|
||||||
var path = require('path') |
|
||||||
var prompt = require('prompt') |
|
||||||
var open = require('open') |
|
||||||
var extend = require('extend') |
|
||||||
var notices = require('./notices.json') |
|
||||||
|
|
||||||
|
|
||||||
console.log('List of Notices') |
|
||||||
console.log(`ID \t DATE \t\t\t TITLE`) |
|
||||||
notices.forEach((notice) => { |
|
||||||
console.log(`${(' ' + notice.id).slice(-2)} \t ${notice.date} \t ${notice.title}`) |
|
||||||
}) |
|
||||||
prompt.get(['id'], (error, res) => { |
|
||||||
prompt.start() |
|
||||||
if (error) { |
|
||||||
console.log("Exiting...") |
|
||||||
process.exit() |
|
||||||
} |
|
||||||
var index = notices.findIndex((notice) => { return notice.id == res.id}) |
|
||||||
if (index === -1) { |
|
||||||
console.log('Notice not found. Exiting...') |
|
||||||
} |
|
||||||
notices.splice(index, 1) |
|
||||||
fs.unlink(`notices/archive/notice_${res.id}.md`) |
|
||||||
fs.writeFile(`notices/notices.json`, JSON.stringify(notices)) |
|
||||||
}) |
|
@ -1,33 +0,0 @@ |
|||||||
var fsp = require('fs-promise') |
|
||||||
var path = require('path') |
|
||||||
var prompt = require('prompt') |
|
||||||
var open = require('open') |
|
||||||
var extend = require('extend') |
|
||||||
var notices = require('./notices.json') |
|
||||||
var id = Number(require('./notice-nonce.json')) |
|
||||||
|
|
||||||
var date = new Date().toDateString() |
|
||||||
|
|
||||||
var notice = { |
|
||||||
read: false, |
|
||||||
date: date, |
|
||||||
} |
|
||||||
|
|
||||||
fsp.writeFile(`notices/archive/notice_${id}.md`,'Message goes here. Please write out your notice and save before proceeding at the command line.') |
|
||||||
.then(() => { |
|
||||||
open(`notices/archive/notice_${id}.md`) |
|
||||||
prompt.start() |
|
||||||
prompt.get(['title'], (err, result) => { |
|
||||||
notice.title = result.title |
|
||||||
fsp.readFile(`notices/archive/notice_${id}.md`) |
|
||||||
.then((body) => { |
|
||||||
notice.body = body.toString() |
|
||||||
notice.id = id |
|
||||||
notices.push(notice) |
|
||||||
return fsp.writeFile(`notices/notices.json`, JSON.stringify(notices)) |
|
||||||
}).then((completion) => { |
|
||||||
id += 1 |
|
||||||
return fsp.writeFile(`notices/notice-nonce.json`, id) |
|
||||||
}) |
|
||||||
}) |
|
||||||
}) |
|
@ -1 +0,0 @@ |
|||||||
4 |
|
@ -0,0 +1,34 @@ |
|||||||
|
// fs.readFileSync is inlined by browserify transform "brfs"
|
||||||
|
const fs = require('fs') |
||||||
|
|
||||||
|
module.exports = [ |
||||||
|
{ |
||||||
|
id: 0, |
||||||
|
read: false, |
||||||
|
date: 'Thu Feb 09 2017', |
||||||
|
title: 'Terms of Use', |
||||||
|
body: fs.readFileSync(__dirname + '/archive/notice_0.md', 'utf8'), |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: 2, |
||||||
|
read: false, |
||||||
|
date: 'Mon May 08 2017', |
||||||
|
title: 'Privacy Notice', |
||||||
|
body: fs.readFileSync(__dirname + '/archive/notice_2.md', 'utf8'), |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: 3, |
||||||
|
read: false, |
||||||
|
date: 'Tue Nov 28 2017', |
||||||
|
title: 'Seed Phrase Alert', |
||||||
|
firstVersion: '<=3.12.0', |
||||||
|
body: fs.readFileSync(__dirname + '/archive/notice_3.md', 'utf8'), |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: 4, |
||||||
|
read: false, |
||||||
|
date: 'Wed Jun 13 2018', |
||||||
|
title: 'Phishing Warning', |
||||||
|
body: fs.readFileSync(__dirname + '/archive/notice_4.md', 'utf8'), |
||||||
|
} |
||||||
|
] |
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -1,31 +1,59 @@ |
|||||||
const assert = require('assert') |
const assert = require('assert') |
||||||
const path = require('path') |
const path = require('path') |
||||||
const accountImporter = require('../../../app/scripts/account-import-strategies/index') |
|
||||||
const ethUtil = require('ethereumjs-util') |
const ethUtil = require('ethereumjs-util') |
||||||
|
const accountImporter = require('../../../app/scripts/account-import-strategies/index') |
||||||
|
const { assertRejects } = require('../test-utils') |
||||||
|
|
||||||
describe('Account Import Strategies', function () { |
describe('Account Import Strategies', function () { |
||||||
const privkey = '0x4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553' |
const privkey = '0x4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553' |
||||||
const json = '{"version":3,"id":"dbb54385-0a99-437f-83c0-647de9f244c3","address":"a7f92ce3fba24196cf6f4bd2e1eb3db282ba998c","Crypto":{"ciphertext":"bde13d9ade5c82df80281ca363320ce254a8a3a06535bbf6ffdeaf0726b1312c","cipherparams":{"iv":"fbf93718a57f26051b292f072f2e5b41"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"7ffe00488319dec48e4c49a120ca49c6afbde9272854c64d9541c83fc6acdffe","n":8192,"r":8,"p":1},"mac":"2adfd9c4bc1cdac4c85bddfb31d9e21a684e0e050247a70c5698facf6b7d4681"}}' |
const json = '{"version":3,"id":"dbb54385-0a99-437f-83c0-647de9f244c3","address":"a7f92ce3fba24196cf6f4bd2e1eb3db282ba998c","Crypto":{"ciphertext":"bde13d9ade5c82df80281ca363320ce254a8a3a06535bbf6ffdeaf0726b1312c","cipherparams":{"iv":"fbf93718a57f26051b292f072f2e5b41"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"7ffe00488319dec48e4c49a120ca49c6afbde9272854c64d9541c83fc6acdffe","n":8192,"r":8,"p":1},"mac":"2adfd9c4bc1cdac4c85bddfb31d9e21a684e0e050247a70c5698facf6b7d4681"}}' |
||||||
|
|
||||||
it('imports a private key and strips 0x prefix', async function () { |
describe('private key import', function () { |
||||||
const importPrivKey = await accountImporter.importAccount('Private Key', [ privkey ]) |
it('imports a private key and strips 0x prefix', async function () { |
||||||
assert.equal(importPrivKey, ethUtil.stripHexPrefix(privkey)) |
const importPrivKey = await accountImporter.importAccount('Private Key', [ privkey ]) |
||||||
}) |
assert.equal(importPrivKey, ethUtil.stripHexPrefix(privkey)) |
||||||
|
}) |
||||||
|
|
||||||
it('fails when password is incorrect for keystore', async function () { |
it('throws an error for empty string private key', async () => { |
||||||
const wrongPassword = 'password2' |
assertRejects(async function() { |
||||||
|
await accountImporter.importAccount('Private Key', [ '' ]) |
||||||
|
}, Error, 'no empty strings') |
||||||
|
}) |
||||||
|
|
||||||
try { |
it('throws an error for undefined string private key', async () => { |
||||||
await accountImporter.importAccount('JSON File', [ json, wrongPassword]) |
assertRejects(async function () { |
||||||
} catch (error) { |
await accountImporter.importAccount('Private Key', [ undefined ]) |
||||||
assert.equal(error.message, 'Key derivation failed - possibly wrong passphrase') |
}) |
||||||
} |
}) |
||||||
}) |
|
||||||
|
|
||||||
it('imports json string and password to return a private key', async function () { |
it('throws an error for undefined string private key', async () => { |
||||||
const fileContentsPassword = 'password1' |
assertRejects(async function () { |
||||||
const importJson = await accountImporter.importAccount('JSON File', [ json, fileContentsPassword]) |
await accountImporter.importAccount('Private Key', []) |
||||||
assert.equal(importJson, '0x5733876abe94146069ce8bcbabbde2677f2e35fa33e875e92041ed2ac87e5bc7') |
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('throws an error for invalid private key', async () => { |
||||||
|
assertRejects(async function () { |
||||||
|
await accountImporter.importAccount('Private Key', [ 'popcorn' ]) |
||||||
|
}) |
||||||
|
}) |
||||||
}) |
}) |
||||||
|
|
||||||
|
describe('JSON keystore import', function () { |
||||||
|
it('fails when password is incorrect for keystore', async function () { |
||||||
|
const wrongPassword = 'password2' |
||||||
|
|
||||||
|
try { |
||||||
|
await accountImporter.importAccount('JSON File', [ json, wrongPassword]) |
||||||
|
} catch (error) { |
||||||
|
assert.equal(error.message, 'Key derivation failed - possibly wrong passphrase') |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
it('imports json string and password to return a private key', async function () { |
||||||
|
const fileContentsPassword = 'password1' |
||||||
|
const importJson = await accountImporter.importAccount('JSON File', [ json, fileContentsPassword]) |
||||||
|
assert.equal(importJson, '0x5733876abe94146069ce8bcbabbde2677f2e35fa33e875e92041ed2ac87e5bc7') |
||||||
|
}) |
||||||
|
}) |
||||||
}) |
}) |
||||||
|
@ -0,0 +1,77 @@ |
|||||||
|
const assert = require('assert') |
||||||
|
const recipientBlackListChecker = require('../../../../../app/scripts/controllers/transactions/lib/recipient-blacklist-checker') |
||||||
|
const { |
||||||
|
ROPSTEN_CODE, |
||||||
|
RINKEYBY_CODE, |
||||||
|
KOVAN_CODE, |
||||||
|
} = require('../../../../../app/scripts/controllers/network/enums') |
||||||
|
|
||||||
|
const KeyringController = require('eth-keyring-controller') |
||||||
|
|
||||||
|
describe('Recipient Blacklist Checker', function () { |
||||||
|
|
||||||
|
let publicAccounts |
||||||
|
|
||||||
|
before(async function () { |
||||||
|
const damnedMnemonic = 'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat' |
||||||
|
const keyringController = new KeyringController({}) |
||||||
|
const Keyring = keyringController.getKeyringClassForType('HD Key Tree') |
||||||
|
const opts = { |
||||||
|
mnemonic: damnedMnemonic, |
||||||
|
numberOfAccounts: 10, |
||||||
|
} |
||||||
|
const keyring = new Keyring(opts) |
||||||
|
publicAccounts = await keyring.getAccounts() |
||||||
|
}) |
||||||
|
|
||||||
|
describe('#checkAccount', function () { |
||||||
|
it('does not fail on test networks', function () { |
||||||
|
let callCount = 0 |
||||||
|
const networks = [ROPSTEN_CODE, RINKEYBY_CODE, KOVAN_CODE] |
||||||
|
for (let networkId in networks) { |
||||||
|
publicAccounts.forEach((account) => { |
||||||
|
recipientBlackListChecker.checkAccount(networkId, account) |
||||||
|
callCount++ |
||||||
|
}) |
||||||
|
} |
||||||
|
assert.equal(callCount, 30) |
||||||
|
}) |
||||||
|
|
||||||
|
it('fails on mainnet', function () { |
||||||
|
const mainnetId = 1 |
||||||
|
let callCount = 0 |
||||||
|
publicAccounts.forEach((account) => { |
||||||
|
try { |
||||||
|
recipientBlackListChecker.checkAccount(mainnetId, account) |
||||||
|
assert.fail('function should have thrown an error') |
||||||
|
} catch (err) { |
||||||
|
assert.equal(err.message, 'Recipient is a public account') |
||||||
|
} |
||||||
|
callCount++ |
||||||
|
}) |
||||||
|
assert.equal(callCount, 10) |
||||||
|
}) |
||||||
|
|
||||||
|
it('fails for public account - uppercase', function () { |
||||||
|
const mainnetId = 1 |
||||||
|
const publicAccount = '0X0D1D4E623D10F9FBA5DB95830F7D3839406C6AF2' |
||||||
|
try { |
||||||
|
recipientBlackListChecker.checkAccount(mainnetId, publicAccount) |
||||||
|
assert.fail('function should have thrown an error') |
||||||
|
} catch (err) { |
||||||
|
assert.equal(err.message, 'Recipient is a public account') |
||||||
|
} |
||||||
|
})
|
||||||
|
|
||||||
|
it('fails for public account - lowercase', async function () { |
||||||
|
const mainnetId = 1 |
||||||
|
const publicAccount = '0x0d1d4e623d10f9fba5db95830f7d3839406c6af2' |
||||||
|
try { |
||||||
|
await recipientBlackListChecker.checkAccount(mainnetId, publicAccount) |
||||||
|
assert.fail('function should have thrown an error') |
||||||
|
} catch (err) { |
||||||
|
assert.equal(err.message, 'Recipient is a public account') |
||||||
|
} |
||||||
|
})
|
||||||
|
}) |
||||||
|
}) |
@ -0,0 +1,17 @@ |
|||||||
|
const assert = require('assert') |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
assertRejects, |
||||||
|
} |
||||||
|
|
||||||
|
// assert.rejects added in node v10
|
||||||
|
async function assertRejects (asyncFn, regExp) { |
||||||
|
let f = () => {} |
||||||
|
try { |
||||||
|
await asyncFn() |
||||||
|
} catch (error) { |
||||||
|
f = () => { throw error } |
||||||
|
} finally { |
||||||
|
assert.throws(f, regExp) |
||||||
|
} |
||||||
|
} |
@ -1,113 +0,0 @@ |
|||||||
const Component = require('react').Component |
|
||||||
const h = require('react-hyperscript') |
|
||||||
const inherits = require('util').inherits |
|
||||||
|
|
||||||
module.exports = CurrencyInput |
|
||||||
|
|
||||||
inherits(CurrencyInput, Component) |
|
||||||
function CurrencyInput (props) { |
|
||||||
Component.call(this) |
|
||||||
|
|
||||||
const sanitizedValue = sanitizeValue(props.value) |
|
||||||
|
|
||||||
this.state = { |
|
||||||
value: sanitizedValue, |
|
||||||
emptyState: false, |
|
||||||
focused: false, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function removeNonDigits (str) { |
|
||||||
return str.match(/\d|$/g).join('') |
|
||||||
} |
|
||||||
|
|
||||||
// Removes characters that are not digits, then removes leading zeros
|
|
||||||
function sanitizeInteger (val) { |
|
||||||
return String(parseInt(removeNonDigits(val) || '0', 10)) |
|
||||||
} |
|
||||||
|
|
||||||
function sanitizeDecimal (val) { |
|
||||||
return removeNonDigits(val) |
|
||||||
} |
|
||||||
|
|
||||||
// Take a single string param and returns a non-negative integer or float as a string.
|
|
||||||
// Breaks the input into three parts: the integer, the decimal point, and the decimal/fractional part.
|
|
||||||
// Removes leading zeros from the integer, and non-digits from the integer and decimal
|
|
||||||
// The integer is returned as '0' in cases where it would be empty. A decimal point is
|
|
||||||
// included in the returned string if one is included in the param
|
|
||||||
// Examples:
|
|
||||||
// sanitizeValue('0') -> '0'
|
|
||||||
// sanitizeValue('a') -> '0'
|
|
||||||
// sanitizeValue('010.') -> '10.'
|
|
||||||
// sanitizeValue('0.005') -> '0.005'
|
|
||||||
// sanitizeValue('22.200') -> '22.200'
|
|
||||||
// sanitizeValue('.200') -> '0.200'
|
|
||||||
// sanitizeValue('a.b.1.c,89.123') -> '0.189123'
|
|
||||||
function sanitizeValue (value) { |
|
||||||
let [ , integer, point, decimal] = (/([^.]*)([.]?)([^.]*)/).exec(value) |
|
||||||
|
|
||||||
integer = sanitizeInteger(integer) || '0' |
|
||||||
decimal = sanitizeDecimal(decimal) |
|
||||||
|
|
||||||
return `${integer}${point}${decimal}` |
|
||||||
} |
|
||||||
|
|
||||||
CurrencyInput.prototype.handleChange = function (newValue) { |
|
||||||
const { onInputChange } = this.props |
|
||||||
const { value } = this.state |
|
||||||
|
|
||||||
let parsedValue = newValue |
|
||||||
const newValueLastIndex = newValue.length - 1 |
|
||||||
|
|
||||||
if (value === '0' && newValue[newValueLastIndex] === '0') { |
|
||||||
parsedValue = parsedValue.slice(0, newValueLastIndex) |
|
||||||
} |
|
||||||
const sanitizedValue = sanitizeValue(parsedValue) |
|
||||||
this.setState({ |
|
||||||
value: sanitizedValue, |
|
||||||
emptyState: newValue === '' && sanitizedValue === '0', |
|
||||||
}) |
|
||||||
onInputChange(sanitizedValue) |
|
||||||
} |
|
||||||
|
|
||||||
// If state.value === props.value plus a decimal point, or at least one
|
|
||||||
// zero or a decimal point and at least one zero, then this returns state.value
|
|
||||||
// after it is sanitized with getValueParts
|
|
||||||
CurrencyInput.prototype.getValueToRender = function () { |
|
||||||
const { value } = this.props |
|
||||||
const { value: stateValue } = this.state |
|
||||||
|
|
||||||
const trailingStateString = (new RegExp(`^${value}(.+)`)).exec(stateValue) |
|
||||||
const trailingDecimalAndZeroes = trailingStateString && (/^[.0]0*/).test(trailingStateString[1]) |
|
||||||
|
|
||||||
return sanitizeValue(trailingDecimalAndZeroes |
|
||||||
? stateValue |
|
||||||
: value) |
|
||||||
} |
|
||||||
|
|
||||||
CurrencyInput.prototype.render = function () { |
|
||||||
const { |
|
||||||
className, |
|
||||||
placeholder, |
|
||||||
readOnly, |
|
||||||
inputRef, |
|
||||||
type, |
|
||||||
} = this.props |
|
||||||
const { emptyState, focused } = this.state |
|
||||||
|
|
||||||
const inputSizeMultiplier = readOnly ? 1 : 1.2 |
|
||||||
|
|
||||||
const valueToRender = this.getValueToRender() |
|
||||||
return h('input', { |
|
||||||
className, |
|
||||||
type, |
|
||||||
value: emptyState ? '' : valueToRender, |
|
||||||
placeholder: focused ? '' : placeholder, |
|
||||||
size: valueToRender.length * inputSizeMultiplier, |
|
||||||
readOnly, |
|
||||||
onFocus: () => this.setState({ focused: true, emptyState: valueToRender === '0' }), |
|
||||||
onBlur: () => this.setState({ focused: false, emptyState: false }), |
|
||||||
onChange: e => this.handleChange(e.target.value), |
|
||||||
ref: inputRef, |
|
||||||
}) |
|
||||||
} |
|
@ -0,0 +1 @@ |
|||||||
|
export { default } from './page-container.component' |
@ -0,0 +1,18 @@ |
|||||||
|
import React, { Component } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
|
||||||
|
export default class PageContainerContent extends Component { |
||||||
|
|
||||||
|
static propTypes = { |
||||||
|
children: PropTypes.node.isRequired, |
||||||
|
}; |
||||||
|
|
||||||
|
render () { |
||||||
|
return ( |
||||||
|
<div className="page-container__content"> |
||||||
|
{this.props.children} |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
export { default } from './page-container-footer.component' |
@ -0,0 +1,54 @@ |
|||||||
|
import React, { Component } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import Button from '../../button' |
||||||
|
|
||||||
|
export default class PageContainerFooter extends Component { |
||||||
|
|
||||||
|
static propTypes = { |
||||||
|
onCancel: PropTypes.func, |
||||||
|
cancelText: PropTypes.string, |
||||||
|
onSubmit: PropTypes.func, |
||||||
|
submitText: PropTypes.string, |
||||||
|
disabled: PropTypes.bool, |
||||||
|
} |
||||||
|
|
||||||
|
static contextTypes = { |
||||||
|
t: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
render () { |
||||||
|
const { |
||||||
|
onCancel, |
||||||
|
cancelText, |
||||||
|
onSubmit, |
||||||
|
submitText, |
||||||
|
disabled, |
||||||
|
} = this.props |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="page-container__footer"> |
||||||
|
|
||||||
|
<Button |
||||||
|
type="default" |
||||||
|
large={true} |
||||||
|
className="page-container__footer-button" |
||||||
|
onClick={() => onCancel()} |
||||||
|
> |
||||||
|
{ cancelText || this.context.t('cancel') } |
||||||
|
</Button> |
||||||
|
|
||||||
|
<Button |
||||||
|
type="primary" |
||||||
|
large={true} |
||||||
|
className="page-container__footer-button" |
||||||
|
disabled={disabled} |
||||||
|
onClick={e => onSubmit(e)} |
||||||
|
> |
||||||
|
{ submitText || this.context.t('next') } |
||||||
|
</Button> |
||||||
|
|
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,35 @@ |
|||||||
|
import React, { Component } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
|
||||||
|
export default class PageContainerHeader extends Component { |
||||||
|
|
||||||
|
static propTypes = { |
||||||
|
title: PropTypes.string, |
||||||
|
subtitle: PropTypes.string, |
||||||
|
onClose: PropTypes.func, |
||||||
|
}; |
||||||
|
|
||||||
|
render () { |
||||||
|
const { title, subtitle, onClose } = this.props |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="page-container__header"> |
||||||
|
|
||||||
|
<div className="page-container__title"> |
||||||
|
{title} |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="page-container__subtitle"> |
||||||
|
{subtitle} |
||||||
|
</div> |
||||||
|
|
||||||
|
<div |
||||||
|
className="page-container__header-close" |
||||||
|
onClick={() => onClose()} |
||||||
|
/> |
||||||
|
|
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
export { default } from './page-container-header.component' |
@ -0,0 +1,57 @@ |
|||||||
|
import React, { Component } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
|
||||||
|
export default class PageContainerHeader extends Component { |
||||||
|
|
||||||
|
static propTypes = { |
||||||
|
title: PropTypes.string.isRequired, |
||||||
|
subtitle: PropTypes.string, |
||||||
|
onClose: PropTypes.func, |
||||||
|
showBackButton: PropTypes.bool, |
||||||
|
onBackButtonClick: PropTypes.func, |
||||||
|
backButtonStyles: PropTypes.object, |
||||||
|
backButtonString: PropTypes.string, |
||||||
|
}; |
||||||
|
|
||||||
|
renderHeaderRow () { |
||||||
|
const { showBackButton, onBackButtonClick, backButtonStyles, backButtonString } = this.props |
||||||
|
|
||||||
|
return showBackButton && ( |
||||||
|
<div className="page-container__header-row"> |
||||||
|
<span |
||||||
|
className="page-container__back-button" |
||||||
|
onClick={onBackButtonClick} |
||||||
|
style={backButtonStyles} |
||||||
|
> |
||||||
|
{ backButtonString || 'Back' } |
||||||
|
</span> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
render () { |
||||||
|
const { title, subtitle, onClose } = this.props |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="page-container__header"> |
||||||
|
|
||||||
|
{ this.renderHeaderRow() } |
||||||
|
|
||||||
|
<div className="page-container__title"> |
||||||
|
{title} |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="page-container__subtitle"> |
||||||
|
{subtitle} |
||||||
|
</div> |
||||||
|
|
||||||
|
<div |
||||||
|
className="page-container__header-close" |
||||||
|
onClick={() => onClose()} |
||||||
|
/> |
||||||
|
|
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,72 @@ |
|||||||
|
import React, { Component } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
|
||||||
|
import PageContainerHeader from './page-container-header' |
||||||
|
import PageContainerFooter from './page-container-footer' |
||||||
|
|
||||||
|
export default class PageContainer extends Component { |
||||||
|
|
||||||
|
static propTypes = { |
||||||
|
// PageContainerHeader props
|
||||||
|
title: PropTypes.string.isRequired, |
||||||
|
subtitle: PropTypes.string, |
||||||
|
onClose: PropTypes.func, |
||||||
|
showBackButton: PropTypes.bool, |
||||||
|
onBackButtonClick: PropTypes.func, |
||||||
|
backButtonStyles: PropTypes.object, |
||||||
|
backButtonString: PropTypes.string, |
||||||
|
// Content props
|
||||||
|
ContentComponent: PropTypes.func, |
||||||
|
contentComponentProps: PropTypes.object, |
||||||
|
// PageContainerFooter props
|
||||||
|
onCancel: PropTypes.func, |
||||||
|
cancelText: PropTypes.string, |
||||||
|
onSubmit: PropTypes.func, |
||||||
|
submitText: PropTypes.string, |
||||||
|
disabled: PropTypes.bool, |
||||||
|
}; |
||||||
|
|
||||||
|
render () { |
||||||
|
const { |
||||||
|
title, |
||||||
|
subtitle, |
||||||
|
onClose, |
||||||
|
showBackButton, |
||||||
|
onBackButtonClick, |
||||||
|
backButtonStyles, |
||||||
|
backButtonString, |
||||||
|
ContentComponent, |
||||||
|
contentComponentProps, |
||||||
|
onCancel, |
||||||
|
cancelText, |
||||||
|
onSubmit, |
||||||
|
submitText, |
||||||
|
disabled, |
||||||
|
} = this.props |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="page-container"> |
||||||
|
<PageContainerHeader |
||||||
|
title={title} |
||||||
|
subtitle={subtitle} |
||||||
|
onClose={onClose} |
||||||
|
showBackButton={showBackButton} |
||||||
|
onBackButtonClick={onBackButtonClick} |
||||||
|
backButtonStyles={backButtonStyles} |
||||||
|
backButtonString={backButtonString} |
||||||
|
/> |
||||||
|
<div className="page-container__content"> |
||||||
|
<ContentComponent { ...contentComponentProps } /> |
||||||
|
</div> |
||||||
|
<PageContainerFooter |
||||||
|
onCancel={onCancel} |
||||||
|
cancelText={cancelText} |
||||||
|
onSubmit={onSubmit} |
||||||
|
submitText={submitText} |
||||||
|
disabled={disabled} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -1,74 +0,0 @@ |
|||||||
const Component = require('react').Component |
|
||||||
const h = require('react-hyperscript') |
|
||||||
const inherits = require('util').inherits |
|
||||||
const connect = require('react-redux').connect |
|
||||||
const { checksumAddress } = require('../../util') |
|
||||||
const Identicon = require('../identicon') |
|
||||||
const CurrencyDisplay = require('./currency-display') |
|
||||||
const { conversionRateSelector, getCurrentCurrency } = require('../../selectors') |
|
||||||
|
|
||||||
inherits(AccountListItem, Component) |
|
||||||
function AccountListItem () { |
|
||||||
Component.call(this) |
|
||||||
} |
|
||||||
|
|
||||||
function mapStateToProps (state) { |
|
||||||
return { |
|
||||||
conversionRate: conversionRateSelector(state), |
|
||||||
currentCurrency: getCurrentCurrency(state), |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
module.exports = connect(mapStateToProps)(AccountListItem) |
|
||||||
|
|
||||||
AccountListItem.prototype.render = function () { |
|
||||||
const { |
|
||||||
className, |
|
||||||
account, |
|
||||||
handleClick, |
|
||||||
icon = null, |
|
||||||
conversionRate, |
|
||||||
currentCurrency, |
|
||||||
displayBalance = true, |
|
||||||
displayAddress = false, |
|
||||||
} = this.props |
|
||||||
|
|
||||||
const { name, address, balance } = account || {} |
|
||||||
|
|
||||||
return h('div.account-list-item', { |
|
||||||
className, |
|
||||||
onClick: () => handleClick({ name, address, balance }), |
|
||||||
}, [ |
|
||||||
|
|
||||||
h('div.account-list-item__top-row', {}, [ |
|
||||||
|
|
||||||
h( |
|
||||||
Identicon, |
|
||||||
{ |
|
||||||
address, |
|
||||||
diameter: 18, |
|
||||||
className: 'account-list-item__identicon', |
|
||||||
}, |
|
||||||
), |
|
||||||
|
|
||||||
h('div.account-list-item__account-name', {}, name || address), |
|
||||||
|
|
||||||
icon && h('div.account-list-item__icon', [icon]), |
|
||||||
|
|
||||||
]), |
|
||||||
|
|
||||||
displayAddress && name && h('div.account-list-item__account-address', checksumAddress(address)), |
|
||||||
|
|
||||||
displayBalance && h(CurrencyDisplay, { |
|
||||||
primaryCurrency: 'ETH', |
|
||||||
convertedCurrency: currentCurrency, |
|
||||||
value: balance, |
|
||||||
conversionRate, |
|
||||||
readOnly: true, |
|
||||||
className: 'account-list-item__account-balances', |
|
||||||
primaryBalanceClassName: 'account-list-item__account-primary-balance', |
|
||||||
convertedBalanceClassName: 'account-list-item__account-secondary-balance', |
|
||||||
}, name), |
|
||||||
|
|
||||||
]) |
|
||||||
} |
|
@ -1,72 +0,0 @@ |
|||||||
const Component = require('react').Component |
|
||||||
const h = require('react-hyperscript') |
|
||||||
const inherits = require('util').inherits |
|
||||||
const AccountListItem = require('./account-list-item') |
|
||||||
|
|
||||||
module.exports = FromDropdown |
|
||||||
|
|
||||||
inherits(FromDropdown, Component) |
|
||||||
function FromDropdown () { |
|
||||||
Component.call(this) |
|
||||||
} |
|
||||||
|
|
||||||
FromDropdown.prototype.getListItemIcon = function (currentAccount, selectedAccount) { |
|
||||||
const listItemIcon = h(`i.fa.fa-check.fa-lg`, { style: { color: '#02c9b1' } }) |
|
||||||
|
|
||||||
return currentAccount.address === selectedAccount.address |
|
||||||
? listItemIcon |
|
||||||
: null |
|
||||||
} |
|
||||||
|
|
||||||
FromDropdown.prototype.renderDropdown = function () { |
|
||||||
const { |
|
||||||
accounts, |
|
||||||
selectedAccount, |
|
||||||
closeDropdown, |
|
||||||
onSelect, |
|
||||||
} = this.props |
|
||||||
|
|
||||||
return h('div', {}, [ |
|
||||||
|
|
||||||
h('div.send-v2__from-dropdown__close-area', { |
|
||||||
onClick: closeDropdown, |
|
||||||
}), |
|
||||||
|
|
||||||
h('div.send-v2__from-dropdown__list', {}, [ |
|
||||||
|
|
||||||
...accounts.map(account => h(AccountListItem, { |
|
||||||
className: 'account-list-item__dropdown', |
|
||||||
account, |
|
||||||
handleClick: () => { |
|
||||||
onSelect(account) |
|
||||||
closeDropdown() |
|
||||||
}, |
|
||||||
icon: this.getListItemIcon(account, selectedAccount), |
|
||||||
})), |
|
||||||
|
|
||||||
]), |
|
||||||
|
|
||||||
]) |
|
||||||
} |
|
||||||
|
|
||||||
FromDropdown.prototype.render = function () { |
|
||||||
const { |
|
||||||
selectedAccount, |
|
||||||
openDropdown, |
|
||||||
dropdownOpen, |
|
||||||
} = this.props |
|
||||||
|
|
||||||
return h('div.send-v2__from-dropdown', {}, [ |
|
||||||
|
|
||||||
h(AccountListItem, { |
|
||||||
account: selectedAccount, |
|
||||||
handleClick: openDropdown, |
|
||||||
icon: h(`i.fa.fa-caret-down.fa-lg`, { style: { color: '#dedede' } }), |
|
||||||
}), |
|
||||||
|
|
||||||
dropdownOpen && this.renderDropdown(), |
|
||||||
|
|
||||||
]) |
|
||||||
|
|
||||||
} |
|
||||||
|
|
@ -1,106 +0,0 @@ |
|||||||
const Component = require('react').Component |
|
||||||
const PropTypes = require('prop-types') |
|
||||||
const h = require('react-hyperscript') |
|
||||||
const inherits = require('util').inherits |
|
||||||
const InputNumber = require('../input-number.js') |
|
||||||
const connect = require('react-redux').connect |
|
||||||
|
|
||||||
GasTooltip.contextTypes = { |
|
||||||
t: PropTypes.func, |
|
||||||
} |
|
||||||
|
|
||||||
module.exports = connect()(GasTooltip) |
|
||||||
|
|
||||||
|
|
||||||
inherits(GasTooltip, Component) |
|
||||||
function GasTooltip () { |
|
||||||
Component.call(this) |
|
||||||
this.state = { |
|
||||||
gasLimit: 0, |
|
||||||
gasPrice: 0, |
|
||||||
} |
|
||||||
|
|
||||||
this.updateGasPrice = this.updateGasPrice.bind(this) |
|
||||||
this.updateGasLimit = this.updateGasLimit.bind(this) |
|
||||||
this.onClose = this.onClose.bind(this) |
|
||||||
} |
|
||||||
|
|
||||||
GasTooltip.prototype.componentWillMount = function () { |
|
||||||
const { gasPrice = 0, gasLimit = 0} = this.props |
|
||||||
|
|
||||||
this.setState({ |
|
||||||
gasPrice: parseInt(gasPrice, 16) / 1000000000, |
|
||||||
gasLimit: parseInt(gasLimit, 16), |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
GasTooltip.prototype.updateGasPrice = function (newPrice) { |
|
||||||
const { onFeeChange } = this.props |
|
||||||
const { gasLimit } = this.state |
|
||||||
|
|
||||||
this.setState({ gasPrice: newPrice }) |
|
||||||
onFeeChange({ |
|
||||||
gasLimit: gasLimit.toString(16), |
|
||||||
gasPrice: (newPrice * 1000000000).toString(16), |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
GasTooltip.prototype.updateGasLimit = function (newLimit) { |
|
||||||
const { onFeeChange } = this.props |
|
||||||
const { gasPrice } = this.state |
|
||||||
|
|
||||||
this.setState({ gasLimit: newLimit }) |
|
||||||
onFeeChange({ |
|
||||||
gasLimit: newLimit.toString(16), |
|
||||||
gasPrice: (gasPrice * 1000000000).toString(16), |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
GasTooltip.prototype.onClose = function (e) { |
|
||||||
e.stopPropagation() |
|
||||||
this.props.onClose() |
|
||||||
} |
|
||||||
|
|
||||||
GasTooltip.prototype.render = function () { |
|
||||||
const { gasPrice, gasLimit } = this.state |
|
||||||
|
|
||||||
return h('div.gas-tooltip', {}, [ |
|
||||||
h('div.gas-tooltip-close-area', { |
|
||||||
onClick: this.onClose, |
|
||||||
}), |
|
||||||
h('div.customize-gas-tooltip-container', {}, [ |
|
||||||
h('div.customize-gas-tooltip', {}, [ |
|
||||||
h('div.gas-tooltip-header.gas-tooltip-label', {}, ['Customize Gas']), |
|
||||||
h('div.gas-tooltip-input-label', {}, [ |
|
||||||
h('span.gas-tooltip-label', {}, ['Gas Price']), |
|
||||||
h('i.fa.fa-info-circle'), |
|
||||||
]), |
|
||||||
h(InputNumber, { |
|
||||||
unitLabel: 'GWEI', |
|
||||||
step: 1, |
|
||||||
min: 0, |
|
||||||
placeholder: '0', |
|
||||||
value: gasPrice, |
|
||||||
onChange: (newPrice) => this.updateGasPrice(newPrice), |
|
||||||
}), |
|
||||||
h('div.gas-tooltip-input-label', { |
|
||||||
style: { |
|
||||||
'marginTop': '81px', |
|
||||||
}, |
|
||||||
}, [ |
|
||||||
h('span.gas-tooltip-label', {}, [this.context.t('gasLimit')]), |
|
||||||
h('i.fa.fa-info-circle'), |
|
||||||
]), |
|
||||||
h(InputNumber, { |
|
||||||
unitLabel: 'UNITS', |
|
||||||
step: 1, |
|
||||||
min: 0, |
|
||||||
placeholder: '0', |
|
||||||
value: gasLimit, |
|
||||||
onChange: (newLimit) => this.updateGasLimit(newLimit), |
|
||||||
}), |
|
||||||
]), |
|
||||||
h('div.gas-tooltip-arrow', {}), |
|
||||||
]), |
|
||||||
]) |
|
||||||
} |
|
@ -1,33 +0,0 @@ |
|||||||
// const Component = require('react').Component
|
|
||||||
// const h = require('react-hyperscript')
|
|
||||||
// const inherits = require('util').inherits
|
|
||||||
// const Identicon = require('../identicon')
|
|
||||||
|
|
||||||
// module.exports = MemoTextArea
|
|
||||||
|
|
||||||
// inherits(MemoTextArea, Component)
|
|
||||||
// function MemoTextArea () {
|
|
||||||
// Component.call(this)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// MemoTextArea.prototype.render = function () {
|
|
||||||
// const { memo, identities, onChange } = this.props
|
|
||||||
|
|
||||||
// return h('div.send-v2__memo-text-area', [
|
|
||||||
|
|
||||||
// h('textarea.send-v2__memo-text-area__input', {
|
|
||||||
// placeholder: 'Optional',
|
|
||||||
// value: memo,
|
|
||||||
// onChange,
|
|
||||||
// // onBlur: () => {
|
|
||||||
// // this.setErrorsFor('memo')
|
|
||||||
// // },
|
|
||||||
// onFocus: event => {
|
|
||||||
// // this.clearErrorsFor('memo')
|
|
||||||
// },
|
|
||||||
// }),
|
|
||||||
|
|
||||||
// ])
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
@ -1,78 +0,0 @@ |
|||||||
const { |
|
||||||
addCurrencies, |
|
||||||
conversionUtil, |
|
||||||
conversionGTE, |
|
||||||
multiplyCurrencies, |
|
||||||
} = require('../../conversion-util') |
|
||||||
const { |
|
||||||
calcTokenAmount, |
|
||||||
} = require('../../token-util') |
|
||||||
|
|
||||||
function isBalanceSufficient ({ |
|
||||||
amount = '0x0', |
|
||||||
gasTotal = '0x0', |
|
||||||
balance, |
|
||||||
primaryCurrency, |
|
||||||
amountConversionRate, |
|
||||||
conversionRate, |
|
||||||
}) { |
|
||||||
const totalAmount = addCurrencies(amount, gasTotal, { |
|
||||||
aBase: 16, |
|
||||||
bBase: 16, |
|
||||||
toNumericBase: 'hex', |
|
||||||
}) |
|
||||||
|
|
||||||
const balanceIsSufficient = conversionGTE( |
|
||||||
{ |
|
||||||
value: balance, |
|
||||||
fromNumericBase: 'hex', |
|
||||||
fromCurrency: primaryCurrency, |
|
||||||
conversionRate, |
|
||||||
}, |
|
||||||
{ |
|
||||||
value: totalAmount, |
|
||||||
fromNumericBase: 'hex', |
|
||||||
conversionRate: amountConversionRate || conversionRate, |
|
||||||
fromCurrency: primaryCurrency, |
|
||||||
}, |
|
||||||
) |
|
||||||
|
|
||||||
return balanceIsSufficient |
|
||||||
} |
|
||||||
|
|
||||||
function isTokenBalanceSufficient ({ |
|
||||||
amount = '0x0', |
|
||||||
tokenBalance, |
|
||||||
decimals, |
|
||||||
}) { |
|
||||||
const amountInDec = conversionUtil(amount, { |
|
||||||
fromNumericBase: 'hex', |
|
||||||
}) |
|
||||||
|
|
||||||
const tokenBalanceIsSufficient = conversionGTE( |
|
||||||
{ |
|
||||||
value: tokenBalance, |
|
||||||
fromNumericBase: 'dec', |
|
||||||
}, |
|
||||||
{ |
|
||||||
value: calcTokenAmount(amountInDec, decimals), |
|
||||||
fromNumericBase: 'dec', |
|
||||||
}, |
|
||||||
) |
|
||||||
|
|
||||||
return tokenBalanceIsSufficient |
|
||||||
} |
|
||||||
|
|
||||||
function getGasTotal (gasLimit, gasPrice) { |
|
||||||
return multiplyCurrencies(gasLimit, gasPrice, { |
|
||||||
toNumericBase: 'hex', |
|
||||||
multiplicandBase: 16, |
|
||||||
multiplierBase: 16, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
module.exports = { |
|
||||||
getGasTotal, |
|
||||||
isBalanceSufficient, |
|
||||||
isTokenBalanceSufficient, |
|
||||||
} |
|
@ -1,89 +0,0 @@ |
|||||||
const connect = require('react-redux').connect |
|
||||||
const actions = require('../../actions') |
|
||||||
const abi = require('ethereumjs-abi') |
|
||||||
const SendEther = require('../../send-v2') |
|
||||||
const { withRouter } = require('react-router-dom') |
|
||||||
const { compose } = require('recompose') |
|
||||||
|
|
||||||
const { |
|
||||||
accountsWithSendEtherInfoSelector, |
|
||||||
getCurrentAccountWithSendEtherInfo, |
|
||||||
conversionRateSelector, |
|
||||||
getSelectedToken, |
|
||||||
getSelectedAddress, |
|
||||||
getAddressBook, |
|
||||||
getSendFrom, |
|
||||||
getCurrentCurrency, |
|
||||||
getSelectedTokenToFiatRate, |
|
||||||
getSelectedTokenContract, |
|
||||||
} = require('../../selectors') |
|
||||||
|
|
||||||
module.exports = compose( |
|
||||||
withRouter, |
|
||||||
connect(mapStateToProps, mapDispatchToProps) |
|
||||||
)(SendEther) |
|
||||||
|
|
||||||
function mapStateToProps (state) { |
|
||||||
const fromAccounts = accountsWithSendEtherInfoSelector(state) |
|
||||||
const selectedAddress = getSelectedAddress(state) |
|
||||||
const selectedToken = getSelectedToken(state) |
|
||||||
const conversionRate = conversionRateSelector(state) |
|
||||||
|
|
||||||
let data |
|
||||||
let primaryCurrency |
|
||||||
let tokenToFiatRate |
|
||||||
if (selectedToken) { |
|
||||||
data = Array.prototype.map.call( |
|
||||||
abi.rawEncode(['address', 'uint256'], [selectedAddress, '0x0']), |
|
||||||
x => ('00' + x.toString(16)).slice(-2) |
|
||||||
).join('') |
|
||||||
|
|
||||||
primaryCurrency = selectedToken.symbol |
|
||||||
|
|
||||||
tokenToFiatRate = getSelectedTokenToFiatRate(state) |
|
||||||
} |
|
||||||
|
|
||||||
return { |
|
||||||
...state.metamask.send, |
|
||||||
from: getSendFrom(state) || getCurrentAccountWithSendEtherInfo(state), |
|
||||||
fromAccounts, |
|
||||||
toAccounts: [...fromAccounts, ...getAddressBook(state)], |
|
||||||
conversionRate, |
|
||||||
selectedToken, |
|
||||||
primaryCurrency, |
|
||||||
convertedCurrency: getCurrentCurrency(state), |
|
||||||
data, |
|
||||||
selectedAddress, |
|
||||||
amountConversionRate: selectedToken ? tokenToFiatRate : conversionRate, |
|
||||||
tokenContract: getSelectedTokenContract(state), |
|
||||||
unapprovedTxs: state.metamask.unapprovedTxs, |
|
||||||
network: state.metamask.network, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function mapDispatchToProps (dispatch) { |
|
||||||
return { |
|
||||||
showCustomizeGasModal: () => dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' })), |
|
||||||
estimateGas: params => dispatch(actions.estimateGas(params)), |
|
||||||
getGasPrice: () => dispatch(actions.getGasPrice()), |
|
||||||
signTokenTx: (tokenAddress, toAddress, amount, txData) => ( |
|
||||||
dispatch(actions.signTokenTx(tokenAddress, toAddress, amount, txData)) |
|
||||||
), |
|
||||||
signTx: txParams => dispatch(actions.signTx(txParams)), |
|
||||||
updateAndApproveTx: txParams => dispatch(actions.updateAndApproveTx(txParams)), |
|
||||||
updateTx: txData => dispatch(actions.updateTransaction(txData)), |
|
||||||
setSelectedAddress: address => dispatch(actions.setSelectedAddress(address)), |
|
||||||
addToAddressBook: (address, nickname) => dispatch(actions.addToAddressBook(address, nickname)), |
|
||||||
updateGasTotal: newTotal => dispatch(actions.updateGasTotal(newTotal)), |
|
||||||
updateGasPrice: newGasPrice => dispatch(actions.updateGasPrice(newGasPrice)), |
|
||||||
updateGasLimit: newGasLimit => dispatch(actions.updateGasLimit(newGasLimit)), |
|
||||||
updateSendTokenBalance: tokenBalance => dispatch(actions.updateSendTokenBalance(tokenBalance)), |
|
||||||
updateSendFrom: newFrom => dispatch(actions.updateSendFrom(newFrom)), |
|
||||||
updateSendTo: (newTo, nickname) => dispatch(actions.updateSendTo(newTo, nickname)), |
|
||||||
updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)), |
|
||||||
updateSendMemo: newMemo => dispatch(actions.updateSendMemo(newMemo)), |
|
||||||
updateSendErrors: newError => dispatch(actions.updateSendErrors(newError)), |
|
||||||
clearSend: () => dispatch(actions.clearSend()), |
|
||||||
setMaxModeTo: bool => dispatch(actions.setMaxModeTo(bool)), |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,74 @@ |
|||||||
|
import React, { Component } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import { checksumAddress } from '../../../util' |
||||||
|
import Identicon from '../../identicon' |
||||||
|
import CurrencyDisplay from '../../send/currency-display' |
||||||
|
|
||||||
|
export default class AccountListItem extends Component { |
||||||
|
|
||||||
|
static propTypes = { |
||||||
|
account: PropTypes.object, |
||||||
|
className: PropTypes.string, |
||||||
|
conversionRate: PropTypes.number, |
||||||
|
currentCurrency: PropTypes.string, |
||||||
|
displayAddress: PropTypes.bool, |
||||||
|
displayBalance: PropTypes.bool, |
||||||
|
handleClick: PropTypes.func, |
||||||
|
icon: PropTypes.node, |
||||||
|
}; |
||||||
|
|
||||||
|
render () { |
||||||
|
const { |
||||||
|
account, |
||||||
|
className, |
||||||
|
conversionRate, |
||||||
|
currentCurrency, |
||||||
|
displayAddress = false, |
||||||
|
displayBalance = true, |
||||||
|
handleClick, |
||||||
|
icon = null, |
||||||
|
} = this.props |
||||||
|
|
||||||
|
const { name, address, balance } = account || {} |
||||||
|
|
||||||
|
return (<div |
||||||
|
className={`account-list-item ${className}`} |
||||||
|
onClick={() => handleClick({ name, address, balance })} |
||||||
|
> |
||||||
|
|
||||||
|
<div className="account-list-item__top-row"> |
||||||
|
<Identicon |
||||||
|
address={address} |
||||||
|
className="account-list-item__identicon" |
||||||
|
diameter={18} |
||||||
|
/> |
||||||
|
|
||||||
|
<div className="account-list-item__account-name">{ name || address }</div> |
||||||
|
|
||||||
|
{icon && <div className="account-list-item__icon">{ icon }</div>} |
||||||
|
|
||||||
|
</div> |
||||||
|
|
||||||
|
{displayAddress && name && <div className="account-list-item__account-address"> |
||||||
|
{ checksumAddress(address) } |
||||||
|
</div>} |
||||||
|
|
||||||
|
{displayBalance && <CurrencyDisplay |
||||||
|
className="account-list-item__account-balances" |
||||||
|
conversionRate={conversionRate} |
||||||
|
convertedBalanceClassName="account-list-item__account-secondary-balance" |
||||||
|
convertedCurrency={currentCurrency} |
||||||
|
primaryBalanceClassName="account-list-item__account-primary-balance" |
||||||
|
primaryCurrency="ETH" |
||||||
|
readOnly={true} |
||||||
|
value={balance} |
||||||
|
/>} |
||||||
|
|
||||||
|
</div>) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
AccountListItem.contextTypes = { |
||||||
|
t: PropTypes.func, |
||||||
|
} |
||||||
|
|
@ -0,0 +1,15 @@ |
|||||||
|
import { connect } from 'react-redux' |
||||||
|
import { |
||||||
|
getConversionRate, |
||||||
|
getConvertedCurrency, |
||||||
|
} from '../send.selectors.js' |
||||||
|
import AccountListItem from './account-list-item.component' |
||||||
|
|
||||||
|
export default connect(mapStateToProps)(AccountListItem) |
||||||
|
|
||||||
|
function mapStateToProps (state) { |
||||||
|
return { |
||||||
|
conversionRate: getConversionRate(state), |
||||||
|
currentCurrency: getConvertedCurrency(state), |
||||||
|
} |
||||||
|
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue