1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 18:00:18 +01:00

Merge pull request #125 from MetaMask/TransactionList

Add transaction history to account detail view
This commit is contained in:
kumavis 2016-04-20 22:07:00 -07:00
commit db85827b2b
12 changed files with 160 additions and 38 deletions

View File

@ -4,7 +4,9 @@
- Pending transactions are now persisted to localStorage and resume even after browser is closed. - Pending transactions are now persisted to localStorage and resume even after browser is closed.
- Completed transactions are now persisted and can be displayed via UI. - Completed transactions are now persisted and can be displayed via UI.
- Added transaction list to account detail view.
- Fix bug on config screen where current RPC address was always displayed wrong. - Fix bug on config screen where current RPC address was always displayed wrong.
- Fixed bug where entering a decimal value when sending a transaction would result in sending the wrong amount.
# 1.5.1 2016-04-15 # 1.5.1 2016-04-15

View File

@ -170,14 +170,26 @@ ConfigManager.prototype.rejectTx = function(txId) {
} }
ConfigManager.prototype._setTxStatus = function(txId, status) { ConfigManager.prototype._setTxStatus = function(txId, status) {
var transactions = this.getTxList() var tx = this.getTx(txId)
transactions.forEach((tx) => {
if (tx.id === txId) {
tx.status = status tx.status = status
this.updateTx(tx)
}
ConfigManager.prototype.updateTx = function(tx) {
var transactions = this.getTxList()
var found, index
transactions.forEach((otherTx, i) => {
if (otherTx.id === tx.id) {
found = true
index = i
} }
}) })
if (found) {
transactions[index] = tx
}
this._saveTxList(transactions) this._saveTxList(transactions)
} }
ConfigManager.prototype.unconfirmedTxs = function() { ConfigManager.prototype.unconfirmedTxs = function() {
var transactions = this.getTxList() var transactions = this.getTxList()
return transactions.filter(tx => tx.status === 'unconfirmed') return transactions.filter(tx => tx.status === 'unconfirmed')

View File

@ -135,6 +135,7 @@ IdentityStore.prototype.addUnconfirmedTransaction = function(txParams, cb){
// create txData obj with parameters and meta data // create txData obj with parameters and meta data
var time = (new Date()).getTime() var time = (new Date()).getTime()
var txId = createId() var txId = createId()
txParams.metamaskId = txId
var txData = { var txData = {
id: txId, id: txId,
txParams: txParams, txParams: txParams,
@ -337,6 +338,13 @@ function IdManagement(opts) {
txParams.gasLimit = ethUtil.addHexPrefix(txParams.gasLimit || txParams.gas) txParams.gasLimit = ethUtil.addHexPrefix(txParams.gasLimit || txParams.gas)
txParams.nonce = ethUtil.addHexPrefix(txParams.nonce) txParams.nonce = ethUtil.addHexPrefix(txParams.nonce)
var tx = new Transaction(txParams) var tx = new Transaction(txParams)
// Add the tx hash to the persisted meta-tx object
var hash = '0x' + tx.hash().toString('hex')
var metaTx = configManager.getTx(txParams.metamaskId)
metaTx.hash = hash
configManager.updateTx(metaTx)
var rawTx = '0x'+tx.serialize().toString('hex') var rawTx = '0x'+tx.serialize().toString('hex')
return '0x'+LightwalletSigner.signTx(this.keyStore, this.derivedKey, rawTx, txParams.from, this.hdPathString) return '0x'+LightwalletSigner.signTx(this.keyStore, this.derivedKey, rawTx, txParams.from, this.hdPathString)
} }

View File

@ -17,7 +17,7 @@ injectCss(css)
async.parallel({ async.parallel({
currentDomain: getCurrentDomain, currentDomain: getCurrentDomain,
accountManager: connectToAccountManager, accountManager: connectToAccountManager,
}, setupApp) }, getNetworkVersion)
function connectToAccountManager(cb){ function connectToAccountManager(cb){
// setup communication with background // setup communication with background
@ -65,6 +65,13 @@ function getCurrentDomain(cb){
}) })
} }
function getNetworkVersion(cb, results) {
web3.version.getNetwork(function(err, result) {
results.networkVersion = result
setupApp(err, results)
})
}
function setupApp(err, opts){ function setupApp(err, opts){
if (err) { if (err) {
alert(err.stack) alert(err.stack)
@ -78,6 +85,6 @@ function setupApp(err, opts){
container: container, container: container,
accountManager: opts.accountManager, accountManager: opts.accountManager,
currentDomain: opts.currentDomain, currentDomain: opts.currentDomain,
networkVersion: opts.networkVersion,
}) })
} }

