mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
ENS Reverse Resolution support (#7177)
* ENS Reverse Resolution support * Save punycode for ENS domains with Unicode characters * Update SenderToRecipient recipientEns tooltip * Use cached results when reverse-resolving ENS names * Display ENS names in tx activity log
This commit is contained in:
parent
f9cd775eae
commit
eed4a9ed65
25
app/scripts/controllers/ens/ens.js
Normal file
25
app/scripts/controllers/ens/ens.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
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,
|
||||||
|
provider,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
lookup (ensName) {
|
||||||
|
return this._ethJsEns.lookup(ensName)
|
||||||
|
}
|
||||||
|
|
||||||
|
reverse (address) {
|
||||||
|
return this._ethJsEns.reverse(address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Ens
|
75
app/scripts/controllers/ens/index.js
Normal file
75
app/scripts/controllers/ens/index.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
const ethUtil = require('ethereumjs-util')
|
||||||
|
const ObservableStore = require('obs-store')
|
||||||
|
const punycode = require('punycode')
|
||||||
|
const Ens = require('./ens')
|
||||||
|
|
||||||
|
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
|
||||||
|
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()
|
||||||
|
if (Ens.getNetworkEnsSupport(network)) {
|
||||||
|
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) {
|
||||||
|
return this._reverseResolveAddress(ethUtil.toChecksumAddress(address))
|
||||||
|
}
|
||||||
|
|
||||||
|
async _reverseResolveAddress (address) {
|
||||||
|
if (!this._ens) {
|
||||||
|
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 || registeredAddress === ZERO_X_ERROR_ADDRESS) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ethUtil.toChecksumAddress(registeredAddress) !== address) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
this._updateResolutionsByAddress(address, punycode.toASCII(domain))
|
||||||
|
return domain
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateResolutionsByAddress (address, domain) {
|
||||||
|
const oldState = this.store.getState()
|
||||||
|
this.store.putState({
|
||||||
|
ensResolutionsByAddress: {
|
||||||
|
...oldState.ensResolutionsByAddress,
|
||||||
|
[address]: domain,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = EnsController
|
@ -23,6 +23,7 @@ const createLoggerMiddleware = require('./lib/createLoggerMiddleware')
|
|||||||
const providerAsMiddleware = require('eth-json-rpc-middleware/providerAsMiddleware')
|
const providerAsMiddleware = require('eth-json-rpc-middleware/providerAsMiddleware')
|
||||||
const {setupMultiplex} = require('./lib/stream-utils.js')
|
const {setupMultiplex} = require('./lib/stream-utils.js')
|
||||||
const KeyringController = require('eth-keyring-controller')
|
const KeyringController = require('eth-keyring-controller')
|
||||||
|
const EnsController = require('./controllers/ens')
|
||||||
const NetworkController = require('./controllers/network')
|
const NetworkController = require('./controllers/network')
|
||||||
const PreferencesController = require('./controllers/preferences')
|
const PreferencesController = require('./controllers/preferences')
|
||||||
const AppStateController = require('./controllers/app-state')
|
const AppStateController = require('./controllers/app-state')
|
||||||
@ -138,6 +139,11 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
networkController: this.networkController,
|
networkController: this.networkController,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.ensController = new EnsController({
|
||||||
|
provider: this.provider,
|
||||||
|
networkStore: this.networkController.networkStore,
|
||||||
|
})
|
||||||
|
|
||||||
this.incomingTransactionsController = new IncomingTransactionsController({
|
this.incomingTransactionsController = new IncomingTransactionsController({
|
||||||
blockTracker: this.blockTracker,
|
blockTracker: this.blockTracker,
|
||||||
networkController: this.networkController,
|
networkController: this.networkController,
|
||||||
@ -315,6 +321,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
// ThreeBoxController
|
// ThreeBoxController
|
||||||
ThreeBoxController: this.threeBoxController.store,
|
ThreeBoxController: this.threeBoxController.store,
|
||||||
ABTestController: this.abTestController.store,
|
ABTestController: this.abTestController.store,
|
||||||
|
// ENS Controller
|
||||||
|
EnsController: this.ensController.store,
|
||||||
})
|
})
|
||||||
this.memStore.subscribe(this.sendUpdate.bind(this))
|
this.memStore.subscribe(this.sendUpdate.bind(this))
|
||||||
}
|
}
|
||||||
@ -501,6 +509,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
// AppStateController
|
// AppStateController
|
||||||
setLastActiveTime: nodeify(this.appStateController.setLastActiveTime, this.appStateController),
|
setLastActiveTime: nodeify(this.appStateController.setLastActiveTime, this.appStateController),
|
||||||
|
|
||||||
|
// EnsController
|
||||||
|
tryReverseResolveAddress: nodeify(this.ensController.reverseResolveAddress, this.ensController),
|
||||||
|
|
||||||
// KeyringController
|
// KeyringController
|
||||||
setLocked: nodeify(this.setLocked, this),
|
setLocked: nodeify(this.setLocked, this),
|
||||||
createNewVaultAndKeychain: nodeify(this.createNewVaultAndKeychain, this),
|
createNewVaultAndKeychain: nodeify(this.createNewVaultAndKeychain, this),
|
||||||
|
@ -141,6 +141,7 @@
|
|||||||
"prop-types": "^15.6.1",
|
"prop-types": "^15.6.1",
|
||||||
"pubnub": "4.24.4",
|
"pubnub": "4.24.4",
|
||||||
"pump": "^3.0.0",
|
"pump": "^3.0.0",
|
||||||
|
"punycode": "^2.1.1",
|
||||||
"qrcode-generator": "1.4.1",
|
"qrcode-generator": "1.4.1",
|
||||||
"ramda": "^0.24.1",
|
"ramda": "^0.24.1",
|
||||||
"react": "^15.6.2",
|
"react": "^15.6.2",
|
||||||
|
135
test/unit/app/controllers/ens-controller-test.js
Normal file
135
test/unit/app/controllers/ens-controller-test.js
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
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 networkStore = {
|
||||||
|
subscribe: sinon.spy(),
|
||||||
|
}
|
||||||
|
const ens = new EnsController({
|
||||||
|
ens: {},
|
||||||
|
networkStore,
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.ok(ens._ens)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
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)
|
||||||
|
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 networkStore = {
|
||||||
|
subscribe: sinon.spy(),
|
||||||
|
}
|
||||||
|
const ens = new EnsController({
|
||||||
|
ens: {
|
||||||
|
reverse,
|
||||||
|
lookup,
|
||||||
|
},
|
||||||
|
networkStore,
|
||||||
|
})
|
||||||
|
|
||||||
|
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 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)
|
||||||
|
assert.strictEqual(name, undefined)
|
||||||
|
})
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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 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 {
|
||||||
|
await ens.reverseResolveAddress(address)
|
||||||
|
assert.fail('#reverseResolveAddress did not throw')
|
||||||
|
} catch (e) {
|
||||||
|
assert.ok(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -24,6 +24,7 @@ export default class ConfirmPageContainer extends Component {
|
|||||||
fromName: PropTypes.string,
|
fromName: PropTypes.string,
|
||||||
toAddress: PropTypes.string,
|
toAddress: PropTypes.string,
|
||||||
toName: PropTypes.string,
|
toName: PropTypes.string,
|
||||||
|
toEns: PropTypes.string,
|
||||||
toNickname: PropTypes.string,
|
toNickname: PropTypes.string,
|
||||||
// Content
|
// Content
|
||||||
contentComponent: PropTypes.node,
|
contentComponent: PropTypes.node,
|
||||||
@ -69,6 +70,7 @@ export default class ConfirmPageContainer extends Component {
|
|||||||
fromName,
|
fromName,
|
||||||
fromAddress,
|
fromAddress,
|
||||||
toName,
|
toName,
|
||||||
|
toEns,
|
||||||
toNickname,
|
toNickname,
|
||||||
toAddress,
|
toAddress,
|
||||||
disabled,
|
disabled,
|
||||||
@ -128,6 +130,7 @@ export default class ConfirmPageContainer extends Component {
|
|||||||
senderAddress={fromAddress}
|
senderAddress={fromAddress}
|
||||||
recipientName={toName}
|
recipientName={toName}
|
||||||
recipientAddress={toAddress}
|
recipientAddress={toAddress}
|
||||||
|
recipientEns={toEns}
|
||||||
recipientNickname={toNickname}
|
recipientNickname={toNickname}
|
||||||
assetImage={renderAssetImage ? assetImage : undefined}
|
assetImage={renderAssetImage ? assetImage : undefined}
|
||||||
/>
|
/>
|
||||||
|
@ -1 +1 @@
|
|||||||
export { default } from './transaction-list-item-details.component'
|
export { default } from './transaction-list-item-details.container'
|
||||||
|
@ -17,6 +17,10 @@ export default class TransactionListItemDetails extends PureComponent {
|
|||||||
metricsEvent: PropTypes.func,
|
metricsEvent: PropTypes.func,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
recipientEns: null,
|
||||||
|
}
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onCancel: PropTypes.func,
|
onCancel: PropTypes.func,
|
||||||
onRetry: PropTypes.func,
|
onRetry: PropTypes.func,
|
||||||
@ -26,7 +30,11 @@ export default class TransactionListItemDetails extends PureComponent {
|
|||||||
isEarliestNonce: PropTypes.bool,
|
isEarliestNonce: PropTypes.bool,
|
||||||
cancelDisabled: PropTypes.bool,
|
cancelDisabled: PropTypes.bool,
|
||||||
transactionGroup: PropTypes.object,
|
transactionGroup: PropTypes.object,
|
||||||
|
recipientEns: PropTypes.string,
|
||||||
|
recipientAddress: PropTypes.string.isRequired,
|
||||||
rpcPrefs: PropTypes.object,
|
rpcPrefs: PropTypes.object,
|
||||||
|
senderAddress: PropTypes.string.isRequired,
|
||||||
|
tryReverseResolveAddress: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -82,6 +90,12 @@ export default class TransactionListItemDetails extends PureComponent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async componentDidMount () {
|
||||||
|
const { recipientAddress, tryReverseResolveAddress } = this.props
|
||||||
|
|
||||||
|
tryReverseResolveAddress(recipientAddress)
|
||||||
|
}
|
||||||
|
|
||||||
renderCancel () {
|
renderCancel () {
|
||||||
const { t } = this.context
|
const { t } = this.context
|
||||||
const {
|
const {
|
||||||
@ -128,11 +142,14 @@ export default class TransactionListItemDetails extends PureComponent {
|
|||||||
showRetry,
|
showRetry,
|
||||||
onCancel,
|
onCancel,
|
||||||
onRetry,
|
onRetry,
|
||||||
|
recipientEns,
|
||||||
|
recipientAddress,
|
||||||
rpcPrefs: { blockExplorerUrl } = {},
|
rpcPrefs: { blockExplorerUrl } = {},
|
||||||
|
senderAddress,
|
||||||
isEarliestNonce,
|
isEarliestNonce,
|
||||||
} = this.props
|
} = this.props
|
||||||
const { primaryTransaction: transaction } = transactionGroup
|
const { primaryTransaction: transaction } = transactionGroup
|
||||||
const { hash, txParams: { to, from } = {} } = transaction
|
const { hash } = transaction
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="transaction-list-item-details">
|
<div className="transaction-list-item-details">
|
||||||
@ -192,8 +209,9 @@ export default class TransactionListItemDetails extends PureComponent {
|
|||||||
<SenderToRecipient
|
<SenderToRecipient
|
||||||
variant={FLAT_VARIANT}
|
variant={FLAT_VARIANT}
|
||||||
addressOnly
|
addressOnly
|
||||||
recipientAddress={to}
|
recipientEns={recipientEns}
|
||||||
senderAddress={from}
|
recipientAddress={recipientAddress}
|
||||||
|
senderAddress={senderAddress}
|
||||||
onRecipientClick={() => {
|
onRecipientClick={() => {
|
||||||
this.context.metricsEvent({
|
this.context.metricsEvent({
|
||||||
eventOpts: {
|
eventOpts: {
|
||||||
|
@ -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)
|
@ -191,6 +191,7 @@ export default class TransactionListItem extends PureComponent {
|
|||||||
} = this.props
|
} = this.props
|
||||||
const { txParams = {} } = transaction
|
const { txParams = {} } = transaction
|
||||||
const { showTransactionDetails } = this.state
|
const { showTransactionDetails } = this.state
|
||||||
|
const fromAddress = txParams.from
|
||||||
const toAddress = tokenData
|
const toAddress = tokenData
|
||||||
? tokenData.params && tokenData.params[0] && tokenData.params[0].value || txParams.to
|
? tokenData.params && tokenData.params[0] && tokenData.params[0].value || txParams.to
|
||||||
: txParams.to
|
: txParams.to
|
||||||
@ -253,6 +254,8 @@ export default class TransactionListItem extends PureComponent {
|
|||||||
showCancel={showCancel}
|
showCancel={showCancel}
|
||||||
cancelDisabled={!hasEnoughCancelGas}
|
cancelDisabled={!hasEnoughCancelGas}
|
||||||
rpcPrefs={rpcPrefs}
|
rpcPrefs={rpcPrefs}
|
||||||
|
senderAddress={fromAddress}
|
||||||
|
recipientAddress={toAddress}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -5,7 +5,7 @@ import Identicon from '../identicon'
|
|||||||
import Tooltip from '../tooltip-v2'
|
import Tooltip from '../tooltip-v2'
|
||||||
import copyToClipboard from 'copy-to-clipboard'
|
import copyToClipboard from 'copy-to-clipboard'
|
||||||
import { DEFAULT_VARIANT, CARDS_VARIANT, FLAT_VARIANT } from './sender-to-recipient.constants'
|
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 = {
|
const variantHash = {
|
||||||
[DEFAULT_VARIANT]: 'sender-to-recipient--default',
|
[DEFAULT_VARIANT]: 'sender-to-recipient--default',
|
||||||
@ -18,6 +18,7 @@ export default class SenderToRecipient extends PureComponent {
|
|||||||
senderName: PropTypes.string,
|
senderName: PropTypes.string,
|
||||||
senderAddress: PropTypes.string,
|
senderAddress: PropTypes.string,
|
||||||
recipientName: PropTypes.string,
|
recipientName: PropTypes.string,
|
||||||
|
recipientEns: PropTypes.string,
|
||||||
recipientAddress: PropTypes.string,
|
recipientAddress: PropTypes.string,
|
||||||
recipientNickname: PropTypes.string,
|
recipientNickname: PropTypes.string,
|
||||||
t: PropTypes.func,
|
t: PropTypes.func,
|
||||||
@ -60,14 +61,28 @@ export default class SenderToRecipient extends PureComponent {
|
|||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
position="bottom"
|
position="bottom"
|
||||||
title={this.state.senderAddressCopied ? t('copiedExclamation') : t('copyAddress')}
|
html={
|
||||||
|
this.state.senderAddressCopied
|
||||||
|
? <p>{t('copiedExclamation')}</p>
|
||||||
|
: addressOnly
|
||||||
|
? <p>{t('copyAddress')}</p>
|
||||||
|
: (
|
||||||
|
<p>
|
||||||
|
{addressSlicer(checksummedSenderAddress)}<br/>
|
||||||
|
{t('copyAddress')}
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
wrapperClassName="sender-to-recipient__tooltip-wrapper"
|
wrapperClassName="sender-to-recipient__tooltip-wrapper"
|
||||||
containerClassName="sender-to-recipient__tooltip-container"
|
containerClassName="sender-to-recipient__tooltip-container"
|
||||||
onHidden={() => this.setState({ senderAddressCopied: false })}
|
onHidden={() => this.setState({ senderAddressCopied: false })}
|
||||||
>
|
>
|
||||||
<div className="sender-to-recipient__name">
|
<div className="sender-to-recipient__name">
|
||||||
<span>{ addressOnly ? `${t('from')}: ` : '' }</span>
|
{
|
||||||
{ addressOnly ? checksummedSenderAddress : senderName }
|
addressOnly
|
||||||
|
? <span>{`${t('from')}: ${checksummedSenderAddress}`}</span>
|
||||||
|
: senderName
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
@ -90,7 +105,7 @@ export default class SenderToRecipient extends PureComponent {
|
|||||||
|
|
||||||
renderRecipientWithAddress () {
|
renderRecipientWithAddress () {
|
||||||
const { t } = this.context
|
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)
|
const checksummedRecipientAddress = checksumAddress(recipientAddress)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -107,7 +122,18 @@ export default class SenderToRecipient extends PureComponent {
|
|||||||
{ this.renderRecipientIdenticon() }
|
{ this.renderRecipientIdenticon() }
|
||||||
<Tooltip
|
<Tooltip
|
||||||
position="bottom"
|
position="bottom"
|
||||||
title={this.state.recipientAddressCopied ? t('copiedExclamation') : t('copyAddress')}
|
html={
|
||||||
|
this.state.senderAddressCopied
|
||||||
|
? <p>{t('copiedExclamation')}</p>
|
||||||
|
: (addressOnly && !recipientNickname && !recipientEns)
|
||||||
|
? <p>{t('copyAddress')}</p>
|
||||||
|
: (
|
||||||
|
<p>
|
||||||
|
{addressSlicer(checksummedRecipientAddress)}<br/>
|
||||||
|
{t('copyAddress')}
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
wrapperClassName="sender-to-recipient__tooltip-wrapper"
|
wrapperClassName="sender-to-recipient__tooltip-wrapper"
|
||||||
containerClassName="sender-to-recipient__tooltip-container"
|
containerClassName="sender-to-recipient__tooltip-container"
|
||||||
onHidden={() => this.setState({ recipientAddressCopied: false })}
|
onHidden={() => this.setState({ recipientAddressCopied: false })}
|
||||||
@ -116,8 +142,8 @@ export default class SenderToRecipient extends PureComponent {
|
|||||||
<span>{ addressOnly ? `${t('to')}: ` : '' }</span>
|
<span>{ addressOnly ? `${t('to')}: ` : '' }</span>
|
||||||
{
|
{
|
||||||
addressOnly
|
addressOnly
|
||||||
? checksummedRecipientAddress
|
? (recipientNickname || recipientEns || checksummedRecipientAddress)
|
||||||
: (recipientNickname || recipientName || this.context.t('newContract'))
|
: (recipientNickname || recipientEns || recipientName || this.context.t('newContract'))
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
@ -8,6 +8,7 @@ export default class Tooltip extends PureComponent {
|
|||||||
children: null,
|
children: null,
|
||||||
containerClassName: '',
|
containerClassName: '',
|
||||||
hideOnClick: false,
|
hideOnClick: false,
|
||||||
|
html: null,
|
||||||
onHidden: null,
|
onHidden: null,
|
||||||
position: 'left',
|
position: 'left',
|
||||||
size: 'small',
|
size: 'small',
|
||||||
@ -21,6 +22,7 @@ export default class Tooltip extends PureComponent {
|
|||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
containerClassName: PropTypes.string,
|
containerClassName: PropTypes.string,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
|
html: PropTypes.node,
|
||||||
onHidden: PropTypes.func,
|
onHidden: PropTypes.func,
|
||||||
position: PropTypes.oneOf([
|
position: PropTypes.oneOf([
|
||||||
'top',
|
'top',
|
||||||
@ -38,9 +40,9 @@ export default class Tooltip extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
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 (
|
return (
|
||||||
<div className={wrapperClassName}>
|
<div className={wrapperClassName}>
|
||||||
{children}
|
{children}
|
||||||
@ -51,6 +53,7 @@ export default class Tooltip extends PureComponent {
|
|||||||
return (
|
return (
|
||||||
<div className={wrapperClassName}>
|
<div className={wrapperClassName}>
|
||||||
<ReactTippy
|
<ReactTippy
|
||||||
|
html={html}
|
||||||
className={containerClassName}
|
className={containerClassName}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
title={title}
|
title={title}
|
||||||
|
@ -64,6 +64,7 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
tokenData: PropTypes.object,
|
tokenData: PropTypes.object,
|
||||||
tokenProps: PropTypes.object,
|
tokenProps: PropTypes.object,
|
||||||
toName: PropTypes.string,
|
toName: PropTypes.string,
|
||||||
|
toEns: PropTypes.string,
|
||||||
toNickname: PropTypes.string,
|
toNickname: PropTypes.string,
|
||||||
transactionStatus: PropTypes.string,
|
transactionStatus: PropTypes.string,
|
||||||
txData: PropTypes.object,
|
txData: PropTypes.object,
|
||||||
@ -103,6 +104,7 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
transactionCategory: PropTypes.string,
|
transactionCategory: PropTypes.string,
|
||||||
getNextNonce: PropTypes.func,
|
getNextNonce: PropTypes.func,
|
||||||
nextNonce: PropTypes.number,
|
nextNonce: PropTypes.number,
|
||||||
|
tryReverseResolveAddress: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -567,7 +569,7 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
const { txData: { origin, id } = {}, cancelTransaction, getNextNonce } = this.props
|
const { toAddress, txData: { origin, id } = {}, cancelTransaction, getNextNonce, tryReverseResolveAddress } = this.props
|
||||||
const { metricsEvent } = this.context
|
const { metricsEvent } = this.context
|
||||||
metricsEvent({
|
metricsEvent({
|
||||||
eventOpts: {
|
eventOpts: {
|
||||||
@ -598,6 +600,7 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getNextNonce()
|
getNextNonce()
|
||||||
|
tryReverseResolveAddress(toAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
@ -613,6 +616,7 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
fromAddress,
|
fromAddress,
|
||||||
toName,
|
toName,
|
||||||
toAddress,
|
toAddress,
|
||||||
|
toEns,
|
||||||
toNickname,
|
toNickname,
|
||||||
methodData,
|
methodData,
|
||||||
valid: propsValid = true,
|
valid: propsValid = true,
|
||||||
@ -643,6 +647,7 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
fromAddress={fromAddress}
|
fromAddress={fromAddress}
|
||||||
toName={toName}
|
toName={toName}
|
||||||
toAddress={toAddress}
|
toAddress={toAddress}
|
||||||
|
toEns={toEns}
|
||||||
toNickname={toNickname}
|
toNickname={toNickname}
|
||||||
showEdit={onEdit && !isTxReprice}
|
showEdit={onEdit && !isTxReprice}
|
||||||
// In the event that the key is falsy (and inherently invalid), use a fallback string
|
// In the event that the key is falsy (and inherently invalid), use a fallback string
|
||||||
|
@ -18,6 +18,7 @@ import {
|
|||||||
setMetaMetricsSendCount,
|
setMetaMetricsSendCount,
|
||||||
updateTransaction,
|
updateTransaction,
|
||||||
getNextNonce,
|
getNextNonce,
|
||||||
|
tryReverseResolveAddress,
|
||||||
} from '../../store/actions'
|
} from '../../store/actions'
|
||||||
import {
|
import {
|
||||||
INSUFFICIENT_FUNDS_ERROR_KEY,
|
INSUFFICIENT_FUNDS_ERROR_KEY,
|
||||||
@ -51,6 +52,7 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
const isMainnet = getIsMainnet(state)
|
const isMainnet = getIsMainnet(state)
|
||||||
const { confirmTransaction, metamask } = state
|
const { confirmTransaction, metamask } = state
|
||||||
const {
|
const {
|
||||||
|
ensResolutionsByAddress,
|
||||||
conversionRate,
|
conversionRate,
|
||||||
identities,
|
identities,
|
||||||
addressBook,
|
addressBook,
|
||||||
@ -93,7 +95,9 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
: addressSlicer(checksumAddress(toAddress))
|
: addressSlicer(checksumAddress(toAddress))
|
||||||
)
|
)
|
||||||
|
|
||||||
const addressBookObject = addressBook[checksumAddress(toAddress)]
|
const checksummedAddress = checksumAddress(toAddress)
|
||||||
|
const addressBookObject = addressBook[checksummedAddress]
|
||||||
|
const toEns = ensResolutionsByAddress[checksummedAddress] || ''
|
||||||
const toNickname = addressBookObject ? addressBookObject.name : ''
|
const toNickname = addressBookObject ? addressBookObject.name : ''
|
||||||
const isTxReprice = Boolean(lastGasPrice)
|
const isTxReprice = Boolean(lastGasPrice)
|
||||||
const transactionStatus = transaction ? transaction.status : ''
|
const transactionStatus = transaction ? transaction.status : ''
|
||||||
@ -134,6 +138,7 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
fromAddress,
|
fromAddress,
|
||||||
fromName,
|
fromName,
|
||||||
toAddress,
|
toAddress,
|
||||||
|
toEns,
|
||||||
toName,
|
toName,
|
||||||
toNickname,
|
toNickname,
|
||||||
ethTransactionAmount,
|
ethTransactionAmount,
|
||||||
@ -176,6 +181,9 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
|
|
||||||
export const mapDispatchToProps = dispatch => {
|
export const mapDispatchToProps = dispatch => {
|
||||||
return {
|
return {
|
||||||
|
tryReverseResolveAddress: (address) => {
|
||||||
|
return dispatch(tryReverseResolveAddress(address))
|
||||||
|
},
|
||||||
updateCustomNonce: value => {
|
updateCustomNonce: value => {
|
||||||
customNonceValue = value
|
customNonceValue = value
|
||||||
dispatch(updateCustomNonce(value))
|
dispatch(updateCustomNonce(value))
|
||||||
|
@ -392,6 +392,8 @@ var actions = {
|
|||||||
setShowRestorePromptToFalse,
|
setShowRestorePromptToFalse,
|
||||||
turnThreeBoxSyncingOn,
|
turnThreeBoxSyncingOn,
|
||||||
turnThreeBoxSyncingOnAndInitialize,
|
turnThreeBoxSyncingOnAndInitialize,
|
||||||
|
|
||||||
|
tryReverseResolveAddress,
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = actions
|
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 () {
|
function fetchInfoToSync () {
|
||||||
return dispatch => {
|
return dispatch => {
|
||||||
log.debug(`background.fetchInfoToSync`)
|
log.debug(`background.fetchInfoToSync`)
|
||||||
|
@ -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"
|
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
|
||||||
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
|
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
|
||||||
|
|
||||||
punycode@^2.1.0:
|
punycode@^2.1.0, punycode@^2.1.1:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||||
|
Loading…
x
Reference in New Issue
Block a user