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:
commit
db85827b2b
@ -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
|
||||||
|
|
||||||
|
@ -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')
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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' })
|
||||||
|
@ -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')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -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
|
||||||
|
39
ui/app/components/transaction-list.js
Normal file
39
ui/app/components/transaction-list.js
Normal 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))
|
||||||
|
])
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
])
|
||||||
|
}
|
@ -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))
|
||||||
|
@ -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()
|
||||||
|
@ -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
12
ui/lib/explorer-link.js
Normal 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}`
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user