diff --git a/src/components/AlertMessage.js b/src/components/AlertMessage.js index fe7a03a7..31eedb85 100644 --- a/src/components/AlertMessage.js +++ b/src/components/AlertMessage.js @@ -7,16 +7,21 @@ import { function AlertMessage(props) { const [show, setShow] = useState(false); + const dismissible = props.dismissible === false ? false : true + useEffect(() => { setShow(!!props.message || !!props.children) }, [props.message, props.children]); - const dismissible = props.dismissible === false ? false : true + function onClose(){ + setShow(false) + props.onClose && props.onClose() + } return ( <> {show && - setShow(false)} dismissible={dismissible}> + {props.message || props.children} } diff --git a/src/components/App.js b/src/components/App.js index 7b12987e..a697cbf9 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -22,6 +22,7 @@ import { Droplet, DropletFill, DropletHalf, + CashCoin, Coin, Inboxes, Stars, @@ -99,7 +100,6 @@ class App extends React.Component { this.setState({ address: null, balance: null, - queryClient: null, stargateClient: null }) } @@ -111,6 +111,12 @@ class App extends React.Component { }) } + if(manual && !this.state.keplr){ + return this.setState({ + keplrError: true + }) + } + if(localStorage.getItem('connected') !== '1'){ if(manual){ localStorage.setItem('connected', '1') @@ -152,15 +158,13 @@ class App extends React.Component { address: address, accountName: key.name, stargateClient: stargateClient, - queryClient: network.queryClient, error: false }) this.getBalance() } catch (e) { console.log(e) return this.setState({ - error: 'Failed to connect to signing client: ' + e.message, - loading: false + error: 'Failed to connect to signer: ' + e.message }) } } @@ -209,7 +213,9 @@ class App extends React.Component { } async getBalance() { - this.state.queryClient.getBalance(this.state.address, this.props.network.denom) + if(!this.state.address) return + + this.props.queryClient.getBalance(this.state.address, this.props.network.denom) .then( (balance) => { this.setState({ @@ -254,16 +260,16 @@ class App extends React.Component { let icon, mode let iconProps = { size: '1.4em', - className: 'me-3', + className: 'mx-2 mx-md-3', role: 'button', onClick: () => this.props.changeNetworkMode(mode) } if (this.props.directory.testnet) { - iconProps.className = 'me-3 text-warning' + iconProps.className = iconProps.className + ' text-warning' icon = mode = 'mainnet' } else { - iconProps.className = 'me-3 text-muted' + iconProps.className = iconProps.className + ' text-muted' icon = mode = 'testnet' } @@ -279,7 +285,7 @@ class App extends React.Component { return (
-
+
this.props.setActive('networks')} role="button" className="text-reset text-decoration-none"> {this.props.theme === 'light' @@ -350,8 +356,9 @@ class App extends React.Component { + {this.state.accountName || 'Wallet'} @@ -386,19 +393,10 @@ class App extends React.Component { )} - {!this.state.address && this.props.network && ( - !this.state.keplr - ? ( - - Please install the Keplr browser extension using desktop Google Chrome.
WalletConnect and mobile support is coming soon. -
- ) : this.props.active !== 'networks' && ( -
- -
- ) + {!this.state.keplr && this.state.keplrError && ( + this.setState({keplrError: false})}> + Please install the Keplr browser extension using desktop Google Chrome.
WalletConnect and mobile support is coming soon. +
)} {this.props.active === 'networks' && ( )} - {this.props.active === 'governance' && this.state.address && ( + {this.props.active === 'governance' && ( )} - {this.props.active === 'delegations' && this.state.address && + {this.props.active === 'delegations' && <> } diff --git a/src/components/DelegateForm.js b/src/components/DelegateForm.js index e7db0f48..5dab58d6 100644 --- a/src/components/DelegateForm.js +++ b/src/components/DelegateForm.js @@ -45,7 +45,7 @@ class DelegateForm extends React.Component { let messages = this.buildMessages(denomAmount) let gas try { - gas = await client.simulate(this.props.address, messages, undefined, this.modifier()) + gas = await client.simulate(this.props.address, messages) } catch (error) { this.setState({ loading: false, error: error.message }) return @@ -89,14 +89,10 @@ class DelegateForm extends React.Component { return messages } - modifier(){ - if(this.props.undelegate) return 1.5 - } - async setAvailableAmount(){ this.setState({error: undefined}) const messages = this.buildMessages(multiply(this.props.availableBalance.amount, 0.95)) - this.props.stargateClient.simulate(this.props.address, messages, undefined, this.modifier()).then(gas => { + this.props.stargateClient.simulate(this.props.address, messages).then(gas => { const saveTxFeeNum = (this.props.redelegate || this.props.undelegate) ? 0 : 10 const gasPrice = this.props.stargateClient.getFee(gas).amount[0].amount const decimals = pow(10, this.props.network.decimals || 6) @@ -131,32 +127,34 @@ class DelegateForm extends React.Component { }
- - Amount -
-
- - {this.denom()} +
+ + Amount +
+
+ + {this.denom()} +
+ {this.props.availableBalance && +
this.setAvailableAmount()}> + Available: +
+ }
- {this.props.availableBalance && -
this.setAvailableAmount()}> - Available: -
+
+ + Memo + + +

+ {!this.state.loading + ? + : } -

- - - Memo - - -

- {!this.state.loading - ? - : - } -

+

+ ) diff --git a/src/components/Delegations.js b/src/components/Delegations.js index 03889b45..d410c762 100644 --- a/src/components/Delegations.js +++ b/src/components/Delegations.js @@ -35,7 +35,7 @@ class Delegations extends React.Component { } async componentDidMount() { - const isNanoLedger = this.props.stargateClient.getIsNanoLedger(); + const isNanoLedger = this.props.stargateClient?.getIsNanoLedger(); this.setState({ isNanoLedger: isNanoLedger }); await this.getDelegations() this.getGrants(true) @@ -49,22 +49,19 @@ class Delegations extends React.Component { async componentDidUpdate(prevProps, prevState) { if (this.props.network !== prevProps.network) { this.clearRefreshInterval() - this.setState({ delegations: undefined }) + this.setState({ delegations: undefined, validatorApy: {} }) } if (prevProps.validator !== this.props.validator && this.props.validator && !this.state.validatorModal.show) { this.showValidatorModal(this.props.validator) } - if (!this.props.address) return; - if (this.props.address !== prevProps.address) { this.clearRefreshInterval() - const isNanoLedger = this.props.stargateClient.getIsNanoLedger(); + const isNanoLedger = this.props.stargateClient?.getIsNanoLedger(); this.setState({ isNanoLedger: isNanoLedger, error: null, - validatorApy: {}, operatorGrants: {} }); await this.getDelegations() @@ -87,9 +84,9 @@ class Delegations extends React.Component { } async refresh() { + this.calculateApy(); this.getWithdrawAddress(); this.getRewards(); - this.calculateApy(); this.refreshInterval(); } @@ -114,6 +111,8 @@ class Delegations extends React.Component { } async getDelegations(hideError) { + if(!this.props.address) return + return this.props.queryClient.getDelegations(this.props.address) .then( (delegations) => { @@ -143,6 +142,8 @@ class Delegations extends React.Component { } async getWithdrawAddress() { + if(!this.props.address) return + const withdraw = await this.props.queryClient.getWithdrawAddress(this.props.address) if (withdraw !== this.props.address) { this.setState({ error: 'You have a different withdraw address set. REStake WILL NOT WORK!' }) @@ -150,6 +151,8 @@ class Delegations extends React.Component { } getRewards(hideError) { + if(!this.props.address) return + this.props.queryClient .getRewards(this.props.address, this.props.network.denom) .then( @@ -182,7 +185,7 @@ class Delegations extends React.Component { } async getGrants(hideError) { - if (!this.authzSupport() || !this.props.operators.length) return + if (!this.props.address || !this.authzSupport() || !this.props.operators.length) return let allGrants try { @@ -194,7 +197,7 @@ class Delegations extends React.Component { const calls = this.orderedOperators().map((operator) => { return () => { const { botAddress } = operator; - if (!this.props.operators.includes(operator)) return; + if (!this.props.address || !this.props.operators.includes(operator)) return; return this.props.queryClient.getGrants(botAddress, this.props.address).then( (result) => { @@ -255,7 +258,6 @@ class Delegations extends React.Component { onGrant(operator, expired, maxTokens) { this.clearRefreshInterval() const operatorGrant = expired ? this.defaultGrant : { - claimGrant: {}, stakeGrant: {}, validators: [operator.address], maxTokens: maxTokens ? bignumber(maxTokens.amount) : null @@ -340,7 +342,7 @@ class Delegations extends React.Component { } restakePossible() { - return !this.state.isNanoLedger && this.authzSupport(); + return this.props.address && !this.state.isNanoLedger && this.authzSupport(); } totalRewards(validators) { @@ -418,7 +420,7 @@ class Delegations extends React.Component { operators={this.props.operators} balance={this.props.balance} rewards={this.state.rewards} - delegations={this.state.delegations} + delegations={this.state.delegations || {}} grants={this.operatorGrants()} authzSupport={this.authzSupport()} restakePossible={this.restakePossible()} @@ -433,7 +435,7 @@ class Delegations extends React.Component { } render() { - if (!this.state.delegations || !this.props.validators) { + if (!this.props.validators) { return (
@@ -480,7 +482,7 @@ class Delegations extends React.Component { validators={this.props.validators} operators={this.props.operators} validatorApy={this.state.validatorApy} - delegations={this.state.delegations} + delegations={this.state.delegations || {}} rewards={this.state.rewards} stargateClient={this.props.stargateClient} validatorLoading={this.state.validatorLoading} @@ -496,9 +498,11 @@ class Delegations extends React.Component {
- + {this.props.address && ( + + )}
diff --git a/src/components/Governance.js b/src/components/Governance.js index ebcc1d79..d3e6cd08 100644 --- a/src/components/Governance.js +++ b/src/components/Governance.js @@ -1,33 +1,14 @@ import React, { useState, useEffect, useReducer } from 'react'; import _ from 'lodash' -import FuzzySearch from 'fuzzy-search' -import { Bech32 } from '@cosmjs/encoding' - -import { add } from 'mathjs' - -import Coins from "./Coins"; -import ClaimRewards from "./ClaimRewards"; -import RevokeRestake from "./RevokeRestake"; -import ValidatorImage from './ValidatorImage' -import TooltipIcon from './TooltipIcon' import { - Table, - Button, Spinner, - Dropdown, - OverlayTrigger, - Tooltip, - Nav } from 'react-bootstrap' -import { FilterSquare, XCircle } from "react-bootstrap-icons"; import { useParams, useNavigate } from "react-router-dom"; -import ValidatorName from "./ValidatorName"; -import ManageRestake from "./ManageRestake"; import AlertMessage from './AlertMessage'; import Proposals from './Proposals'; -import { executeSync, mapSync } from '../utils/Helpers.mjs'; +import { executeSync } from '../utils/Helpers.mjs'; import ProposalModal from './ProposalModal'; import Proposal from '../utils/Proposal.mjs'; import Vote from '../utils/Vote.mjs'; @@ -50,12 +31,9 @@ function Governance(props) { const params = useParams(); useEffect(() => { - setProposals() - setTallies({}) - setVotes({}) - + setProposals(false) getProposals() - }, [props.queryClient]); + }, [network]); useEffect(() => { const interval = setInterval(() => { @@ -82,9 +60,15 @@ function Governance(props) { }, [proposals]); useEffect(() => { - if(!proposals || !address) return + if(!proposals) return - getVotes(proposals) + if(address){ + getVotes(proposals) + }else{ + proposals.forEach(proposal => { + setVotes({ [proposal.proposal_id]: undefined }) + }) + } }, [proposals, address]); async function getProposals(hideError) { diff --git a/src/components/NetworkFinder.js b/src/components/NetworkFinder.js index 63dca12c..8508b56d 100644 --- a/src/components/NetworkFinder.js +++ b/src/components/NetworkFinder.js @@ -21,7 +21,7 @@ function NetworkFinder() { const govMatch = useMatch("/:network/govern/*"); const networkMode = process.env.TESTNET_MODE === '1' ? 'testnet' : 'mainnet' - const directory = CosmosDirectory(networkMode === 'testnet') + const directory = getDirectory() const LS_THEME_KEY = "restake-theme"; const LS_THEME = localStorage.getItem(LS_THEME_KEY) @@ -34,55 +34,69 @@ function NetworkFinder() { {loading: true, networks: {}, operators: [], validators: {}, networkMode, directory} ) - const getNetworks = async () => { - let registryNetworks, operatorAddresses + function getDirectory() { + let testnet = networkMode === 'testnet'; + if (params.network && !testnet) { + const data = networksData.find(el => el.name === params.network); + if (data && data.testnet) { + testnet = true; + } + } + return CosmosDirectory(testnet); + } + + async function getNetworks() { + let registryNetworks, operatorAddresses; try { - registryNetworks = await state.directory.getChains() - operatorAddresses = await state.directory.getOperatorAddresses() + registryNetworks = await state.directory.getChains(); + operatorAddresses = await state.directory.getOperatorAddresses(); } catch (error) { - setState({error: error.message, loading: false}) - return {} + setState({ error: error.message, loading: false }); + return {}; } - const networks = Object.values(registryNetworks).map(data => { - const networkData = networksData.find(el => el.name === data.path) - if(networkData && networkData.enabled === false) return - if(!data.image || data.status === 'killed') return - - if(!networkData) data.experimental = true - - return new Network({...data, ...networkData}, operatorAddresses[data.path]) - }) - return _.compact(networks).reduce((a, v) => ({ ...a, [v.path]: v}), {}) + const networkData = networksData.find(el => el.name === data.path); + if (networkData && networkData.enabled === false) + return; + if (!data.image || data.status === 'killed') + return; + + if (!networkData) + data.experimental = true; + + return new Network({ ...data, ...networkData }, operatorAddresses[data.path]); + }); + return _.compact(networks).reduce((a, v) => ({ ...a, [v.path]: v }), {}); } - const changeNetworkMode = (networkMode) => { - let domain = networkMode === 'testnet' ? process.env.TESTNET_DOMAIN : process.env.MAINNET_DOMAIN - if(domain){ - window.location.replace('https://' + domain) - }else{ - const directory = CosmosDirectory(networkMode === 'testnet') - setState({ networkMode, directory, active: 'networks', network: null, networks: {}, validators: {}, operators: [] }) + function changeNetworkMode(networkMode) { + let domain = networkMode === 'testnet' ? process.env.TESTNET_DOMAIN : process.env.MAINNET_DOMAIN; + if (domain) { + window.location.replace('https://' + domain); + } else { + const directory = CosmosDirectory(networkMode === 'testnet'); + setState({ networkMode, directory, active: 'networks', network: null, queryClient: null, networks: {}, validators: {}, operators: [] }); } } - const changeNetwork = (network) => { + function changeNetwork(network) { setState({ network: network, + queryClient: network.queryClient, validators: network.getValidators(), operators: network.getOperators(), loading: false - }) - if(govMatch){ - setActive('governance', network) - }else{ - setActive('delegations', network) + }); + if (govMatch) { + setActive('governance', network); + } else { + setActive('delegations', network); } } - const setActive = (active, network) => { - network = network || state.network + function setActive(active, network) { + network = network || state.network; switch (active) { case 'governance': navigate("/" + network.path + '/govern'); @@ -94,10 +108,10 @@ function NetworkFinder() { navigate("/"); break; } - setState({active}) + setState({ active }); const body = document.querySelector('#root'); - body.scrollIntoView({}, 500) + body.scrollIntoView({}, 500); } @@ -116,7 +130,7 @@ function NetworkFinder() { setTheme('light') } - if(localStorage.getItem(LS_THEME_KEY) !== themeChoice){ + if (localStorage.getItem(LS_THEME_KEY) !== themeChoice) { localStorage.setItem(LS_THEME_KEY, themeChoice) } @@ -126,7 +140,7 @@ function NetworkFinder() { }, [themeChoice]) useEffect(() => { - if(theme){ + if (theme) { const themeLink = document.getElementById("theme-style"); const themeName = theme === 'dark' ? DARK_THEME : LIGHT_THEME themeLink.setAttribute("href", `https://cdn.jsdelivr.net/npm/bootswatch@5.1.3/dist/${themeName}/bootstrap.min.css`); @@ -135,31 +149,31 @@ function NetworkFinder() { }, [theme]) useEffect(() => { - if(state.error) return - if(!Object.keys(state.networks).length){ - setState({loading: true}) + if (state.error) return + if (!Object.keys(state.networks).length) { + setState({ loading: true }) getNetworks().then(networks => { - setState({networks: networks}) + setState({ networks: networks }) }) } }, [state.networks]) useEffect(() => { - if(!params.network){ - setState({active: 'networks'}) + if (!params.network) { + setState({ active: 'networks' }) } }, [govMatch, params.network]) useEffect(() => { - if(Object.keys(state.networks).length && !state.network){ + if (Object.keys(state.networks).length && !state.network) { let networkName = params.network const network = state.networks[networkName] - if(!network){ + if (!network) { navigate("/"); - setState({loading: false}) + setState({ loading: false }) return } - if(params.network != networkName){ + if (params.network != networkName) { navigate("/" + networkName); } network.load().then(() => { @@ -168,6 +182,7 @@ function NetworkFinder() { setState({ active: govMatch ? 'governance' : 'delegations', network: network, + queryClient: network.queryClient, validators: network.getValidators(), operators: network.getOperators(), loading: false @@ -184,9 +199,9 @@ function NetworkFinder() { }, [state.networks, state.network, params.network, navigate]) useEffect(() => { - if(params.validator && state.validators[params.validator]){ + if (params.validator && state.validators[params.validator]) { setState({ validator: state.validators[params.validator] }) - }else if(state.validator){ + } else if (state.validator) { setState({ validator: null }) } }, [state.validators, params.validator]) @@ -205,11 +220,11 @@ function NetworkFinder() { ) } - return changeNetwork(network, validators)} setActive={setActive} - theme={theme} themeChoice={themeChoice} themeDefault={themeDefault} setThemeChoice={setThemeChoice} + return changeNetwork(network, validators)} setActive={setActive} + theme={theme} themeChoice={themeChoice} themeDefault={themeDefault} setThemeChoice={setThemeChoice} />; } diff --git a/src/components/NetworkSelect.js b/src/components/NetworkSelect.js index 35c66412..275b877d 100644 --- a/src/components/NetworkSelect.js +++ b/src/components/NetworkSelect.js @@ -100,16 +100,16 @@ function NetworkSelect(props) { return ( <> - diff --git a/src/components/Networks.js b/src/components/Networks.js index ca2f56b7..983a998a 100644 --- a/src/components/Networks.js +++ b/src/components/Networks.js @@ -87,10 +87,10 @@ function Networks(props) { changeNetwork(network)}> - + - + {network.prettyName} @@ -107,10 +107,10 @@ function Networks(props) { return ( <> -
-
+
+
- +
-
+
-
- setFilter({...filter, group: e.target.value})}> +
-
+
{renderNetworks(results)} diff --git a/src/components/ProposalDetails.js b/src/components/ProposalDetails.js index 2717a487..9dc0f60c 100644 --- a/src/components/ProposalDetails.js +++ b/src/components/ProposalDetails.js @@ -16,7 +16,7 @@ function ProposalDetails(props) { const { title, description } = proposal.content - const fixDescription = description.split(/\\n/).join('\n') + const fixDescription = description.replace(/\/n/g, '\n').split(/\\n/).join('\n') return ( <> @@ -84,17 +84,19 @@ function ProposalDetails(props) {
-
-

Your Vote

- -
+ {props.address && ( +
+

Your Vote

+ +
+ )}
{title}
-

+

{fixDescription}

diff --git a/src/components/Proposals.js b/src/components/Proposals.js index c745c132..e1b967db 100644 --- a/src/components/Proposals.js +++ b/src/components/Proposals.js @@ -78,21 +78,21 @@ function Proposals(props) { const vote = votes[proposalId] return ( - {proposalId} + {proposalId} - props.showProposal(proposal)}> + props.showProposal(proposal)}> {proposal.content.title} - + {proposal.statusHuman} - + {proposal.isDeposit ? proposal.deposit_end_time : proposal.voting_end_time} - + {proposal.isVoting && ( vote ? vote.optionHuman : )} @@ -105,7 +105,7 @@ function Proposals(props) {
@@ -115,10 +115,20 @@ function Proposals(props) { return ( <> -
-
+
+
+
+ + + + +
+
+
- +
-
+
-
- setFilter({...filter, group: e.target.value})}> +
- setFilter({...filter, status: e.target.value})}> {Object.entries(PROPOSAL_STATUSES).map(([key, value]) => { return ( @@ -157,11 +167,11 @@ function Proposals(props) { - + - - + + diff --git a/src/components/RevokeRestake.js b/src/components/RevokeRestake.js index 5d4ac9be..5d35d044 100644 --- a/src/components/RevokeRestake.js +++ b/src/components/RevokeRestake.js @@ -16,7 +16,7 @@ function RevokeRestake(props) { console.log(messages) try { - const gas = await stargateClient.simulate(address, messages, undefined, 1.3) + const gas = await stargateClient.simulate(address, messages) const result = await stargateClient.signAndBroadcast(address, messages, gas) console.log("Successfully broadcasted:", result); props.setLoading(false) diff --git a/src/components/ValidatorGrants.js b/src/components/ValidatorGrants.js index 7f6b8c55..30c5478d 100644 --- a/src/components/ValidatorGrants.js +++ b/src/components/ValidatorGrants.js @@ -199,7 +199,7 @@ function ValidatorGrants(props) { : grantsExist ? state.maxTokens && smaller(state.maxTokens, props.rewards) ? Not enough grant remaining - : Invalid / total delegation reached + : Invalid / total delegation reached : Inactive} diff --git a/src/components/ValidatorModal.js b/src/components/ValidatorModal.js index fcc2ad57..9f563983 100644 --- a/src/components/ValidatorModal.js +++ b/src/components/ValidatorModal.js @@ -9,8 +9,8 @@ import AboutLedger from './AboutLedger'; import { Modal, - Tabs, - Tab + Tab, + Nav } from 'react-bootstrap' import ValidatorDelegate from './ValidatorDelegate'; @@ -140,53 +140,83 @@ function ValidatorModal(props) { showValidator={selectValidator} manageButton={redelegate ? 'Redelegate' : 'Delegate'} />} {selectedValidator && ( - setActiveTab(k)} id="validator-tabs" className="mb-3"> - - - - - - - {operator() && ( - - setActiveTab(k)} id="validator-tabs"> + + + + + + + + - - )} - {network.authzSupport && operator() && ( - - - - )} - + onDelegate={onDelegate} /> + + {operator() && ( + + + + )} + {network.authzSupport && operator() && ( + + + + )} + + )} diff --git a/src/components/Validators.js b/src/components/Validators.js index 10af8ff3..439d1ef1 100644 --- a/src/components/Validators.js +++ b/src/components/Validators.js @@ -168,7 +168,7 @@ function Validators(props) { - - @@ -446,7 +459,6 @@ function Validators(props) { - {network.apyEnabled !== false && ( @@ -468,7 +480,7 @@ function Validators(props) { {!props.modal && ( -
## Proposal StatusEnd TimeVotedEnd TimeVoted Progress
+ - ) : ( + ) : props.address ? ( + ) : ( + ) ) : ( + + + +
- +
@@ -413,7 +427,6 @@ function Validators(props) {
# Validator REStake Frequency + {props.rewards && ( {_.chunk(Object.entries(choices), 2).map((group, index) => { return ( -
+
{group.map(([key, value]) => { const voteChoice = vote && key === vote.option return ( @@ -115,8 +115,8 @@ function VoteForm(props) { checked={key === choice} disabled={!proposal.isVoting} onChange={handleVoteChange} - /> - + /> + {voteChoice ? {value} : value} diff --git a/src/utils/Network.mjs b/src/utils/Network.mjs index 1afe5d6b..2f309a99 100644 --- a/src/utils/Network.mjs +++ b/src/utils/Network.mjs @@ -72,6 +72,7 @@ class Network { this.image = this.chain.image this.coinGeckoId = this.chain.coinGeckoId this.estimatedApr = this.chain.estimatedApr + this.apyEnabled = this.apyEnabled && !!this.estimatedApr this.authzSupport = this.chain.authzSupport this.defaultGasPrice = format(bignumber(multiply(0.000000025, pow(10, this.decimals))), { notation: 'fixed' }) + this.denom this.gasPrice = this.data.gasPrice || this.defaultGasPrice