View File

@ -126,6 +126,16 @@ describe('config-manager', function() {
}) })
}) })
describe('#updateTx', function() {
it('replaces the tx with the same id', function() {
configManager.addTx({ id: '1', status: 'unconfirmed' })
configManager.addTx({ id: '2', status: 'confirmed' })
configManager.updateTx({ id: '1', status: 'blah', hash: 'foo' })
var result = configManager.getTx('1')
assert.equal(result.hash, 'foo')
})
})
describe('#unconfirmedTxs', function() { describe('#unconfirmedTxs', function() {
it('returns unconfirmed txs in a hash', function() { it('returns unconfirmed txs in a hash', function() {
configManager.addTx({ id: '1', status: 'unconfirmed' }) configManager.addTx({ id: '1', status: 'unconfirmed' })

View File

@ -82,6 +82,8 @@ describe('util', function() {
}) })
describe('normalizing values', function() {
describe('#normalizeToWei', function() { describe('#normalizeToWei', function() {
it('should convert an eth to the appropriate equivalent values', function() { it('should convert an eth to the appropriate equivalent values', function() {
var valueTable = { var valueTable = {
@ -92,8 +94,8 @@ describe('util', function() {
szabo: '1000000', szabo: '1000000',
finney:'1000', finney:'1000',
ether: '1', ether: '1',
kether:'0.001', // kether:'0.001',
mether:'0.000001', // mether:'0.000001',
// AUDIT: We're getting BN numbers on these ones. // AUDIT: We're getting BN numbers on these ones.
// I think they're big enough to ignore for now. // I think they're big enough to ignore for now.
// gether:'0.000000001', // gether:'0.000000001',
@ -106,9 +108,21 @@ describe('util', function() {
var value = new ethUtil.BN(valueTable[currency], 10) var value = new ethUtil.BN(valueTable[currency], 10)
var output = util.normalizeToWei(value, currency) var output = util.normalizeToWei(value, currency)
assert.equal(output.toString(10), valueTable.wei, `value of ${output.toString(10)} ${currency} should convert to ${oneEthBn}`) assert.equal(output.toString(10), valueTable.wei, `value of ${output.toString(10)} ${currency} should convert to ${oneEthBn}`)
} }
}) })
}) })
describe('#normalizeNumberToWei', function() {
it('should convert a kwei number to the appropriate equivalent wei', function() {
var result = util.normalizeNumberToWei(1.111, 'kwei')
assert.equal(result.toString(10), '1111', 'accepts decimals')
})
it('should convert a ether number to the appropriate equivalent wei', function() {
var result = util.normalizeNumberToWei(1.111, 'ether')
assert.equal(result.toString(10), '1111000000000000000', 'accepts decimals')
})
})
})
}) })

View File

@ -5,6 +5,7 @@ const connect = require('react-redux').connect
const copyToClipboard = require('copy-to-clipboard') const copyToClipboard = require('copy-to-clipboard')
const actions = require('./actions') const actions = require('./actions')
const AccountPanel = require('./components/account-panel') const AccountPanel = require('./components/account-panel')
const transactionList = require('./components/transaction-list')
module.exports = connect(mapStateToProps)(AccountDetailScreen) module.exports = connect(mapStateToProps)(AccountDetailScreen)
@ -15,6 +16,8 @@ function mapStateToProps(state) {
accounts: state.metamask.accounts, accounts: state.metamask.accounts,
address: state.appState.currentView.context, address: state.appState.currentView.context,
accountDetail: accountDetail, accountDetail: accountDetail,
transactions: state.metamask.transactions,
networkVersion: state.networkVersion,
} }
} }
@ -29,6 +32,7 @@ AccountDetailScreen.prototype.render = function() {
var identity = state.identities[state.address] var identity = state.identities[state.address]
var account = state.accounts[state.address] var account = state.accounts[state.address]
var accountDetail = state.accountDetail var accountDetail = state.accountDetail
var transactions = state.transactions
return ( return (
@ -71,6 +75,9 @@ AccountDetailScreen.prototype.render = function() {
]), ]),
]), ]),
transactionList(transactions
.filter(tx => tx.txParams.from === state.address)
.sort((a, b) => b.time - a.time), state.networkVersion),
this.exportedAccount(accountDetail), this.exportedAccount(accountDetail),
// transaction table // transaction table

View File

