From e654f19cd9c3fcb2f590734f076dda6a5a7e802f Mon Sep 17 00:00:00 2001
From: Whymarrh Whitby
Date: Tue, 17 Sep 2019 12:14:53 -0400
Subject: [PATCH 1/9] ENS Reverse Resolution support
---
app/scripts/controllers/ens/ens.js | 25 +++++
app/scripts/controllers/ens/index.js | 67 +++++++++++++
app/scripts/metamask-controller.js | 11 +++
.../app/controllers/ens-controller-test.js | 94 +++++++++++++++++++
.../sender-to-recipient.component.js | 2 +-
.../confirm-transaction-base.component.js | 4 +-
.../confirm-transaction-base.container.js | 13 ++-
ui/app/store/actions.js | 15 +++
8 files changed, 227 insertions(+), 4 deletions(-)
create mode 100644 app/scripts/controllers/ens/ens.js
create mode 100644 app/scripts/controllers/ens/index.js
create mode 100644 test/unit/app/controllers/ens-controller-test.js
diff --git a/app/scripts/controllers/ens/ens.js b/app/scripts/controllers/ens/ens.js
new file mode 100644
index 000000000000..53d6e89e8608
--- /dev/null
+++ b/app/scripts/controllers/ens/ens.js
@@ -0,0 +1,25 @@
+const EthJsEns = require('ethjs-ens')
+const ensNetworkMap = require('ethjs-ens/lib/network-map.json')
+
+class Ens {
+ constructor ({ network, provider } = {}) {
+ this._ethJsEns = new EthJsEns({
+ network,
+ provider,
+ })
+ }
+
+ lookup (ensName) {
+ return this._ethJsEns.lookup(ensName)
+ }
+
+ reverse (address) {
+ return this._ethJsEns.reverse(address)
+ }
+}
+
+Ens.getNetworkEnsSupport = function getNetworkEnsSupport (network) {
+ return Boolean(ensNetworkMap[network])
+}
+
+module.exports = Ens
diff --git a/app/scripts/controllers/ens/index.js b/app/scripts/controllers/ens/index.js
new file mode 100644
index 000000000000..e8302d5348e5
--- /dev/null
+++ b/app/scripts/controllers/ens/index.js
@@ -0,0 +1,67 @@
+const ethUtil = require('ethereumjs-util')
+const ObservableStore = require('obs-store')
+const Ens = require('./ens')
+
+const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
+const ZERO_X_ERROR_ADDRESS = '0x'
+
+class EnsController {
+ constructor ({ ens, provider, networkStore } = {}) {
+ this._ens = ens
+ if (!this._ens) {
+ const network = networkStore.getState()
+ if (Ens.getNetworkEnsSupport(network)) {
+ this._ens = new Ens({
+ network,
+ provider,
+ })
+ }
+
+ networkStore.subscribe((network) => {
+ this._ens = new Ens({
+ network,
+ provider,
+ })
+ })
+ }
+
+ this.store = new ObservableStore({
+ ensResolutionsByAddress: {},
+ })
+ }
+
+ reverseResolveAddress (address) {
+ return this._reverseResolveAddress(ethUtil.toChecksumAddress(address))
+ }
+
+ async _reverseResolveAddress (address) {
+ if (!this._ens) {
+ return undefined
+ }
+
+ const domain = await this._ens.reverse(address)
+ const registeredAddress = await this._ens.lookup(domain)
+ if (registeredAddress === ZERO_ADDRESS) throw new Error('No address for name')
+ if (registeredAddress === ZERO_X_ERROR_ADDRESS) throw new Error('ENS Registry error')
+
+ if (ethUtil.toChecksumAddress(registeredAddress) === address) {
+ // TODO deal with homoglyphs
+ this._updateResolutionsByAddress(address, domain)
+ return domain
+ } else {
+ return undefined
+ }
+ }
+
+ _updateResolutionsByAddress (address, domain) {
+ const oldState = this.store.getState()
+ this.store.putState({
+ ensResolutionsByAddress: {
+ ...oldState.ensResolutionsByAddress,
+ [address]: domain,
+ },
+ })
+ }
+}
+
+module.exports = EnsController
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index eac6d1e81d90..1c607a4c65eb 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -23,6 +23,7 @@ const createLoggerMiddleware = require('./lib/createLoggerMiddleware')
const providerAsMiddleware = require('eth-json-rpc-middleware/providerAsMiddleware')
const {setupMultiplex} = require('./lib/stream-utils.js')
const KeyringController = require('eth-keyring-controller')
+const EnsController = require('./controllers/ens')
const NetworkController = require('./controllers/network')
const PreferencesController = require('./controllers/preferences')
const AppStateController = require('./controllers/app-state')
@@ -138,6 +139,11 @@ module.exports = class MetamaskController extends EventEmitter {
networkController: this.networkController,
})
+ this.ensController = new EnsController({
+ provider: this.provider,
+ networkStore: this.networkController.networkStore,
+ })
+
this.incomingTransactionsController = new IncomingTransactionsController({
blockTracker: this.blockTracker,
networkController: this.networkController,
@@ -315,6 +321,8 @@ module.exports = class MetamaskController extends EventEmitter {
// ThreeBoxController
ThreeBoxController: this.threeBoxController.store,
ABTestController: this.abTestController.store,
+ // ENS Controller
+ EnsController: this.ensController.store,
})
this.memStore.subscribe(this.sendUpdate.bind(this))
}
@@ -501,6 +509,9 @@ module.exports = class MetamaskController extends EventEmitter {
// AppStateController
setLastActiveTime: nodeify(this.appStateController.setLastActiveTime, this.appStateController),
+ // EnsController
+ tryReverseResolveAddress: nodeify(this.ensController.reverseResolveAddress, this.ensController),
+
// KeyringController
setLocked: nodeify(this.setLocked, this),
createNewVaultAndKeychain: nodeify(this.createNewVaultAndKeychain, this),
diff --git a/test/unit/app/controllers/ens-controller-test.js b/test/unit/app/controllers/ens-controller-test.js
new file mode 100644
index 000000000000..b154a0934091
--- /dev/null
+++ b/test/unit/app/controllers/ens-controller-test.js
@@ -0,0 +1,94 @@
+const assert = require('assert')
+const sinon = require('sinon')
+const ObservableStore = require('obs-store')
+const HttpProvider = require('ethjs-provider-http')
+const EnsController = require('../../../../app/scripts/controllers/ens')
+
+const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
+const ZERO_X_ERROR_ADDRESS = '0x'
+
+describe('EnsController', function () {
+ describe('#constructor', function () {
+ it('should construct the controller given a provider and a network', async () => {
+ const provider = new HttpProvider('https://ropsten.infura.io')
+ const currentNetworkId = '3'
+ const networkStore = new ObservableStore(currentNetworkId)
+ const ens = new EnsController({
+ provider,
+ networkStore,
+ })
+
+ assert.ok(ens._ens)
+ })
+
+ it('should construct the controller given an existing ENS instance', async () => {
+ const ens = new EnsController({
+ ens: {},
+ })
+
+ assert.ok(ens._ens)
+ })
+ })
+
+ describe('#reverseResolveName', function () {
+ it('should resolve to an ENS name', async () => {
+ const address = '0x8e5d75d60224ea0c33d0041e75de68b1c3cb6dd5'
+ const ens = new EnsController({
+ ens: {
+ reverse: sinon.stub().withArgs(address).returns('peaksignal.eth'),
+ lookup: sinon.stub().withArgs('peaksignal.eth').returns(address),
+ },
+ })
+
+ const name = await ens.reverseResolveAddress(address)
+ assert.equal(name, 'peaksignal.eth')
+ })
+
+ it('should fail if the name is registered to a different address than the reverse-resolved', async () => {
+ const address = '0x8e5d75d60224ea0c33d0041e75de68b1c3cb6dd5'
+ const ens = new EnsController({
+ ens: {
+ reverse: sinon.stub().withArgs(address).returns('peaksignal.eth'),
+ lookup: sinon.stub().withArgs('peaksignal.eth').returns('0xfoo'),
+ },
+ })
+
+ const name = await ens.reverseResolveAddress(address)
+ assert.strictEqual(name, undefined)
+ })
+
+ it('should throw an error when the lookup resolves to the zero address', async () => {
+ const address = '0x8e5d75d60224ea0c33d0041e75de68b1c3cb6dd5'
+ const ens = new EnsController({
+ ens: {
+ reverse: sinon.stub().withArgs(address).returns('peaksignal.eth'),
+ lookup: sinon.stub().withArgs('peaksignal.eth').returns(ZERO_ADDRESS),
+ },
+ })
+
+ try {
+ await ens.reverseResolveAddress(address)
+ assert.fail('#reverseResolveAddress did not throw')
+ } catch (e) {
+ assert.ok(e)
+ }
+ })
+
+ it('should throw an error the lookup resolves to the zero x address', async () => {
+ const address = '0x8e5d75d60224ea0c33d0041e75de68b1c3cb6dd5'
+ const ens = new EnsController({
+ ens: {
+ reverse: sinon.stub().withArgs(address).returns('peaksignal.eth'),
+ lookup: sinon.stub().withArgs('peaksignal.eth').returns(ZERO_X_ERROR_ADDRESS),
+ },
+ })
+
+ try {
+ await ens.reverseResolveAddress(address)
+ assert.fail('#reverseResolveAddress did not throw')
+ } catch (e) {
+ assert.ok(e)
+ }
+ })
+ })
+})
diff --git a/ui/app/components/ui/sender-to-recipient/sender-to-recipient.component.js b/ui/app/components/ui/sender-to-recipient/sender-to-recipient.component.js
index c8e7a1870b07..9496d03bd18b 100644
--- a/ui/app/components/ui/sender-to-recipient/sender-to-recipient.component.js
+++ b/ui/app/components/ui/sender-to-recipient/sender-to-recipient.component.js
@@ -116,7 +116,7 @@ export default class SenderToRecipient extends PureComponent {
{ addressOnly ? `${t('to')}: ` : '' }
{
addressOnly
- ? checksummedRecipientAddress
+ ? (recipientNickname || checksummedRecipientAddress)
: (recipientNickname || recipientName || this.context.t('newContract'))
}
diff --git a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js
index e3d1ba44c455..49dffeb66dc3 100644
--- a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js
+++ b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js
@@ -103,6 +103,7 @@ export default class ConfirmTransactionBase extends Component {
transactionCategory: PropTypes.string,
getNextNonce: PropTypes.func,
nextNonce: PropTypes.number,
+ tryReverseResolveAddress: PropTypes.func.isRequired,
}
state = {
@@ -567,7 +568,7 @@ export default class ConfirmTransactionBase extends Component {
}
componentDidMount () {
- const { txData: { origin, id } = {}, cancelTransaction, getNextNonce } = this.props
+ const { toAddress, txData: { origin, id } = {}, cancelTransaction, getNextNonce, tryReverseResolveAddress } = this.props
const { metricsEvent } = this.context
metricsEvent({
eventOpts: {
@@ -598,6 +599,7 @@ export default class ConfirmTransactionBase extends Component {
}
getNextNonce()
+ tryReverseResolveAddress(toAddress)
}
componentWillUnmount () {
diff --git a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js
index 6ffa13bd0dd8..f03c89912d38 100644
--- a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js
+++ b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js
@@ -18,6 +18,7 @@ import {
setMetaMetricsSendCount,
updateTransaction,
getNextNonce,
+ tryReverseResolveAddress,
} from '../../store/actions'
import {
INSUFFICIENT_FUNDS_ERROR_KEY,
@@ -51,6 +52,7 @@ const mapStateToProps = (state, ownProps) => {
const isMainnet = getIsMainnet(state)
const { confirmTransaction, metamask } = state
const {
+ ensResolutionsByAddress,
conversionRate,
identities,
addressBook,
@@ -93,8 +95,12 @@ const mapStateToProps = (state, ownProps) => {
: addressSlicer(checksumAddress(toAddress))
)
- const addressBookObject = addressBook[checksumAddress(toAddress)]
- const toNickname = addressBookObject ? addressBookObject.name : ''
+ const checksummedAddress = checksumAddress(toAddress)
+ const ensName = ensResolutionsByAddress[checksummedAddress] || ''
+ const addressBookObject = addressBook[checksummedAddress]
+ const toNickname = addressBookObject
+ ? addressBookObject.name
+ : ensName
const isTxReprice = Boolean(lastGasPrice)
const transactionStatus = transaction ? transaction.status : ''
@@ -176,6 +182,9 @@ const mapStateToProps = (state, ownProps) => {
export const mapDispatchToProps = dispatch => {
return {
+ tryReverseResolveAddress: (address) => {
+ return dispatch(tryReverseResolveAddress(address))
+ },
updateCustomNonce: value => {
customNonceValue = value
dispatch(updateCustomNonce(value))
diff --git a/ui/app/store/actions.js b/ui/app/store/actions.js
index 59bad34bf71b..f760245901cf 100644
--- a/ui/app/store/actions.js
+++ b/ui/app/store/actions.js
@@ -392,6 +392,8 @@ var actions = {
setShowRestorePromptToFalse,
turnThreeBoxSyncingOn,
turnThreeBoxSyncingOnAndInitialize,
+
+ tryReverseResolveAddress,
}
module.exports = actions
@@ -599,6 +601,19 @@ function requestRevealSeedWords (password) {
}
}
+function tryReverseResolveAddress (address) {
+ return () => {
+ return new Promise((resolve) => {
+ background.tryReverseResolveAddress(address, (err) => {
+ if (err) {
+ log.error(err)
+ }
+ resolve()
+ })
+ })
+ }
+}
+
function fetchInfoToSync () {
return dispatch => {
log.debug(`background.fetchInfoToSync`)
From d48a381e819573d4918fd572942b89b49f7f8045 Mon Sep 17 00:00:00 2001
From: Whymarrh Whitby
Date: Mon, 28 Oct 2019 10:31:07 -0230
Subject: [PATCH 2/9] Save punycode for ENS domains with Unicode characters
---
app/scripts/controllers/ens/index.js | 4 ++--
package.json | 1 +
yarn.lock | 2 +-
3 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/app/scripts/controllers/ens/index.js b/app/scripts/controllers/ens/index.js
index e8302d5348e5..4d418bbad051 100644
--- a/app/scripts/controllers/ens/index.js
+++ b/app/scripts/controllers/ens/index.js
@@ -1,5 +1,6 @@
const ethUtil = require('ethereumjs-util')
const ObservableStore = require('obs-store')
+const punycode = require('punycode')
const Ens = require('./ens')
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
@@ -45,8 +46,7 @@ class EnsController {
if (registeredAddress === ZERO_X_ERROR_ADDRESS) throw new Error('ENS Registry error')
if (ethUtil.toChecksumAddress(registeredAddress) === address) {
- // TODO deal with homoglyphs
- this._updateResolutionsByAddress(address, domain)
+ this._updateResolutionsByAddress(address, punycode.toASCII(domain))
return domain
} else {
return undefined
diff --git a/package.json b/package.json
index 3a8d5ea1a785..5c155f5eae1c 100644
--- a/package.json
+++ b/package.json
@@ -141,6 +141,7 @@
"prop-types": "^15.6.1",
"pubnub": "4.24.4",
"pump": "^3.0.0",
+ "punycode": "^2.1.1",
"qrcode-generator": "1.4.1",
"ramda": "^0.24.1",
"react": "^15.6.2",
diff --git a/yarn.lock b/yarn.lock
index d752122bd1a3..774d204e73c1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -21366,7 +21366,7 @@ punycode@^1.2.4, punycode@^1.3.2, punycode@^1.4.1:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
-punycode@^2.1.0:
+punycode@^2.1.0, punycode@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
From 4e86b71b32f1164ec7a8a2e9e53c8b9faf863c1b Mon Sep 17 00:00:00 2001
From: Whymarrh Whitby
Date: Mon, 28 Oct 2019 10:45:22 -0230
Subject: [PATCH 3/9] Clear EnsController store on network change
---
app/scripts/controllers/ens/index.js | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/app/scripts/controllers/ens/index.js b/app/scripts/controllers/ens/index.js
index 4d418bbad051..a2cad3799810 100644
--- a/app/scripts/controllers/ens/index.js
+++ b/app/scripts/controllers/ens/index.js
@@ -8,6 +8,10 @@ const ZERO_X_ERROR_ADDRESS = '0x'
class EnsController {
constructor ({ ens, provider, networkStore } = {}) {
+ const initState = {
+ ensResolutionsByAddress: {},
+ }
+
this._ens = ens
if (!this._ens) {
const network = networkStore.getState()
@@ -19,6 +23,7 @@ class EnsController {
}
networkStore.subscribe((network) => {
+ this.store.putState(initState)
this._ens = new Ens({
network,
provider,
@@ -26,9 +31,7 @@ class EnsController {
})
}
- this.store = new ObservableStore({
- ensResolutionsByAddress: {},
- })
+ this.store = new ObservableStore(initState)
}
reverseResolveAddress (address) {
From 9cc1d32f4b87c15c354470199c675c8453ac19f5 Mon Sep 17 00:00:00 2001
From: Whymarrh Whitby
Date: Mon, 28 Oct 2019 11:16:04 -0230
Subject: [PATCH 4/9] Use cached results when reverse-resolving ENS names
---
app/scripts/controllers/ens/index.js | 5 +++++
.../unit/app/controllers/ens-controller-test.js | 17 +++++++++++++++++
2 files changed, 22 insertions(+)
diff --git a/app/scripts/controllers/ens/index.js b/app/scripts/controllers/ens/index.js
index a2cad3799810..d43d8f8794a8 100644
--- a/app/scripts/controllers/ens/index.js
+++ b/app/scripts/controllers/ens/index.js
@@ -43,6 +43,11 @@ class EnsController {
return undefined
}
+ const state = this.store.getState()
+ if (state.ensResolutionsByAddress[address]) {
+ return state.ensResolutionsByAddress[address]
+ }
+
const domain = await this._ens.reverse(address)
const registeredAddress = await this._ens.lookup(domain)
if (registeredAddress === ZERO_ADDRESS) throw new Error('No address for name')
diff --git a/test/unit/app/controllers/ens-controller-test.js b/test/unit/app/controllers/ens-controller-test.js
index b154a0934091..298eb6ad6cdf 100644
--- a/test/unit/app/controllers/ens-controller-test.js
+++ b/test/unit/app/controllers/ens-controller-test.js
@@ -44,6 +44,23 @@ describe('EnsController', function () {
assert.equal(name, 'peaksignal.eth')
})
+ it('should only resolve an ENS name once', async () => {
+ const address = '0x8e5d75d60224ea0c33d0041e75de68b1c3cb6dd5'
+ const reverse = sinon.stub().withArgs(address).returns('peaksignal.eth')
+ const lookup = sinon.stub().withArgs('peaksignal.eth').returns(address)
+ const ens = new EnsController({
+ ens: {
+ reverse,
+ lookup,
+ },
+ })
+
+ assert.equal(await ens.reverseResolveAddress(address), 'peaksignal.eth')
+ assert.equal(await ens.reverseResolveAddress(address), 'peaksignal.eth')
+ assert.ok(lookup.calledOnce)
+ assert.ok(reverse.calledOnce)
+ })
+
it('should fail if the name is registered to a different address than the reverse-resolved', async () => {
const address = '0x8e5d75d60224ea0c33d0041e75de68b1c3cb6dd5'
const ens = new EnsController({
From dfcc4654e8aa5ea89417ad5e9a11b4c9ada62013 Mon Sep 17 00:00:00 2001
From: Whymarrh Whitby
Date: Mon, 28 Oct 2019 10:53:42 -0230
Subject: [PATCH 5/9] Update SenderToRecipient recipientEns tooltip
---
.../confirm-page-container.component.js | 3 ++
.../sender-to-recipient.component.js | 40 +++++++++++++++----
ui/app/components/ui/tooltip-v2.js | 7 +++-
.../confirm-transaction-base.component.js | 3 ++
.../confirm-transaction-base.container.js | 7 ++--
5 files changed, 47 insertions(+), 13 deletions(-)
diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container.component.js
index d26daf78694c..41d9d5952a1e 100644
--- a/ui/app/components/app/confirm-page-container/confirm-page-container.component.js
+++ b/ui/app/components/app/confirm-page-container/confirm-page-container.component.js
@@ -24,6 +24,7 @@ export default class ConfirmPageContainer extends Component {
fromName: PropTypes.string,
toAddress: PropTypes.string,
toName: PropTypes.string,
+ toEns: PropTypes.string,
toNickname: PropTypes.string,
// Content
contentComponent: PropTypes.node,
@@ -69,6 +70,7 @@ export default class ConfirmPageContainer extends Component {
fromName,
fromAddress,
toName,
+ toEns,
toNickname,
toAddress,
disabled,
@@ -128,6 +130,7 @@ export default class ConfirmPageContainer extends Component {
senderAddress={fromAddress}
recipientName={toName}
recipientAddress={toAddress}
+ recipientEns={toEns}
recipientNickname={toNickname}
assetImage={renderAssetImage ? assetImage : undefined}
/>
diff --git a/ui/app/components/ui/sender-to-recipient/sender-to-recipient.component.js b/ui/app/components/ui/sender-to-recipient/sender-to-recipient.component.js
index 9496d03bd18b..864f590fbca2 100644
--- a/ui/app/components/ui/sender-to-recipient/sender-to-recipient.component.js
+++ b/ui/app/components/ui/sender-to-recipient/sender-to-recipient.component.js
@@ -5,7 +5,7 @@ import Identicon from '../identicon'
import Tooltip from '../tooltip-v2'
import copyToClipboard from 'copy-to-clipboard'
import { DEFAULT_VARIANT, CARDS_VARIANT, FLAT_VARIANT } from './sender-to-recipient.constants'
-import { checksumAddress } from '../../../helpers/utils/util'
+import { checksumAddress, addressSlicer } from '../../../helpers/utils/util'
const variantHash = {
[DEFAULT_VARIANT]: 'sender-to-recipient--default',
@@ -18,6 +18,7 @@ export default class SenderToRecipient extends PureComponent {
senderName: PropTypes.string,
senderAddress: PropTypes.string,
recipientName: PropTypes.string,
+ recipientEns: PropTypes.string,
recipientAddress: PropTypes.string,
recipientNickname: PropTypes.string,
t: PropTypes.func,
@@ -60,14 +61,28 @@ export default class SenderToRecipient extends PureComponent {
return (
{t('copiedExclamation')}
+ : addressOnly
+ ? {t('copyAddress')}
+ : (
+
+ {addressSlicer(checksummedSenderAddress)}
+ {t('copyAddress')}
+
+ )
+ }
wrapperClassName="sender-to-recipient__tooltip-wrapper"
containerClassName="sender-to-recipient__tooltip-container"
onHidden={() => this.setState({ senderAddressCopied: false })}
>
- { addressOnly ? `${t('from')}: ` : '' }
- { addressOnly ? checksummedSenderAddress : senderName }
+ {
+ addressOnly
+ ? {`${t('from')}: ${checksummedSenderAddress}`}
+ : senderName
+ }
)
@@ -90,7 +105,7 @@ export default class SenderToRecipient extends PureComponent {
renderRecipientWithAddress () {
const { t } = this.context
- const { recipientName, recipientAddress, recipientNickname, addressOnly, onRecipientClick } = this.props
+ const { recipientEns, recipientName, recipientAddress, recipientNickname, addressOnly, onRecipientClick } = this.props
const checksummedRecipientAddress = checksumAddress(recipientAddress)
return (
@@ -107,7 +122,18 @@ export default class SenderToRecipient extends PureComponent {
{ this.renderRecipientIdenticon() }
{t('copiedExclamation')}
+ : (addressOnly && !recipientNickname && !recipientEns)
+ ? {t('copyAddress')}
+ : (
+
+ {addressSlicer(checksummedRecipientAddress)}
+ {t('copyAddress')}
+
+ )
+ }
wrapperClassName="sender-to-recipient__tooltip-wrapper"
containerClassName="sender-to-recipient__tooltip-container"
onHidden={() => this.setState({ recipientAddressCopied: false })}
@@ -117,7 +143,7 @@ export default class SenderToRecipient extends PureComponent {
{
addressOnly
? (recipientNickname || checksummedRecipientAddress)
- : (recipientNickname || recipientName || this.context.t('newContract'))
+ : (recipientNickname || recipientEns || recipientName || this.context.t('newContract'))
}
diff --git a/ui/app/components/ui/tooltip-v2.js b/ui/app/components/ui/tooltip-v2.js
index b5402679491a..8d63e1515b2b 100644
--- a/ui/app/components/ui/tooltip-v2.js
+++ b/ui/app/components/ui/tooltip-v2.js
@@ -8,6 +8,7 @@ export default class Tooltip extends PureComponent {
children: null,
containerClassName: '',
hideOnClick: false,
+ html: null,
onHidden: null,
position: 'left',
size: 'small',
@@ -21,6 +22,7 @@ export default class Tooltip extends PureComponent {
children: PropTypes.node,
containerClassName: PropTypes.string,
disabled: PropTypes.bool,
+ html: PropTypes.node,
onHidden: PropTypes.func,
position: PropTypes.oneOf([
'top',
@@ -38,9 +40,9 @@ export default class Tooltip extends PureComponent {
}
render () {
- const {arrow, children, containerClassName, disabled, position, size, title, trigger, onHidden, wrapperClassName, style } = this.props
+ const {arrow, children, containerClassName, disabled, position, html, size, title, trigger, onHidden, wrapperClassName, style } = this.props
- if (!title) {
+ if (!title && !html) {
return (
{children}
@@ -51,6 +53,7 @@ export default class Tooltip extends PureComponent {
return (
{
)
const checksummedAddress = checksumAddress(toAddress)
- const ensName = ensResolutionsByAddress[checksummedAddress] || ''
const addressBookObject = addressBook[checksummedAddress]
- const toNickname = addressBookObject
- ? addressBookObject.name
- : ensName
+ const toEns = ensResolutionsByAddress[checksummedAddress] || ''
+ const toNickname = addressBookObject ? addressBookObject.name : ''
const isTxReprice = Boolean(lastGasPrice)
const transactionStatus = transaction ? transaction.status : ''
@@ -140,6 +138,7 @@ const mapStateToProps = (state, ownProps) => {
fromAddress,
fromName,
toAddress,
+ toEns,
toName,
toNickname,
ethTransactionAmount,
From cf548b39844af92309bed85bd4c73bcbf593c3d6 Mon Sep 17 00:00:00 2001
From: Whymarrh Whitby
Date: Mon, 28 Oct 2019 12:48:55 -0230
Subject: [PATCH 6/9] Display ENS names in tx activity log
---
.../transaction-list-item-details/index.js | 2 +-
...transaction-list-item-details.component.js | 24 ++++++++++++++--
...transaction-list-item-details.container.js | 28 +++++++++++++++++++
.../transaction-list-item.component.js | 3 ++
.../sender-to-recipient.component.js | 2 +-
5 files changed, 54 insertions(+), 5 deletions(-)
create mode 100644 ui/app/components/app/transaction-list-item-details/transaction-list-item-details.container.js
diff --git a/ui/app/components/app/transaction-list-item-details/index.js b/ui/app/components/app/transaction-list-item-details/index.js
index 0e878d032923..83bd53e7d50e 100644
--- a/ui/app/components/app/transaction-list-item-details/index.js
+++ b/ui/app/components/app/transaction-list-item-details/index.js
@@ -1 +1 @@
-export { default } from './transaction-list-item-details.component'
+export { default } from './transaction-list-item-details.container'
diff --git a/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js b/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js
index 983bbf6e51f9..f27c74970d86 100644
--- a/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js
+++ b/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js
@@ -17,6 +17,10 @@ export default class TransactionListItemDetails extends PureComponent {
metricsEvent: PropTypes.func,
}
+ static defaultProps = {
+ recipientEns: null,
+ }
+
static propTypes = {
onCancel: PropTypes.func,
onRetry: PropTypes.func,
@@ -26,7 +30,11 @@ export default class TransactionListItemDetails extends PureComponent {
isEarliestNonce: PropTypes.bool,
cancelDisabled: PropTypes.bool,
transactionGroup: PropTypes.object,
+ recipientEns: PropTypes.string,
+ recipientAddress: PropTypes.string.isRequired,
rpcPrefs: PropTypes.object,
+ senderAddress: PropTypes.string.isRequired,
+ tryReverseResolveAddress: PropTypes.func.isRequired,
}
state = {
@@ -82,6 +90,12 @@ export default class TransactionListItemDetails extends PureComponent {
})
}
+ async componentDidMount () {
+ const { recipientAddress, tryReverseResolveAddress } = this.props
+
+ tryReverseResolveAddress(recipientAddress)
+ }
+
renderCancel () {
const { t } = this.context
const {
@@ -128,11 +142,14 @@ export default class TransactionListItemDetails extends PureComponent {
showRetry,
onCancel,
onRetry,
+ recipientEns,
+ recipientAddress,
rpcPrefs: { blockExplorerUrl } = {},
+ senderAddress,
isEarliestNonce,
} = this.props
const { primaryTransaction: transaction } = transactionGroup
- const { hash, txParams: { to, from } = {} } = transaction
+ const { hash } = transaction
return (
@@ -192,8 +209,9 @@ export default class TransactionListItemDetails extends PureComponent {
{
this.context.metricsEvent({
eventOpts: {
diff --git a/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.container.js b/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.container.js
new file mode 100644
index 000000000000..50f93f4978ba
--- /dev/null
+++ b/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.container.js
@@ -0,0 +1,28 @@
+import { connect } from 'react-redux'
+import TransactionListItemDetails from './transaction-list-item-details.component'
+import { checksumAddress } from '../../../helpers/utils/util'
+import { tryReverseResolveAddress } from '../../../store/actions'
+
+const mapStateToProps = (state, ownProps) => {
+ const { metamask } = state
+ const {
+ ensResolutionsByAddress,
+ } = metamask
+ const { recipientAddress } = ownProps
+ const address = checksumAddress(recipientAddress)
+ const recipientEns = ensResolutionsByAddress[address] || ''
+
+ return {
+ recipientEns,
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ tryReverseResolveAddress: (address) => {
+ return dispatch(tryReverseResolveAddress(address))
+ },
+ }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(TransactionListItemDetails)
diff --git a/ui/app/components/app/transaction-list-item/transaction-list-item.component.js b/ui/app/components/app/transaction-list-item/transaction-list-item.component.js
index bb6acae68ba6..feb1e0ab5369 100644
--- a/ui/app/components/app/transaction-list-item/transaction-list-item.component.js
+++ b/ui/app/components/app/transaction-list-item/transaction-list-item.component.js
@@ -185,6 +185,7 @@ export default class TransactionListItem extends PureComponent {
} = this.props
const { txParams = {} } = transaction
const { showTransactionDetails } = this.state
+ const fromAddress = txParams.from
const toAddress = tokenData
? tokenData.params && tokenData.params[0] && tokenData.params[0].value || txParams.to
: txParams.to
@@ -240,6 +241,8 @@ export default class TransactionListItem extends PureComponent {
showCancel={showCancel}
cancelDisabled={!hasEnoughCancelGas}
rpcPrefs={rpcPrefs}
+ senderAddress={fromAddress}
+ recipientAddress={toAddress}
/>
)
diff --git a/ui/app/components/ui/sender-to-recipient/sender-to-recipient.component.js b/ui/app/components/ui/sender-to-recipient/sender-to-recipient.component.js
index 864f590fbca2..3102f17e325e 100644
--- a/ui/app/components/ui/sender-to-recipient/sender-to-recipient.component.js
+++ b/ui/app/components/ui/sender-to-recipient/sender-to-recipient.component.js
@@ -142,7 +142,7 @@ export default class SenderToRecipient extends PureComponent {
{ addressOnly ? `${t('to')}: ` : '' }
{
addressOnly
- ? (recipientNickname || checksummedRecipientAddress)
+ ? (recipientNickname || recipientEns || checksummedRecipientAddress)
: (recipientNickname || recipientEns || recipientName || this.context.t('newContract'))
}
From 14d2b89b04a9366a3428d16270252b158d208560 Mon Sep 17 00:00:00 2001
From: Whymarrh Whitby
Date: Thu, 31 Oct 2019 18:09:04 -0230
Subject: [PATCH 7/9] Always subscribe to network changes for EnsController
---
app/scripts/controllers/ens/index.js | 15 ++++++------
.../app/controllers/ens-controller-test.js | 24 +++++++++++++++++++
2 files changed, 31 insertions(+), 8 deletions(-)
diff --git a/app/scripts/controllers/ens/index.js b/app/scripts/controllers/ens/index.js
index d43d8f8794a8..0595afdc3c01 100644
--- a/app/scripts/controllers/ens/index.js
+++ b/app/scripts/controllers/ens/index.js
@@ -21,17 +21,16 @@ class EnsController {
provider,
})
}
-
- networkStore.subscribe((network) => {
- this.store.putState(initState)
- this._ens = new Ens({
- network,
- provider,
- })
- })
}
this.store = new ObservableStore(initState)
+ networkStore.subscribe((network) => {
+ this.store.putState(initState)
+ this._ens = new Ens({
+ network,
+ provider,
+ })
+ })
}
reverseResolveAddress (address) {
diff --git a/test/unit/app/controllers/ens-controller-test.js b/test/unit/app/controllers/ens-controller-test.js
index 298eb6ad6cdf..1eb52a17cc5b 100644
--- a/test/unit/app/controllers/ens-controller-test.js
+++ b/test/unit/app/controllers/ens-controller-test.js
@@ -22,8 +22,12 @@ describe('EnsController', function () {
})
it('should construct the controller given an existing ENS instance', async () => {
+ const networkStore = {
+ subscribe: sinon.spy(),
+ }
const ens = new EnsController({
ens: {},
+ networkStore,
})
assert.ok(ens._ens)
@@ -33,11 +37,15 @@ describe('EnsController', function () {
describe('#reverseResolveName', function () {
it('should resolve to an ENS name', async () => {
const address = '0x8e5d75d60224ea0c33d0041e75de68b1c3cb6dd5'
+ const networkStore = {
+ subscribe: sinon.spy(),
+ }
const ens = new EnsController({
ens: {
reverse: sinon.stub().withArgs(address).returns('peaksignal.eth'),
lookup: sinon.stub().withArgs('peaksignal.eth').returns(address),
},
+ networkStore,
})
const name = await ens.reverseResolveAddress(address)
@@ -48,11 +56,15 @@ describe('EnsController', function () {
const address = '0x8e5d75d60224ea0c33d0041e75de68b1c3cb6dd5'
const reverse = sinon.stub().withArgs(address).returns('peaksignal.eth')
const lookup = sinon.stub().withArgs('peaksignal.eth').returns(address)
+ const networkStore = {
+ subscribe: sinon.spy(),
+ }
const ens = new EnsController({
ens: {
reverse,
lookup,
},
+ networkStore,
})
assert.equal(await ens.reverseResolveAddress(address), 'peaksignal.eth')
@@ -63,11 +75,15 @@ describe('EnsController', function () {
it('should fail if the name is registered to a different address than the reverse-resolved', async () => {
const address = '0x8e5d75d60224ea0c33d0041e75de68b1c3cb6dd5'
+ const networkStore = {
+ subscribe: sinon.spy(),
+ }
const ens = new EnsController({
ens: {
reverse: sinon.stub().withArgs(address).returns('peaksignal.eth'),
lookup: sinon.stub().withArgs('peaksignal.eth').returns('0xfoo'),
},
+ networkStore,
})
const name = await ens.reverseResolveAddress(address)
@@ -76,11 +92,15 @@ describe('EnsController', function () {
it('should throw an error when the lookup resolves to the zero address', async () => {
const address = '0x8e5d75d60224ea0c33d0041e75de68b1c3cb6dd5'
+ const networkStore = {
+ subscribe: sinon.spy(),
+ }
const ens = new EnsController({
ens: {
reverse: sinon.stub().withArgs(address).returns('peaksignal.eth'),
lookup: sinon.stub().withArgs('peaksignal.eth').returns(ZERO_ADDRESS),
},
+ networkStore,
})
try {
@@ -93,11 +113,15 @@ describe('EnsController', function () {
it('should throw an error the lookup resolves to the zero x address', async () => {
const address = '0x8e5d75d60224ea0c33d0041e75de68b1c3cb6dd5'
+ const networkStore = {
+ subscribe: sinon.spy(),
+ }
const ens = new EnsController({
ens: {
reverse: sinon.stub().withArgs(address).returns('peaksignal.eth'),
lookup: sinon.stub().withArgs('peaksignal.eth').returns(ZERO_X_ERROR_ADDRESS),
},
+ networkStore,
})
try {
From 05b2ffff538ce3c9c8f23186b4c5af88a4bd3077 Mon Sep 17 00:00:00 2001
From: Whymarrh Whitby
Date: Thu, 31 Oct 2019 18:12:57 -0230
Subject: [PATCH 8/9] fixup! ENS Reverse Resolution support
---
app/scripts/controllers/ens/ens.js | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/app/scripts/controllers/ens/ens.js b/app/scripts/controllers/ens/ens.js
index 53d6e89e8608..eb2586a7d3ce 100644
--- a/app/scripts/controllers/ens/ens.js
+++ b/app/scripts/controllers/ens/ens.js
@@ -2,6 +2,10 @@ const EthJsEns = require('ethjs-ens')
const ensNetworkMap = require('ethjs-ens/lib/network-map.json')
class Ens {
+ static getNetworkEnsSupport (network) {
+ return Boolean(ensNetworkMap[network])
+ }
+
constructor ({ network, provider } = {}) {
this._ethJsEns = new EthJsEns({
network,
@@ -18,8 +22,4 @@ class Ens {
}
}
-Ens.getNetworkEnsSupport = function getNetworkEnsSupport (network) {
- return Boolean(ensNetworkMap[network])
-}
-
module.exports = Ens
From 37ba4d87a72655a52f0033ad5954053dd18cfbaa Mon Sep 17 00:00:00 2001
From: Whymarrh Whitby
Date: Thu, 31 Oct 2019 18:13:13 -0230
Subject: [PATCH 9/9] Cleanup EnsController return values
---
app/scripts/controllers/ens/index.js | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/app/scripts/controllers/ens/index.js b/app/scripts/controllers/ens/index.js
index 0595afdc3c01..6456f8b533bd 100644
--- a/app/scripts/controllers/ens/index.js
+++ b/app/scripts/controllers/ens/index.js
@@ -49,15 +49,16 @@ class EnsController {
const domain = await this._ens.reverse(address)
const registeredAddress = await this._ens.lookup(domain)
- if (registeredAddress === ZERO_ADDRESS) throw new Error('No address for name')
- if (registeredAddress === ZERO_X_ERROR_ADDRESS) throw new Error('ENS Registry error')
+ if (registeredAddress === ZERO_ADDRESS || registeredAddress === ZERO_X_ERROR_ADDRESS) {
+ return undefined
+ }
- if (ethUtil.toChecksumAddress(registeredAddress) === address) {
- this._updateResolutionsByAddress(address, punycode.toASCII(domain))
- return domain
- } else {
+ if (ethUtil.toChecksumAddress(registeredAddress) !== address) {
return undefined
}
+
+ this._updateResolutionsByAddress(address, punycode.toASCII(domain))
+ return domain
}
_updateResolutionsByAddress (address, domain) {