Resolve merge conflict.

feature/default_network_editable
Kevin Serrano 9 years ago
commit 86b7cc6637
  1. 1
      CHANGELOG.md
  2. 74
      app/images/forward-carrat.svg
  3. 2
      development/index.html
  4. 84
      development/states/empty-account-detail.json
  5. 1
      development/states/pending-tx-contract.json
  6. 1
      development/states/pending-tx-value.json
  7. 1
      development/states/pending_crash.json
  8. 44
      test/unit/nameForAccount_test.js
  9. 6
      test/unit/util_test.js
  10. 19
      ui-dev.js
  11. 2
      ui/app/components/account-panel.js
  12. 31
      ui/app/components/eth-balance.js
  13. 74
      ui/app/components/mini-account-panel.js
  14. 219
      ui/app/components/pending-tx-details.js
  15. 38
      ui/app/components/pending-tx.js
  16. 4
      ui/app/conf-tx.js
  17. 4
      ui/app/css/index.css
  18. 6
      ui/app/css/lib.css
  19. 17
      ui/app/util.js
  20. 31
      ui/lib/contract-namer.js

@ -6,6 +6,7 @@
- Fix formatting of account details.
- Use web3 minified dist for faster inject times
- Fix issue where dropdowns were not in front of icons.
- Update transaction approval styles.
- Align failed and successful transaction history text.
- Fix issue where large domain names and large transaction values would misalign the transaction history.

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="4.2333331mm"
height="12.800793mm"
viewBox="0 0 14.999999 45.357139"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="forward-carrat.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6"
inkscape:cx="17.87049"
inkscape:cy="17.678567"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
showguides="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="1276"
inkscape:window-height="755"
inkscape:window-x="4"
inkscape:window-y="1"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid4136"
originx="-180"
originy="-602.14286" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-180,-404.8622)">
<path
style="fill:#f7861c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 180,404.8622 0,7.5 10,15 -10,15 0,7.85714 15,-22.85714 z"
id="path4138"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@ -18,7 +18,7 @@ html, body, #app-content, .super-dev-container {
height: 100%;
width: 100%;
position: relative;
background: #cccccc;
background: white;
}
.mock-app-root {
background: #F7F7F7;

@ -0,0 +1,84 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"currentDomain": "example.com",
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
"name": "Wallet 1",
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"mayBeFauceting": false
},
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
"name": "Wallet 2",
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b",
"mayBeFauceting": false
},
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
"name": "Wallet 3",
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823",
"mayBeFauceting": false
},
"0x704107d04affddd9b66ab9de3dd7b095852e9b69": {
"name": "Wallet 4",
"address": "0x704107d04affddd9b66ab9de3dd7b095852e9b69",
"mayBeFauceting": false
}
},
"unconfTxs": {},
"accounts": {
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
"code": "0x",
"balance": "0x01",
"nonce": "0x0",
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
},
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
"code": "0x",
"nonce": "0x0",
"balance": "0x01",
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b"
},
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
"code": "0x",
"nonce": "0x0",
"balance": "0x01",
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823"
},
"0x704107d04affddd9b66ab9de3dd7b095852e9b69": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0x704107d04affddd9b66ab9de3dd7b095852e9b69"
}
},
"transactions": [],
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"network": "2",
"seedWords": null,
"isConfirmed": true,
"unconfMsgs": {},
"messages": [],
"provider": {
"type": "testnet"
},
"selectedAccount": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "accountDetail",
"detailView": null,
"context": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
},
"accountDetail": {
"subview": "transactions"
},
"currentDomain": "127.0.0.1:9966",
"transForward": true,
"isLoading": false,
"warning": null
},
"identities": {}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,44 @@
var assert = require('assert')
var sinon = require('sinon')
var path = require('path')
var contractNamer = require(path.join(__dirname, '..', '..', 'ui', 'lib', 'contract-namer.js'))
describe('contractNamer', function() {
beforeEach(function() {
this.sinon = sinon.sandbox.create()
})
afterEach(function() {
this.sinon.restore()
})
describe('naming a contract', function() {
it('should return nothing for an unknown random account', function() {
const input = '0x2386F26FC10000'
const output = contractNamer(input)
assert.deepEqual(output, null)
})
it('should accept identities as an optional second parameter', function() {
const input = '0x2386F26FC10000'.toLowerCase()
const expected = 'bar'
const identities = {}
identities[input] = { name: expected }
const output = contractNamer(input, identities)
assert.deepEqual(output, expected)
})
it('should check for identities case insensitively', function() {
const input = '0x2386F26FC10000'.toLowerCase()
const expected = 'bar'
const identities = {}
identities[input] = { name: expected }
const output = contractNamer(input.toUpperCase(), identities)
assert.deepEqual(output, expected)
})
})
})

@ -52,6 +52,12 @@ describe('util', function() {
var result = util.addressSummary(address)
assert.equal(result, '0xFDEa65C8...b825')
})
it('should accept arguments for firstseg, lastseg, and keepPrefix', function() {
var address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825'
var result = util.addressSummary(address, 4, 4, false)
assert.equal(result, 'FDEa...b825')
})
})
describe('isValidAddress', function() {

@ -1,3 +1,20 @@
/* UI DEV
*
* This is a utility module.
* It initializes a minimalist browserifiable project
* that contains the Metamask UI, with a mocked state.
*
* Includes a state menu for switching between different
* mocked states, along with query param support,
* so those states are preserved when live-reloading.
*
* This is a convenient way to develop on the UI
* without having to re-enter your password
* every time the plugin rebuilds.
*
* To use, run `npm run ui`.
*/
const render = require('react-dom').render
const h = require('react-hyperscript')
const Root = require('./ui/app/root')
@ -54,7 +71,7 @@ render(
style: {
height: '500px',
width: '360px',
boxShadow: '2px 2px 5px grey',
boxShadow: 'grey 0px 2px 9px',
margin: '20px',
},
}, [

@ -22,7 +22,7 @@ AccountPanel.prototype.render = function () {
var panelState = {
key: `accountPanel${identity.address}`,
identiconKey: identity.address,
identiconLabel: identity.name,
identiconLabel: identity.name || '',
attributes: [
{
key: 'ADDRESS',

@ -12,9 +12,11 @@ function EthBalanceComponent () {
}
EthBalanceComponent.prototype.render = function () {
var state = this.props
var style = state.style
var value = formatBalance(state.value)
var props = this.props
var style = props.style
const value = formatBalance(props.value)
return (
h('.ether-balance', {
@ -30,30 +32,37 @@ EthBalanceComponent.prototype.render = function () {
)
}
EthBalanceComponent.prototype.renderBalance = function (value) {
const props = this.props
if (value === 'None') return value
var balanceObj = generateBalanceObject(value)
var balance = balanceObj.balance
var label = balanceObj.label
var tagName = props.inline ? 'span' : 'div'
var topTag = props.inline ? 'div' : '.flex-column'
return (
h(Tooltip, {
position: 'bottom',
title: value.split(' ')[0],
}, [
h('.flex-column', {
h(topTag, {
style: {
alignItems: 'flex-end',
lineHeight: '13px',
fontFamily: 'Montserrat Light',
lineHeight: props.fontSize || '13px',
fontFamily: 'Montserrat Regular',
textRendering: 'geometricPrecision',
},
}, [
h('div', balance),
h('div', {
h(tagName, {
style: {
fontSize: props.fontSize || '12px',
},
}, balance + ' '),
h(tagName, {
style: {
color: ' #AEAEAE',
fontSize: '12px',
color: props.labelColor || '#AEAEAE',
fontSize: props.fontSize || '12px',
},
}, label),
]),

@ -0,0 +1,74 @@
const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const Identicon = require('./identicon')
module.exports = AccountPanel
inherits(AccountPanel, Component)
function AccountPanel () {
Component.call(this)
}
AccountPanel.prototype.render = function () {
var props = this.props
var picOrder = props.picOrder || 'left'
const { imageSeed } = props
return (
h('.identity-panel.flex-row.flex-left', {
style: {
cursor: props.onClick ? 'pointer' : undefined,
},
onClick: props.onClick,
}, [
this.genIcon(imageSeed, picOrder),
h('div.flex-column.flex-justify-center', {
style: {
lineHeight: '15px',
order: 2,
display: 'flex',
alignItems: picOrder === 'left' ? 'flex-begin' : 'flex-end',
},
}, this.props.children),
])
)
}
AccountPanel.prototype.genIcon = function (seed, picOrder) {
const props = this.props
// When there is no seed value, this is a contract creation.
// We then show the contract icon.
if (!seed) {
return h('.identicon-wrapper.flex-column.select-none', {
style: {
order: picOrder === 'left' ? 1 : 3,
},
}, [
h('i.fa.fa-file-text-o.fa-lg', {
style: {
fontSize: '42px',
transform: 'translate(0px, -16px)',
},
}),
])
}
// If there was a seed, we return an identicon for that address.
return h('.identicon-wrapper.flex-column.select-none', {
style: {
order: picOrder === 'left' ? 1 : 3,
},
}, [
h(Identicon, {
address: seed,
imageify: props.imageifyIdenticons,
}),
])
}

@ -2,10 +2,17 @@ const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const AccountPanel = require('./account-panel')
const MiniAccountPanel = require('./mini-account-panel')
const EtherBalance = require('./eth-balance')
const addressSummary = require('../util').addressSummary
const readableDate = require('../util').readableDate
const formatBalance = require('../util').formatBalance
const nameForAddress = require('../../lib/contract-namer')
const ethUtil = require('ethereumjs-util')
const BN = ethUtil.BN
const baseGasFee = new BN('21000', 10)
const gasCost = new BN('4a817c800', 16)
const baseFeeHex = baseGasFee.mul(gasCost).toString(16)
module.exports = PendingTxDetails
@ -14,52 +21,204 @@ function PendingTxDetails () {
Component.call(this)
}
PendingTxDetails.prototype.render = function () {
var state = this.props
return this.renderGeneric(h, state)
}
const PTXP = PendingTxDetails.prototype
PendingTxDetails.prototype.renderGeneric = function (h, state) {
var txData = state.txData
PTXP.render = function () {
var props = this.props
var txData = props.txData
var txParams = txData.txParams || {}
var address = txParams.from || state.selectedAddress
var identity = state.identities[address] || { address: address }
var account = state.accounts[address] || { address: address }
var address = txParams.from || props.selectedAddress
var identity = props.identities[address] || { address: address }
var balance = props.accounts[address].balance
return (
var gasCost = ethUtil.stripHexPrefix(txParams.gas || baseFeeHex)
var txValue = ethUtil.stripHexPrefix(txParams.value || '0x0')
var maxCost = ((new BN(txValue, 16)).add(new BN(gasCost, 16))).toString(16)
var dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0
return (
h('div', [
// account that will sign
h(AccountPanel, {
showFullAddress: true,
identity: identity,
account: account,
imageifyIdenticons: state.imageifyIdenticons,
h('.flex-row.flex-center', {
style: {
maxWidth: '100%',
},
}, [
h(MiniAccountPanel, {
imageSeed: address,
imageifyIdenticons: props.imageifyIdenticons,
picOrder: 'right',
}, [
h('span.font-small', {
style: {
fontFamily: 'Montserrat Bold, Montserrat, sans-serif',
},
}, identity.name),
h('span.font-small', {
style: {
fontFamily: 'Montserrat Light, Montserrat, sans-serif',
},
}, addressSummary(address, 6, 4, false)),
h('span.font-small', {
style: {
fontFamily: 'Montserrat Light, Montserrat, sans-serif',
},
}, h(EtherBalance, {
value: balance,
inline: true,
})),
]),
h('img', {
src: 'images/forward-carrat.svg',
style: {
padding: '5px 6px 0px 10px',
height: '37px',
},
}),
// tx data
h('.tx-data.flex-column.flex-justify-center.flex-grow.select-none', [
this.miniAccountPanelForRecipient(),
]),
h('style', `
.table-box {
margin: 7px 0px 0px 0px;
width: 100%;
}
.table-box .row {
margin: 0px;
background: rgb(236,236,236);
display: flex;
justify-content: space-between;
font-family: Montserrat Light, sans-serif;
font-size: 13px;
padding: 5px 25px;
}
.table-box .row .value {
font-family: Montserrat Regular;
}
`),
h('.table-box', [
h('.flex-row.flex-space-between', [
h('label.font-small', 'TO ADDRESS'),
h('span.font-small', addressSummary(txParams.to)),
h('.row', [
h('.cell.label', 'Amount'),
h('.cell.value', formatBalance(txParams.value)),
]),
h('.flex-row.flex-space-between', [
h('label.font-small', 'DATE'),
h('span.font-small', readableDate(txData.time)),
h('.cell.row', [
h('.cell.label', 'Max Transaction Fee'),
h('.cell.value', formatBalance(gasCost)),
]),
h('.flex-row.flex-space-between', [
h('label.font-small', 'AMOUNT'),
h('span.font-small', formatBalance(txParams.value)),
h('.cell.row', {
style: {
fontFamily: 'Montserrat Regular',
background: 'white',
padding: '10px 25px',
},
}, [
h('.cell.label', 'Max Total'),
h('.cell.value', {
style: {
display: 'flex',
alignItems: 'center',
},
}, [
h(EtherBalance, {
value: maxCost,
inline: true,
labelColor: 'black',
fontSize: '16px',
}),
]),
]),
])
h('.cell.row', {
style: {
background: '#f7f7f7',
paddingBottom: '0px',
},
}, [
h('.cell.label'),
h('.cell.value', {
style: {
fontFamily: 'Montserrat Light',
fontSize: '11px',
},
}, `Data included: ${dataLength} bytes`),
]),
]), // End of Table
this.warnIfNeeded(),
])
)
}
PTXP.miniAccountPanelForRecipient = function () {
var props = this.props
var txData = props.txData
var txParams = txData.txParams || {}
var isContractDeploy = !('to' in txParams)
// If it's not a contract deploy, send to the account
if (!isContractDeploy) {
return h(MiniAccountPanel, {
imageSeed: txParams.to,
imageifyIdenticons: props.imageifyIdenticons,
picOrder: 'left',
}, [
h('span.font-small', {
style: {
fontFamily: 'Montserrat Bold, Montserrat, sans-serif',
},
}, nameForAddress(txParams.to, props.identities)),
h('span.font-small', {
style: {
fontFamily: 'Montserrat Light, Montserrat, sans-serif',
},
}, addressSummary(txParams.to, 6, 4, false)),
])
} else {
return h(MiniAccountPanel, {
imageifyIdenticons: props.imageifyIdenticons,
picOrder: 'left',
}, [
h('span.font-small', {
style: {
fontFamily: 'Montserrat Bold, Montserrat, sans-serif',
},
}, 'New Contract'),
])
}
}
// Should analyze if there is a DELEGATECALL opcode
// in the recipient contract, and show a warning if so.
PTXP.warnIfNeeded = function () {
const containsDelegateCall = !!this.props.txData.containsDelegateCall
if (!containsDelegateCall) {
return null
}
return h('span.error', {
style: {
fontFamily: 'Montserrat Light',
fontSize: '13px',
display: 'flex',
justifyContent: 'center',
},
}, [
h('i.fa.fa-lg.fa-info-circle', { style: { margin: '5px' } }),
h('span', ' Your identity may be used in other contracts!'),
])
}

@ -21,29 +21,35 @@ PendingTx.prototype.render = function () {
key: txData.id,
}, [
// header
h('h3', {
style: {
fontWeight: 'bold',
textAlign: 'center',
},
}, 'Submit Transaction'),
// tx info
h(PendingTxDetails, state),
h('style', `
.conf-buttons button {
margin-left: 10px;
text-transform: uppercase;
}
`),
// send + cancel
h('.flex-row.flex-space-around', [
h('button', {
h('.flex-row.flex-space-around.conf-buttons', {
style: {
display: 'flex',
justifyContent: 'flex-end',
margin: '14px 25px',
},
}, [
h('button.confirm', {
onClick: state.sendTransaction,
style: { background: 'rgb(251,117,1)' },
}, 'Accept'),
h('button.cancel', {
onClick: state.cancelTransaction,
style: { background: 'rgb(254,35,17)' },
}, 'Reject'),
h('button', {
onClick: state.sendTransaction,
}, 'Approve'),
]),
])
)
}

@ -39,14 +39,14 @@ ConfirmTxScreen.prototype.render = function () {
return (
h('.unconftx-section.flex-column.flex-grow', [
h('.flex-column.flex-grow', [
// subtitle and nav
h('.section-title.flex-row.flex-center', [
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
onClick: this.goHome.bind(this),
}),
h('h2.page-subtitle', 'Confirmation'),
h('h2.page-subtitle', 'Confirm Transaction'),
]),
h('h3', {

@ -411,10 +411,6 @@ input.large-input {
}
/* tx confirm */
.unconftx-section {
margin: 0 20px;
}
.unconftx-section input[type=password] {
height: 22px;
padding: 2px;

@ -220,3 +220,9 @@ hr.horizontal-line {
.invisible {
visibility: hidden;
}
.one-line-concat {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

@ -21,6 +21,7 @@ for (var currency in valueTable) {
module.exports = {
valuesFor: valuesFor,
addressSummary: addressSummary,
miniAddressSummary: miniAddressSummary,
isAllOneCase: isAllOneCase,
isValidAddress: isValidAddress,
numericBalance: numericBalance,
@ -44,10 +45,19 @@ function valuesFor (obj) {
.map(function (key) { return obj[key] })
}
function addressSummary (address) {
function addressSummary (address, firstSegLength = 10, lastSegLength = 4, includeHex = true) {
if (!address) return ''
let checked = ethUtil.toChecksumAddress(address)
if (!includeHex) {
checked = ethUtil.stripHexPrefix(checked)
}
return checked ? checked.slice(0, firstSegLength) + '...' + checked.slice(checked.length - lastSegLength) : '...'
}
function miniAddressSummary (address) {
if (!address) return ''
var checked = ethUtil.toChecksumAddress(address)
return checked ? checked.slice(0, 2 + 8) + '...' + checked.slice(-4) : '...'
return checked ? checked.slice(0, 4) + '...' + checked.slice(-4) : '...'
}
function isValidAddress (address) {
@ -95,7 +105,8 @@ function parseBalance (balance) {
return [beforeDecimal, afterDecimal]
}
// Takes wei hex, returns "None" or "${formattedAmount} ETH"
// Takes wei hex, returns an object with three properties.
// Its "formatted" property is what we generally use to render values.
function formatBalance (balance, decimalsToKeep) {
var parsed = parseBalance(balance)
var beforeDecimal = parsed[0]

@ -0,0 +1,31 @@
/* CONTRACT NAMER
*
* Takes an address,
* Returns a nicname if we have one stored,
* otherwise returns null.
*/
// Nickname keys must be stored in lower case.
const nicknames = {}
module.exports = function(addr, identities = {}) {
const address = addr.toLowerCase()
const ids = hashFromIdentities(identities)
console.dir({ addr, ids })
return addrFromHash(address, ids) || addrFromHash(address, nicknames)
}
function hashFromIdentities(identities) {
const result = {}
for (let key in identities) {
result[key] = identities[key].name
}
return result
}
function addrFromHash(addr, hash) {
const address = addr.toLowerCase()
return hash[address] || null
}
Loading…
Cancel
Save