Merge pull request #333 from poanetwork/vb-rsk-testnet-explorer-links-support

Support RSK testnet explorer links
This commit is contained in:
Victor Baranov 2020-03-23 19:29:30 +03:00 committed by GitHub
commit 91918a6cdc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 184 additions and 108 deletions

View File

@ -2,6 +2,7 @@
## Current Master
- [#333](https://github.com/poanetwork/nifty-wallet/pull/333) - (Fix) Support RSK testnet explorer links
- [#332](https://github.com/poanetwork/nifty-wallet/pull/332) - (Chore) Return to main screen from removal of imported account
- [#330](https://github.com/poanetwork/nifty-wallet/pull/330) - (Fix) Derive correct addresses for custom networks (RSK/ETC)
- [#329](https://github.com/poanetwork/nifty-wallet/pull/329) - (Fix) Connect to unknown private network fix

View File

@ -37,7 +37,7 @@ class ContractImportView extends Component {
this.setState({
contractAddr,
}, () => {
this.autodetectContractAbi()
this.autodetectContractABI()
})
}
@ -52,7 +52,7 @@ class ContractImportView extends Component {
})
}
} catch (e) {
this.clearAbi()
this.clearABI()
log.debug('ABI can not be parsed')
}
}
@ -116,11 +116,11 @@ class ContractImportView extends Component {
)
}
autodetectContractAbi = () => {
autodetectContractABI = () => {
const { contractAddr, web3 } = this.state
const { type, network } = this.props
if (!contractAddr || !web3.isAddress(contractAddr)) {
this.clearAbi()
this.clearABI()
return
}
getFullABI(web3.eth, contractAddr, network, type)
@ -134,7 +134,7 @@ class ContractImportView extends Component {
}
})
.catch(e => {
this.clearAbi()
this.clearABI()
log.debug(e)
this.props.displayWarning(e.message)
})
@ -166,13 +166,13 @@ class ContractImportView extends Component {
const { contractAddr, web3 } = this.state
if (!contractAddr || !web3.isAddress(contractAddr)) {
this.clearAbi()
this.clearABI()
return this.props.displayWarning('Invalid contract address')
}
const contractAddrCode = await this.getContractCode()
if (contractAddrCode === '0x') {
this.clearAbi()
this.clearABI()
return this.props.displayWarning('This is not a contract address')
}
@ -180,12 +180,12 @@ class ContractImportView extends Component {
try {
abi = JSON.parse(this.state.abi)
} catch (e) {
this.clearAbi()
this.clearABI()
this.props.displayWarning('Invalid ABI')
}
if (!abi) {
this.clearAbi()
this.clearABI()
return this.props.displayWarning('Invalid contract ABI')
}
@ -203,7 +203,7 @@ class ContractImportView extends Component {
})
}
clearAbi () {
clearABI () {
this.setState({
abi: '',
abiInputDisabled: false,

View File

@ -57,25 +57,46 @@ const getBlockscoutApiNetworkSuffix = (network) => {
}
}
const _isBlockscoutInstanceForThisChain = (network) => {
switch (Number(network)) {
case 1:
case 99:
case 77:
case 100:
return true
case 42:
case 3:
case 4:
return false
default:
return false
}
}
const fetchABI = (addr, network) => {
return new Promise((resolve, reject) => {
const networkParent = getBlockscoutApiNetworkPrefix(network)
const networkName = getBlockscoutApiNetworkSuffix(network)
const bloscoutApiLink = `https://blockscout.com/${networkParent}/${networkName}/api`
const bloscoutApiContractPath = '?module=contract'
const blockscoutApiGetAbiPath = `&action=getabi&address=${addr}`
const apiLink = `${bloscoutApiLink}${bloscoutApiContractPath}${blockscoutApiGetAbiPath}`
fetch(apiLink)
.then(response => {
return response.json()
})
.then(responseJson => {
resolve(responseJson && responseJson.result)
})
.catch((e) => {
log.debug(e)
const blockscoutInstanceExists = _isBlockscoutInstanceForThisChain(network)
if (blockscoutInstanceExists) {
const networkParent = getBlockscoutApiNetworkPrefix(network)
const networkName = getBlockscoutApiNetworkSuffix(network)
const bloscoutApiLink = `https://blockscout.com/${networkParent}/${networkName}/api`
const bloscoutApiContractPath = '?module=contract'
const blockscoutApiGetAbiPath = `&action=getabi&address=${addr}`
const apiLink = `${bloscoutApiLink}${bloscoutApiContractPath}${blockscoutApiGetAbiPath}`
fetch(apiLink)
.then(response => {
return response.json()
})
.then(responseJson => {
resolve(responseJson && responseJson.result)
})
.catch(e => {
log.debug(e)
reject(e)
})
} else {
resolve()
})
}
})
}
@ -94,13 +115,17 @@ const getFullABI = (eth, contractAddr, network, type) => {
}
try {
eth.contract(targetABI).at(contractAddr).implementation.call((err, implAddr) => {
fetchABI(implAddr, network)
.then((implABI) => {
implABI = implABI && JSON.parse(implABI)
finalABI = implABI ? targetABI.concat(implABI) : targetABI
resolve(finalABI)
})
.catch(e => reject(e))
if (err) {
reject(err)
} else {
fetchABI(implAddr, network)
.then((implABI) => {
implABI = implABI && JSON.parse(implABI)
finalABI = implABI ? targetABI.concat(implABI) : targetABI
resolve(finalABI)
})
.catch(e => reject(e))
}
})
} catch (e) {
reject(e)

View File

@ -205,7 +205,8 @@ class AccountDropdowns extends Component {
viewOnBlockExplorer = () => {
const { selected, network } = this.props
const url = ethNetProps.explorerLinks.getExplorerAccountLinkFor(selected, network)
const networkCode = parseInt(network, 10)
const url = ethNetProps.explorerLinks.getExplorerAccountLinkFor(selected, networkCode)
global.platform.openWindow({ url })
}

View File

@ -17,8 +17,8 @@ class NetworksMenu extends Component {
provider: PropTypes.any.isRequired,
frequentRpcList: PropTypes.array.isRequired,
isNetworkMenuOpen: PropTypes.bool,
setProviderType: PropTypes.function,
showDeleteRPC: PropTypes.function,
setProviderType: PropTypes.func,
showDeleteRPC: PropTypes.func,
}
render () {
@ -50,7 +50,7 @@ class NetworksMenu extends Component {
// classes from three constituent nodes of the toggle element
if (isNotToggleElement) {
this.props.updateNetworksMenuOpenState(false)
props.updateNetworksMenuOpenState(false)
}
}}
zIndex={11}
@ -70,7 +70,7 @@ class NetworksMenu extends Component {
<DropdownMenuItem
key={'default'}
closeMenu={() => this.props.updateNetworksMenuOpenState(!isOpen)}
closeMenu={() => props.updateNetworksMenuOpenState(!isOpen)}
onClick={() => {
props.setProviderType(LOCALHOST, LOCALHOST_RPC_URL)
props.setRpcTarget(LOCALHOST_RPC_URL)
@ -86,8 +86,8 @@ class NetworksMenu extends Component {
</DropdownMenuItem>
<DropdownMenuItem
closeMenu={() => this.props.updateNetworksMenuOpenState(!isOpen)}
onClick={() => this.props.showConfigPage()}
closeMenu={() => props.updateNetworksMenuOpenState(!isOpen)}
onClick={() => props.showConfigPage()}
className={'app-bar-networks-dropdown-custom-rpc'}
>Custom RPC</DropdownMenuItem>
@ -109,7 +109,7 @@ class NetworksMenu extends Component {
return (
<DropdownMenuItem
key={networkObj.providerName}
closeMenu={() => this.props.updateNetworksMenuOpenState(!isOpen)}
closeMenu={() => props.updateNetworksMenuOpenState(!isOpen)}
onClick={() => props.setProviderType(networkObj.providerName)}
style={{
paddingLeft: '20px',
@ -132,6 +132,7 @@ class NetworksMenu extends Component {
}
renderCustomOption ({ rpcTarget, type }) {
const props = this.props
if (type !== 'rpc') {
return null
}
@ -149,8 +150,8 @@ class NetworksMenu extends Component {
return (
<DropdownMenuItem
key={rpcTarget}
onClick={() => this.props.setRpcTarget(rpcTarget)}
closeMenu={() => this.props.updateNetworksMenuOpenState(false)}
onClick={() => props.setRpcTarget(rpcTarget)}
closeMenu={() => props.updateNetworksMenuOpenState(false)}
>
<i className="fa fa-question-circle fa-lg menu-icon" />
{label}
@ -171,7 +172,7 @@ class NetworksMenu extends Component {
return (
<DropdownMenuItem
key={`common${rpc}`}
closeMenu={() => this.props.updateNetworksMenuOpenState(false)}
closeMenu={() => props.updateNetworksMenuOpenState(false)}
onClick={() => props.setRpcTarget(rpc)}
style={{
paddingLeft: '20px',
@ -183,8 +184,8 @@ class NetworksMenu extends Component {
onClick={(event) => {
event.preventDefault()
event.stopPropagation()
this.props.updateNetworksMenuOpenState(false)
props.showDeleteRPC(rpc)
props.updateNetworksMenuOpenState(false)
props.showDeleteRPC(rpc, true)
}}
/>
</DropdownMenuItem>
@ -210,7 +211,7 @@ class NetworksMenu extends Component {
<DropdownMenuItem
key={rpcTarget}
onClick={() => props.setRpcTarget(rpcTarget)}
closeMenu={() => this.props.updateNetworksMenuOpenState(false)}
closeMenu={() => props.updateNetworksMenuOpenState(false)}
style={{
paddingLeft: '20px',
color: 'white',
@ -223,8 +224,8 @@ class NetworksMenu extends Component {
onClick={(event) => {
event.preventDefault()
event.stopPropagation()
this.props.updateNetworksMenuOpenState(false)
props.showDeleteRPC(label)
props.updateNetworksMenuOpenState(false)
props.showDeleteRPC(label, true)
}}
/>
</DropdownMenuItem>
@ -238,7 +239,7 @@ const mapDispatchToProps = dispatch => {
showConfigPage: () => dispatch(actions.showConfigPage()),
setRpcTarget: (rpcTarget) => dispatch(actions.setRpcTarget(rpcTarget)),
setProviderType: (providerType) => dispatch(actions.setProviderType(providerType)),
showDeleteRPC: (label) => dispatch(actions.showDeleteRPC(label)),
showDeleteRPC: (label, transitionForward) => dispatch(actions.showDeleteRPC(label, transitionForward)),
}
}

View File

@ -4,17 +4,21 @@ import { connect } from 'react-redux'
import actions from '../../../ui/app/actions'
class DeleteRpc extends ConfirmScreen {
static propTypes = {
}
render () {
const props = this.props
return (
<ConfirmScreen
subtitle="Delete Custom RPC"
question={`Are you sure to delete ${this.props.url} ?`}
onCancelClick={() => this.props.dispatch(actions.showConfigPage())}
onNoClick={() => this.props.dispatch(actions.showConfigPage())}
question={`Are you sure to delete ${props.url} ?`}
onCancelClick={() => props.showConfigPage()}
onNoClick={() => props.showConfigPage()}
onYesClick={() => {
this.props.dispatch(actions.removeCustomRPC(this.props.url, this.props.provider))
props.removeCustomRPC(props.url, props.provider)
.then(() => {
this.props.dispatch(actions.showConfigPage())
props.showConfigPage()
})
}}
/>
@ -30,4 +34,11 @@ function mapStateToProps (state) {
}
}
module.exports = connect(mapStateToProps)(DeleteRpc)
const mapDispatchToProps = dispatch => {
return {
showConfigPage: () => dispatch(actions.showConfigPage()),
removeCustomRPC: (url, provider) => dispatch(actions.removeCustomRPC(url, provider)),
}
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(DeleteRpc)

View File

@ -5,8 +5,8 @@ import PropTypes from 'prop-types'
export default class PendingMsg extends Component {
static propTypes = {
txData: PropTypes.object,
cancelMessage: PropTypes.function,
signMessage: PropTypes.function,
cancelMessage: PropTypes.func,
signMessage: PropTypes.func,
}
render () {

View File

@ -5,8 +5,8 @@ import PropTypes from 'prop-types'
export default class PendingMsg extends Component {
static propTypes = {
txData: PropTypes.object,
cancelPersonalMessage: PropTypes.function,
signPersonalMessage: PropTypes.function,
cancelPersonalMessage: PropTypes.func,
signPersonalMessage: PropTypes.func,
}
render () {

View File

@ -5,8 +5,8 @@ import PropTypes from 'prop-types'
export default class PendingMsg extends Component {
static propTypes = {
txData: PropTypes.object,
cancelTypedMessage: PropTypes.function,
signTypedMessage: PropTypes.function,
cancelTypedMessage: PropTypes.func,
signTypedMessage: PropTypes.func,
}
render () {

View File

@ -26,6 +26,7 @@ const { POA_CODE,
GOERLI_TESTNET_CODE,
CLASSIC_CODE,
RSK_CODE,
RSK_TESTNET_CODE,
} = require('../../../app/scripts/controllers/network/enums')
const mapDispatchToProps = dispatch => {
@ -87,7 +88,8 @@ TransactionListItem.prototype.render = function () {
numericNet === DAI_CODE ||
numericNet === GOERLI_TESTNET_CODE ||
numericNet === CLASSIC_CODE ||
numericNet === RSK_CODE
numericNet === RSK_CODE ||
numericNet === RSK_TESTNET_CODE
var isMsg = ('msgParams' in transaction)
var isTx = ('txParams' in transaction)

View File

@ -26,29 +26,39 @@ const {
const POCKET_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET, POA, DAI, GOERLI_TESTNET, POA_SOKOL]
class ConfigScreen extends Component {
static propTypes = {
metamask: PropTypes.object,
warning: PropTypes.string,
provider: PropTypes.object,
dProviderStore: PropTypes.object,
setProviderType: PropTypes.func,
showDeleteRPC: PropTypes.func,
displayWarning: PropTypes.func,
goHome: PropTypes.func,
setDProvider: PropTypes.func,
setRpcTarget: PropTypes.func,
confirmChangePassword: PropTypes.func,
revealSeedConfirmation: PropTypes.func,
resetAccount: PropTypes.func,
setCurrentCurrency: PropTypes.func,
}
constructor (props) {
super(props)
this.state = {
loading: false,
dProvider: props.metamask.dProviderStore.dProvider,
dProvider: props.dProviderStore.dProvider,
}
}
static propTypes = {
dispatch: PropTypes.func,
metamask: PropTypes.object,
warning: PropTypes.string,
}
render () {
const state = this.props
const metamaskState = state.metamask
const warning = state.warning
const props = this.props
const metamaskState = props.metamask
const warning = props.warning
if (state.metamask.dProviderStore.dProvider !== this.state.dProvider) {
if (props.dProviderStore.dProvider !== this.state.dProvider) {
this.setState({
dProvider: this.props.metamask.dProviderStore.dProvider,
dProvider: props.dProviderStore.dProvider,
})
}
@ -70,7 +80,7 @@ class ConfigScreen extends Component {
h('.section-title.flex-row.flex-center', [
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
onClick: () => {
state.dispatch(actions.goHome())
props.goHome()
},
style: {
position: 'absolute',
@ -101,7 +111,7 @@ class ConfigScreen extends Component {
},
}, [
this.currentProviderDisplay(metamaskState, state),
this.currentProviderDisplay(metamaskState),
h('div', { style: {display: 'flex'} }, [
h('input#new_rpc', {
@ -119,7 +129,7 @@ class ConfigScreen extends Component {
if (event.key === 'Enter') {
const element = event.target
const newRpc = element.value
this.rpcValidation(newRpc, state)
this.rpcValidation(newRpc)
}
},
}),
@ -130,13 +140,13 @@ class ConfigScreen extends Component {
event.preventDefault()
const element = document.querySelector('input#new_rpc')
const newRpc = element.value
this.rpcValidation(newRpc, state)
this.rpcValidation(newRpc)
},
}, 'Save'),
h('hr.horizontal-line'),
this.currentConversionInformation(metamaskState, state),
this.currentConversionInformation(metamaskState),
h('hr.horizontal-line'),
@ -150,7 +160,7 @@ class ConfigScreen extends Component {
onClick (event) {
window.logStateString((err, result) => {
if (err) {
state.dispatch(actions.displayWarning('Error in retrieving state logs.'))
props.displayWarning('Error in retrieving state logs.')
} else {
exportAsFile('Nifty Wallet State Logs.json', result)
}
@ -170,7 +180,7 @@ class ConfigScreen extends Component {
},
onClick (event) {
event.preventDefault()
state.dispatch(actions.revealSeedConfirmation())
props.revealSeedConfirmation()
},
}, 'Reveal Seed Words'),
]),
@ -221,7 +231,7 @@ class ConfigScreen extends Component {
},
onClick (event) {
event.preventDefault()
state.dispatch(actions.resetAccount())
props.resetAccount()
},
}, 'Reset Account'),
@ -230,7 +240,7 @@ class ConfigScreen extends Component {
h('button.btn-spread', {
onClick (event) {
event.preventDefault()
state.dispatch(actions.confirmChangePassword())
props.confirmChangePassword()
},
}, 'Change password'),
]),
@ -241,30 +251,32 @@ class ConfigScreen extends Component {
}
toggleProvider () {
const isPocket = POCKET_PROVIDER_TYPES.includes(this.props.metamask.provider.type)
const props = this.props
const isPocket = POCKET_PROVIDER_TYPES.includes(props.provider.type)
if (isPocket) {
if (!this.state.dProvider) {
this.props.dispatch(actions.setDProvider(true))
props.setDProvider(true)
this.setState({
dProvider: true,
})
} else {
this.props.dispatch(actions.setDProvider(false))
props.setDProvider(false)
this.setState({
dProvider: false,
})
}
this.props.dispatch(actions.setProviderType(this.props.metamask.provider.type))
props.setProviderType(props.provider.type)
} else {
alert('Pocket does not support this network, using centralized provider')
}
}
componentWillUnmount () {
this.props.dispatch(actions.displayWarning(''))
this.props.displayWarning('')
}
rpcValidation (newRpc, state) {
rpcValidation (newRpc) {
const props = this.props
if (validUrl.isWebUri(newRpc)) {
this.setState({
loading: true,
@ -272,9 +284,9 @@ class ConfigScreen extends Component {
const web3 = new Web3(new Web3.providers.HttpProvider(newRpc))
web3.eth.getBlockNumber((err, res) => {
if (err) {
state.dispatch(actions.displayWarning('Invalid RPC endpoint'))
props.displayWarning('Invalid RPC endpoint')
} else {
state.dispatch(actions.setRpcTarget(newRpc))
props.setRpcTarget(newRpc)
}
this.setState({
loading: false,
@ -282,14 +294,15 @@ class ConfigScreen extends Component {
})
} else {
if (!newRpc.startsWith('http')) {
state.dispatch(actions.displayWarning('URIs require the appropriate HTTP/HTTPS prefix.'))
props.displayWarning('URIs require the appropriate HTTP/HTTPS prefix.')
} else {
state.dispatch(actions.displayWarning('Invalid RPC URI'))
props.displayWarning('Invalid RPC URI')
}
}
}
currentConversionInformation (metamaskState, state) {
currentConversionInformation (metamaskState) {
const props = this.props
const currentCurrency = metamaskState.currentCurrency
const conversionDate = metamaskState.conversionDate
return h('div', [
@ -300,7 +313,7 @@ class ConfigScreen extends Component {
event.preventDefault()
const element = document.getElementById('currentCurrency')
const newCurrency = element.value
state.dispatch(actions.setCurrentCurrency(newCurrency))
props.setCurrentCurrency(newCurrency)
},
defaultValue: currentCurrency,
}, infuraCurrencies.map((currency) => {
@ -310,7 +323,8 @@ class ConfigScreen extends Component {
])
}
currentProviderDisplay (metamaskState, state) {
currentProviderDisplay (metamaskState) {
const props = this.props
const provider = metamaskState.provider
let title, value
@ -328,7 +342,7 @@ class ConfigScreen extends Component {
provider.type === 'rpc' && h('button.btn-spread', {
onClick (event) {
event.preventDefault()
state.dispatch(actions.showDeleteRPC())
props.showDeleteRPC()
},
}, 'Delete'),
])
@ -338,8 +352,25 @@ class ConfigScreen extends Component {
function mapStateToProps (state) {
return {
metamask: state.metamask,
provider: state.metamask.provider,
dProviderStore: state.metamask.dProviderStore,
warning: state.appState.warning,
}
}
module.exports = connect(mapStateToProps)(ConfigScreen)
const mapDispatchToProps = dispatch => {
return {
setProviderType: (providerType) => dispatch(actions.setProviderType(providerType)),
showDeleteRPC: (label, transitionForward) => dispatch(actions.showDeleteRPC(label, transitionForward)),
displayWarning: (msg) => dispatch(actions.displayWarning(msg)),
goHome: () => dispatch(actions.goHome()),
setDProvider: (set) => dispatch(actions.setDProvider(set)),
setRpcTarget: (rpcTarget) => dispatch(actions.setRpcTarget(rpcTarget)),
setCurrentCurrency: (newCurrency) => dispatch(actions.setRpcTarget(newCurrency)),
confirmChangePassword: () => dispatch(actions.confirmChangePassword()),
resetAccount: () => dispatch(actions.resetAccount()),
revealSeedConfirmation: () => dispatch(actions.revealSeedConfirmation()),
}
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(ConfigScreen)

View File

@ -4,17 +4,21 @@ import { connect } from 'react-redux'
import actions from '../../ui/app/actions'
class RemoveTokenScreen extends ConfirmScreen {
static propTypes = {
}
render () {
const props = this.props
return (
<ConfirmScreen
subtitle="Remove Token"
question={`Are you sure you want to remove token "${this.props.symbol}"?`}
onCancelClick={() => this.props.goHome()}
onNoClick={() => this.props.goHome()}
question={`Are you sure you want to remove token "${props.symbol}"?`}
onCancelClick={() => props.goHome()}
onNoClick={() => props.goHome()}
onYesClick={() => {
this.props.removeToken(this.props.address)
props.removeToken(props.address)
.then(() => {
this.props.goHome()
props.goHome()
})
}}
/>

6
package-lock.json generated
View File

@ -12001,9 +12001,9 @@
}
},
"eth-net-props": {
"version": "1.0.32",
"resolved": "https://registry.npmjs.org/eth-net-props/-/eth-net-props-1.0.32.tgz",
"integrity": "sha512-t0BN0xoHZlwUjLZFJCdHyA6d5PMG1Ic+ykii4gW4kU/cRQ4q60utO/MgBerRHQnjwraQzYgy0OxcX94RLoM/NA==",
"version": "1.0.33",
"resolved": "https://registry.npmjs.org/eth-net-props/-/eth-net-props-1.0.33.tgz",
"integrity": "sha512-RoqoXkY3+ztjdD9EfbjnMapWqfRjUscPon4Vjt48RLHDDYyNmz7LiQ4Qgb4E41PNS7xRoGkQCWJvJRAI9vDsFQ==",
"requires": {
"chai": "^4.2.0"
}

View File

@ -116,7 +116,7 @@
"eth-keychain-controller": "github:vbaranov/KeyringController#5.1.0",
"eth-ledger-bridge-keyring": "github:vbaranov/eth-ledger-bridge-keyring#0.1.0-clear-accounts-flag",
"eth-method-registry": "^1.0.0",
"eth-net-props": "^1.0.32",
"eth-net-props": "^1.0.33",
"eth-phishing-detect": "^1.1.4",
"eth-query": "^2.1.2",
"eth-sig-util": "^2.2.0",