@ -0,0 +1,39 @@
const h = require('react-hyperscript')
const formatBalance = require('../util').formatBalance
const addressSummary = require('../util').addressSummary
const explorerLink = require('../../lib/explorer-link')
module.exports = function(transactions, network) {
return h('details', [
h('summary', [
h('div.font-small', {style: {display: 'inline'}}, 'Transactions'),
]),
h('.flex-row.flex-space-around', [
h('div.font-small','To'),
h('div.font-small','Amount'),
]),
h('.tx-list', {
style: {
overflowY: 'auto',
height: '180px',
},
},
transactions.map((transaction) => {
return h('.tx.flex-row.flex-space-around', [
h('a.font-small',
{
href: explorerLink(transaction.hash, parseInt(network)),
target: '_blank',
},
addressSummary(transaction.txParams.to)),
h('div.font-small', formatBalance(transaction.txParams.value))
])
})
)
])
}

View File

@ -96,7 +96,7 @@ SendTransactionScreen.prototype.render = function() {
}, 'Send') }, 'Send')
]), ]),
state.warning ? h('span.error', state.warning) : null, state.warning ? h('span.error', state.warning.split('.')[0]) : null,
]) ])
) )
} }
@ -108,11 +108,11 @@ SendTransactionScreen.prototype.back = function() {
SendTransactionScreen.prototype.onSubmit = function(event) { SendTransactionScreen.prototype.onSubmit = function(event) {
var recipient = document.querySelector('input.address').value var recipient = document.querySelector('input.address').value
var amount = new ethUtil.BN(document.querySelector('input.ether').value, 10)
var currency = document.querySelector('select.currency').value
var txData = document.querySelector('textarea.txData').value
var value = util.normalizeToWei(amount, currency) var inputAmount = parseFloat(document.querySelector('input.ether').value)
var currency = document.querySelector('select.currency').value
var value = util.normalizeNumberToWei(inputAmount, currency)
var balance = this.props.balance var balance = this.props.balance
if (value.gt(balance)) { if (value.gt(balance)) {
@ -132,6 +132,8 @@ SendTransactionScreen.prototype.onSubmit = function(event) {
from: this.props.address, from: this.props.address,
value: '0x' + value.toString(16), value: '0x' + value.toString(16),
} }
var txData = document.querySelector('textarea.txData').value
if (txData) txParams.data = txData if (txData) txParams.data = txData
this.props.dispatch(actions.signTx(txParams)) this.props.dispatch(actions.signTx(txParams))

View File

@ -28,6 +28,7 @@ module.exports = {
ethToWei: ethToWei, ethToWei: ethToWei,
weiToEth: weiToEth, weiToEth: weiToEth,
normalizeToWei: normalizeToWei, normalizeToWei: normalizeToWei,
normalizeNumberToWei: normalizeNumberToWei,
valueTable: valueTable, valueTable: valueTable,
bnTable: bnTable, bnTable: bnTable,
} }
@ -85,13 +86,18 @@ function dataSize(data) {
// returns a BN in wei // returns a BN in wei
function normalizeToWei(amount, currency) { function normalizeToWei(amount, currency) {
try { try {
var ether = amount.div(bnTable[currency]) return amount.mul(bnTable.wei).div(bnTable[currency])
var wei = ether.mul(bnTable.wei)
return wei
} catch (e) {} } catch (e) {}
return amount return amount
} }
var multiple = new ethUtil.BN('1000', 10)
function normalizeNumberToWei(n, currency) {
var enlarged = n * 1000
var amount = new ethUtil.BN(String(enlarged), 10)
return normalizeToWei(amount, currency).div(multiple)
}
function readableDate(ms) { function readableDate(ms) {
var date = new Date(ms) var date = new Date(ms)
var month = date.getMonth() var month = date.getMonth()

View File

@ -32,7 +32,10 @@ function startApp(metamaskState, accountManager, opts){
// appState represents the current tab's popup state // appState represents the current tab's popup state
appState: { appState: {
currentDomain: opts.currentDomain, currentDomain: opts.currentDomain,
} },
// Which blockchain we are using:
networkVersion: opts.networkVersion,
}) })
// if unconfirmed txs, start on txConf page // if unconfirmed txs, start on txConf page

12
ui/lib/explorer-link.js Normal file
View File

@ -0,0 +1,12 @@
module.exports = function(hash, network) {
let prefix
switch (network) {
case 1: // main net
prefix = ''
case 2: // morden test net
prefix = 'testnet.'
default:
prefix = ''
}
return `http://${prefix}etherscan.io/tx/${hash}`
}