feature/default_network_editable
commit
ccbc233282
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,54 @@ |
||||
const namehash = require('eth-ens-namehash') |
||||
const multihash = require('multihashes') |
||||
const Eth = require('ethjs-query') |
||||
const EthContract = require('ethjs-contract') |
||||
const registrarAbi = require('./contracts/registrar') |
||||
const resolverAbi = require('./contracts/resolver') |
||||
|
||||
module.exports = resolveEnsToIpfsContentId |
||||
|
||||
|
||||
async function resolveEnsToIpfsContentId ({ provider, name }) { |
||||
const eth = new Eth(provider) |
||||
const hash = namehash.hash(name) |
||||
const contract = new EthContract(eth) |
||||
// lookup registrar
|
||||
const chainId = Number.parseInt(await eth.net_version(), 10) |
||||
const registrarAddress = getRegistrarForChainId(chainId) |
||||
if (!registrarAddress) { |
||||
throw new Error(`EnsIpfsResolver - no known ens-ipfs registrar for chainId "${chainId}"`) |
||||
} |
||||
const Registrar = contract(registrarAbi).at(registrarAddress) |
||||
// lookup resolver
|
||||
const resolverLookupResult = await Registrar.resolver(hash) |
||||
const resolverAddress = resolverLookupResult[0] |
||||
if (hexValueIsEmpty(resolverAddress)) { |
||||
throw new Error(`EnsIpfsResolver - no resolver found for name "${name}"`) |
||||
} |
||||
const Resolver = contract(resolverAbi).at(resolverAddress) |
||||
// lookup content id
|
||||
const contentLookupResult = await Resolver.content(hash) |
||||
const contentHash = contentLookupResult[0] |
||||
if (hexValueIsEmpty(contentHash)) { |
||||
throw new Error(`EnsIpfsResolver - no content ID found for name "${name}"`) |
||||
} |
||||
const nonPrefixedHex = contentHash.slice(2) |
||||
const buffer = multihash.fromHexString(nonPrefixedHex) |
||||
const contentId = multihash.toB58String(multihash.encode(buffer, 'sha2-256')) |
||||
return contentId |
||||
} |
||||
|
||||
function hexValueIsEmpty(value) { |
||||
return [undefined, null, '0x', '0x0', '0x0000000000000000000000000000000000000000000000000000000000000000'].includes(value) |
||||
} |
||||
|
||||
function getRegistrarForChainId (chainId) { |
||||
switch (chainId) { |
||||
// mainnet
|
||||
case 1: |
||||
return '0x314159265dd8dbb310642f98f50c066173c1259b' |
||||
// ropsten
|
||||
case 3: |
||||
return '0x112234455c3a32fd11230c42e7bccd4a84e02010' |
||||
} |
||||
} |
@ -0,0 +1,63 @@ |
||||
const urlUtil = require('url') |
||||
const extension = require('extensionizer') |
||||
const resolveEnsToIpfsContentId = require('./resolver.js') |
||||
|
||||
const supportedTopLevelDomains = ['eth'] |
||||
|
||||
module.exports = setupEnsIpfsResolver |
||||
|
||||
function setupEnsIpfsResolver({ provider }) { |
||||
|
||||
// install listener
|
||||
const urlPatterns = supportedTopLevelDomains.map(tld => `*://*.${tld}/*`) |
||||
extension.webRequest.onErrorOccurred.addListener(webRequestDidFail, { urls: urlPatterns }) |
||||
|
||||
// return api object
|
||||
return { |
||||
// uninstall listener
|
||||
remove () { |
||||
extension.webRequest.onErrorOccurred.removeListener(webRequestDidFail) |
||||
}, |
||||
} |
||||
|
||||
async function webRequestDidFail (details) { |
||||
const { tabId, url } = details |
||||
// ignore requests that are not associated with tabs
|
||||
if (tabId === -1) return |
||||
// parse ens name
|
||||
const urlData = urlUtil.parse(url) |
||||
const { hostname: name, path, search } = urlData |
||||
const domainParts = name.split('.') |
||||
const topLevelDomain = domainParts[domainParts.length - 1] |
||||
// if unsupported TLD, abort
|
||||
if (!supportedTopLevelDomains.includes(topLevelDomain)) return |
||||
// otherwise attempt resolve
|
||||
attemptResolve({ tabId, name, path, search }) |
||||
} |
||||
|
||||
async function attemptResolve({ tabId, name, path, search }) { |
||||
extension.tabs.update(tabId, { url: `loading.html` }) |
||||
try { |
||||
const ipfsContentId = await resolveEnsToIpfsContentId({ provider, name }) |
||||
let url = `https://gateway.ipfs.io/ipfs/${ipfsContentId}${path}${search || ''}` |
||||
try { |
||||
// check if ipfs gateway has result
|
||||
const response = await fetch(url, { method: 'HEAD' }) |
||||
// if failure, redirect to 404 page
|
||||
if (response.status !== 200) { |
||||
extension.tabs.update(tabId, { url: '404.html' }) |
||||
return |
||||
} |
||||
// otherwise redirect to the correct page
|
||||
extension.tabs.update(tabId, { url }) |
||||
} catch (err) { |
||||
console.warn(err) |
||||
// if HEAD fetch failed, redirect so user can see relevant error page
|
||||
extension.tabs.update(tabId, { url }) |
||||
} |
||||
} catch (err) { |
||||
console.warn(err) |
||||
extension.tabs.update(tabId, { url: `error.html?name=${name}` }) |
||||
} |
||||
} |
||||
} |
@ -1,46 +0,0 @@ |
||||
const extension = require('extensionizer') |
||||
const resolver = require('./resolver.js') |
||||
|
||||
module.exports = function (provider) { |
||||
function ipfsContent (details) { |
||||
const name = details.url.substring(7, details.url.length - 1) |
||||
let clearTime = null |
||||
if (/^.+\.eth$/.test(name) === false) return |
||||
|
||||
extension.tabs.query({active: true}, tab => { |
||||
extension.tabs.update(tab.id, { url: 'loading.html' }) |
||||
|
||||
clearTime = setTimeout(() => { |
||||
return extension.tabs.update(tab.id, { url: '404.html' }) |
||||
}, 60000) |
||||
|
||||
resolver.resolve(name, provider).then(ipfsHash => { |
||||
clearTimeout(clearTime) |
||||
let url = 'https://ipfs.infura.io/ipfs/' + ipfsHash |
||||
return fetch(url, { method: 'HEAD' }).then(response => response.status).then(statusCode => { |
||||
if (statusCode !== 200) return extension.tabs.update(tab.id, { url: '404.html' }) |
||||
extension.tabs.update(tab.id, { url: url }) |
||||
}) |
||||
.catch(err => { |
||||
url = 'https://ipfs.infura.io/ipfs/' + ipfsHash |
||||
extension.tabs.update(tab.id, {url: url}) |
||||
return err |
||||
}) |
||||
}) |
||||
.catch(err => { |
||||
clearTimeout(clearTime) |
||||
const url = err === 'unsupport' ? 'unsupport' : 'error' |
||||
extension.tabs.update(tab.id, {url: `${url}.html?name=${name}`}) |
||||
}) |
||||
}) |
||||
return { cancel: true } |
||||
} |
||||
|
||||
extension.webRequest.onErrorOccurred.addListener(ipfsContent, {urls: ['*://*.eth/'], types: ['main_frame']}) |
||||
|
||||
return { |
||||
remove () { |
||||
extension.webRequest.onErrorOccurred.removeListener(ipfsContent) |
||||
}, |
||||
} |
||||
} |
@ -1,71 +0,0 @@ |
||||
const namehash = require('eth-ens-namehash') |
||||
const multihash = require('multihashes') |
||||
const HttpProvider = require('ethjs-provider-http') |
||||
const Eth = require('ethjs-query') |
||||
const EthContract = require('ethjs-contract') |
||||
const registrarAbi = require('./contracts/registrar') |
||||
const resolverAbi = require('./contracts/resolver') |
||||
|
||||
function ens (name, provider) { |
||||
const eth = new Eth(new HttpProvider(getProvider(provider.type))) |
||||
const hash = namehash.hash(name) |
||||
const contract = new EthContract(eth) |
||||
const Registrar = contract(registrarAbi).at(getRegistrar(provider.type)) |
||||
return new Promise((resolve, reject) => { |
||||
if (provider.type === 'mainnet' || provider.type === 'ropsten') { |
||||
Registrar.resolver(hash).then((address) => { |
||||
if (address === '0x0000000000000000000000000000000000000000') { |
||||
reject(null) |
||||
} else { |
||||
const Resolver = contract(resolverAbi).at(address['0']) |
||||
return Resolver.content(hash) |
||||
} |
||||
}).then((contentHash) => { |
||||
if (contentHash['0'] === '0x0000000000000000000000000000000000000000000000000000000000000000') reject(null) |
||||
if (contentHash.ret !== '0x') { |
||||
const hex = contentHash['0'].substring(2) |
||||
const buf = multihash.fromHexString(hex) |
||||
resolve(multihash.toB58String(multihash.encode(buf, 'sha2-256'))) |
||||
} else { |
||||
reject(null) |
||||
} |
||||
}) |
||||
} else { |
||||
return reject('unsupport') |
||||
} |
||||
}) |
||||
} |
||||
|
||||
function getProvider (type) { |
||||
switch (type) { |
||||
case 'mainnet': |
||||
return 'https://mainnet.infura.io/' |
||||
case 'ropsten': |
||||
return 'https://ropsten.infura.io/' |
||||
default: |
||||
return 'http://localhost:8545/' |
||||
} |
||||
} |
||||
|
||||
function getRegistrar (type) { |
||||
switch (type) { |
||||
case 'mainnet': |
||||
return '0x314159265dd8dbb310642f98f50c066173c1259b' |
||||
case 'ropsten': |
||||
return '0x112234455c3a32fd11230c42e7bccd4a84e02010' |
||||
default: |
||||
return '0x0000000000000000000000000000000000000000' |
||||
} |
||||
} |
||||
|
||||
module.exports.resolve = function (name, provider) { |
||||
const path = name.split('.') |
||||
const topLevelDomain = path[path.length - 1] |
||||
if (topLevelDomain === 'eth' || topLevelDomain === 'test') { |
||||
return ens(name, provider) |
||||
} else { |
||||
return new Promise((resolve, reject) => { |
||||
reject(null) |
||||
}) |
||||
} |
||||
} |
@ -1,124 +0,0 @@ |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const inherits = require('util').inherits |
||||
const connect = require('react-redux').connect |
||||
const isNode = require('detect-node') |
||||
const findDOMNode = require('react-dom').findDOMNode |
||||
const jazzicon = require('jazzicon') |
||||
const iconFactoryGen = require('../../lib/icon-factory') |
||||
const iconFactory = iconFactoryGen(jazzicon) |
||||
const { toDataUrl } = require('../../lib/blockies') |
||||
|
||||
module.exports = connect(mapStateToProps)(IdenticonComponent) |
||||
|
||||
inherits(IdenticonComponent, Component) |
||||
function IdenticonComponent () { |
||||
Component.call(this) |
||||
|
||||
this.defaultDiameter = 46 |
||||
} |
||||
|
||||
function mapStateToProps (state) { |
||||
return { |
||||
useBlockie: state.metamask.useBlockie, |
||||
} |
||||
} |
||||
|
||||
IdenticonComponent.prototype.render = function () { |
||||
var props = this.props |
||||
const { className = '', address, image } = props |
||||
var diameter = props.diameter || this.defaultDiameter |
||||
const style = { |
||||
height: diameter, |
||||
width: diameter, |
||||
borderRadius: diameter / 2, |
||||
} |
||||
if (image) { |
||||
return h('img', { |
||||
className: `${className} identicon`, |
||||
src: image, |
||||
style: { |
||||
...style, |
||||
}, |
||||
}) |
||||
} else if (address) { |
||||
return h('div', { |
||||
className: `${className} identicon`, |
||||
key: 'identicon-' + address, |
||||
style: { |
||||
display: 'flex', |
||||
flexShrink: 0, |
||||
alignItems: 'center', |
||||
justifyContent: 'center', |
||||
...style, |
||||
overflow: 'hidden', |
||||
}, |
||||
}) |
||||
} else { |
||||
return h('img.balance-icon', { |
||||
className, |
||||
src: './images/eth_logo.svg', |
||||
style: { |
||||
...style, |
||||
}, |
||||
}) |
||||
} |
||||
} |
||||
|
||||
IdenticonComponent.prototype.componentDidMount = function () { |
||||
var props = this.props |
||||
const { address, useBlockie } = props |
||||
|
||||
if (!address) return |
||||
|
||||
if (!isNode) { |
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
var container = findDOMNode(this) |
||||
|
||||
const diameter = props.diameter || this.defaultDiameter |
||||
|
||||
if (useBlockie) { |
||||
_generateBlockie(container, address, diameter) |
||||
} else { |
||||
_generateJazzicon(container, address, diameter) |
||||
} |
||||
} |
||||
} |
||||
|
||||
IdenticonComponent.prototype.componentDidUpdate = function () { |
||||
var props = this.props |
||||
const { address, useBlockie } = props |
||||
|
||||
if (!address) return |
||||
|
||||
if (!isNode) { |
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
var container = findDOMNode(this) |
||||
|
||||
var children = container.children |
||||
for (var i = 0; i < children.length; i++) { |
||||
container.removeChild(children[i]) |
||||
} |
||||
|
||||
const diameter = props.diameter || this.defaultDiameter |
||||
|
||||
if (useBlockie) { |
||||
_generateBlockie(container, address, diameter) |
||||
} else { |
||||
_generateJazzicon(container, address, diameter) |
||||
} |
||||
} |
||||
} |
||||
|
||||
function _generateBlockie (container, address, diameter) { |
||||
const img = new Image() |
||||
img.src = toDataUrl(address) |
||||
img.height = diameter |
||||
img.width = diameter |
||||
container.appendChild(img) |
||||
} |
||||
|
||||
function _generateJazzicon (container, address, diameter) { |
||||
const img = iconFactory.iconForAddress(address, diameter) |
||||
container.appendChild(img) |
||||
} |
@ -0,0 +1,99 @@ |
||||
import React, { PureComponent } from 'react' |
||||
import PropTypes from 'prop-types' |
||||
import classnames from 'classnames' |
||||
import { toDataUrl } from '../../../lib/blockies' |
||||
import contractMap from 'eth-contract-metadata' |
||||
import { checksumAddress } from '../../../app/util' |
||||
import Jazzicon from '../jazzicon' |
||||
|
||||
const getStyles = diameter => ( |
||||
{ |
||||
height: diameter, |
||||
width: diameter, |
||||
borderRadius: diameter / 2, |
||||
} |
||||
) |
||||
|
||||
export default class Identicon extends PureComponent { |
||||
static propTypes = { |
||||
address: PropTypes.string, |
||||
className: PropTypes.string, |
||||
diameter: PropTypes.number, |
||||
image: PropTypes.string, |
||||
useBlockie: PropTypes.bool, |
||||
} |
||||
|
||||
static defaultProps = { |
||||
diameter: 46, |
||||
} |
||||
|
||||
renderImage () { |
||||
const { className, diameter, image } = this.props |
||||
|
||||
return ( |
||||
<img |
||||
className={classnames('identicon', className)} |
||||
src={image} |
||||
style={getStyles(diameter)} |
||||
/> |
||||
) |
||||
} |
||||
|
||||
renderJazzicon () { |
||||
const { address, className, diameter } = this.props |
||||
|
||||
return ( |
||||
<Jazzicon |
||||
address={address} |
||||
diameter={diameter} |
||||
className={classnames('identicon', className)} |
||||
style={getStyles(diameter)} |
||||
/> |
||||
) |
||||
} |
||||
|
||||
renderBlockie () { |
||||
const { address, className, diameter } = this.props |
||||
|
||||
return ( |
||||
<div |
||||
className={classnames('identicon', className)} |
||||
style={getStyles(diameter)} |
||||
> |
||||
<img |
||||
src={toDataUrl(address)} |
||||
height={diameter} |
||||
width={diameter} |
||||
/> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
render () { |
||||
const { className, address, image, diameter, useBlockie } = this.props |
||||
|
||||
if (image) { |
||||
return this.renderImage() |
||||
} |
||||
|
||||
if (address) { |
||||
const checksummedAddress = checksumAddress(address) |
||||
|
||||
if (contractMap[checksummedAddress] && contractMap[checksummedAddress].logo) { |
||||
return this.renderJazzicon() |
||||
} |
||||
|
||||
return useBlockie |
||||
? this.renderBlockie() |
||||
: this.renderJazzicon() |
||||
} |
||||
|
||||
return ( |
||||
<img |
||||
className={classnames('balance-icon', className)} |
||||
src="./images/eth_logo.svg" |
||||
style={getStyles(diameter)} |
||||
/> |
||||
) |
||||
} |
||||
} |
@ -0,0 +1,12 @@ |
||||
import { connect } from 'react-redux' |
||||
import Identicon from './identicon.component' |
||||
|
||||
const mapStateToProps = state => { |
||||
const { metamask: { useBlockie } } = state |
||||
|
||||
return { |
||||
useBlockie, |
||||
} |
||||
} |
||||
|
||||
export default connect(mapStateToProps)(Identicon) |
@ -0,0 +1 @@ |
||||
export { default } from './identicon.container' |
@ -0,0 +1,7 @@ |
||||
.identicon { |
||||
display: flex; |
||||
flex-shrink: 0; |
||||
align-items: center; |
||||
justify-content: center; |
||||
overflow: hidden; |
||||
} |
@ -0,0 +1 @@ |
||||
export { default } from './jazzicon.component' |
@ -0,0 +1,69 @@ |
||||
import React, { PureComponent } from 'react' |
||||
import PropTypes from 'prop-types' |
||||
import isNode from 'detect-node' |
||||
import { findDOMNode } from 'react-dom' |
||||
import jazzicon from 'jazzicon' |
||||
import iconFactoryGenerator from '../../../lib/icon-factory' |
||||
const iconFactory = iconFactoryGenerator(jazzicon) |
||||
|
||||
/** |
||||
* Wrapper around the jazzicon library to return a React component, as the library returns an |
||||
* HTMLDivElement which needs to be appended. |
||||
*/ |
||||
export default class Jazzicon extends PureComponent { |
||||
static propTypes = { |
||||
address: PropTypes.string.isRequired, |
||||
className: PropTypes.string, |
||||
diameter: PropTypes.number, |
||||
style: PropTypes.object, |
||||
} |
||||
|
||||
static defaultProps = { |
||||
diameter: 46, |
||||
} |
||||
|
||||
componentDidMount () { |
||||
if (!isNode) { |
||||
this.appendJazzicon() |
||||
} |
||||
} |
||||
|
||||
componentDidUpdate (prevProps) { |
||||
const { address: prevAddress } = prevProps |
||||
const { address } = this.props |
||||
|
||||
if (!isNode && address !== prevAddress) { |
||||
this.removeExistingChildren() |
||||
this.appendJazzicon() |
||||
} |
||||
} |
||||
|
||||
removeExistingChildren () { |
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
const container = findDOMNode(this) |
||||
const { children } = container |
||||
|
||||
for (let i = 0; i < children.length; i++) { |
||||
container.removeChild(children[i]) |
||||
} |
||||
} |
||||
|
||||
appendJazzicon () { |
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
const container = findDOMNode(this) |
||||
const { address, diameter } = this.props |
||||
const image = iconFactory.iconForAddress(address, diameter) |
||||
container.appendChild(image) |
||||
} |
||||
|
||||
render () { |
||||
const { className, style } = this.props |
||||
|
||||
return ( |
||||
<div |
||||
className={className} |
||||
style={style} |
||||
/> |
||||
) |
||||
} |
||||
} |
@ -1 +0,0 @@ |
||||
export { default } from './transaction-details.container' |
@ -1,54 +0,0 @@ |
||||
import React, { PureComponent } from 'react' |
||||
import PropTypes from 'prop-types' |
||||
import Modal from '../../modal' |
||||
import TransactionListItemDetails from '../../transaction-list-item-details' |
||||
import { hexToDecimal } from '../../../helpers/conversions.util' |
||||
|
||||
export default class TransactionConfirmed extends PureComponent { |
||||
static contextTypes = { |
||||
t: PropTypes.func, |
||||
} |
||||
|
||||
static propTypes = { |
||||
hideModal: PropTypes.func, |
||||
transaction: PropTypes.object, |
||||
onRetry: PropTypes.func, |
||||
showRetry: PropTypes.bool, |
||||
onCancel: PropTypes.func, |
||||
showCancel: PropTypes.bool, |
||||
} |
||||
|
||||
handleSubmit = () => { |
||||
this.props.hideModal() |
||||
} |
||||
|
||||
handleRetry = () => { |
||||
const { onRetry, hideModal } = this.props |
||||
|
||||
Promise.resolve(onRetry()).then(() => hideModal()) |
||||
} |
||||
|
||||
render () { |
||||
const { t } = this.context |
||||
const { transaction, showRetry, onCancel, showCancel } = this.props |
||||
const { txParams: { nonce } = {} } = transaction |
||||
const decimalNonce = nonce && hexToDecimal(nonce) |
||||
|
||||
return ( |
||||
<Modal |
||||
onSubmit={this.handleSubmit} |
||||
onClose={this.handleSubmit} |
||||
submitText={t('ok')} |
||||
headerText={t('transactionWithNonce', [`#${decimalNonce}`])} |
||||
> |
||||
<TransactionListItemDetails |
||||
transaction={transaction} |
||||
onRetry={this.handleRetry} |
||||
showRetry={showRetry} |
||||
onCancel={() => onCancel()} |
||||
showCancel={showCancel} |
||||
/> |
||||
</Modal> |
||||
) |
||||
} |
||||
} |
@ -1,4 +0,0 @@ |
||||
import TransactionDetails from './transaction-details.component' |
||||
import withModalProps from '../../../higher-order-components/with-modal-props' |
||||
|
||||
export default withModalProps(TransactionDetails) |
@ -1 +1 @@ |
||||
export { default } from './transaction-breakdown.component' |
||||
export { default } from './transaction-breakdown.container' |
||||
|
@ -0,0 +1,11 @@ |
||||
import { connect } from 'react-redux' |
||||
import TransactionBreakdown from './transaction-breakdown.component' |
||||
import { getNativeCurrency } from '../../selectors' |
||||
|
||||
const mapStateToProps = (state) => { |
||||
return { |
||||
nativeCurrency: getNativeCurrency(state), |
||||
} |
||||
} |
||||
|
||||
export default connect(mapStateToProps)(TransactionBreakdown) |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue