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
@ -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