Skip to content

Commit

Permalink
Merge pull request #577 from eco-stake/add-prices
Browse files Browse the repository at this point in the history
Add Coingecko prices
  • Loading branch information
tombeynon authored Aug 10, 2022
2 parents 83f5ad0 + b5c853e commit d2cb257
Show file tree
Hide file tree
Showing 21 changed files with 150 additions and 97 deletions.
Binary file added src/assets/coingecko.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/components/AboutLedger.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ function AboutLedger(props) {
const aboutParam = searchParams.get('about')

const { network, validator } = props
const { daemon_name, chain_id, node_home, codebase } = network.chain.chainData
const { daemon_name, chain_id, node_home, codebase } = network.chain.data
const { git_repo, binaries } = codebase || {}

const address = validator?.restake.address || <kbd>GrantAddress</kbd>
Expand Down
36 changes: 24 additions & 12 deletions src/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import {
Dropdown,
ButtonGroup,
Navbar,
Nav
Nav,
Spinner
} from 'react-bootstrap';
import {
Droplet,
Expand Down Expand Up @@ -627,25 +628,35 @@ class App extends React.Component {
)}
</li>
)}
<li className="nav-item ps-3">
{this.state.address && (
<li className="nav-item px-3 border-end align-items-center d-none d-md-flex">
{this.state.balance ? (
<Coins
coins={this.state.balance}
asset={this.props.network.baseAsset}
className="small text-end"
/>
) : (
<Spinner animation="border" role="status" className="spinner-border-sm text-secondary">
<span className="visually-hidden">Loading...</span>
</Spinner>
)}
</li>
)}
<li className="nav-item ps-3 d-flex align-items-center">
<Dropdown as={ButtonGroup}>
<Dropdown.Toggle size="sm" className="rounded" id="dropdown-custom-1">
{this.state.address ? (
<>
<Coins
coins={this.state.balance}
asset={this.props.network.baseAsset}
className="me-1 d-none d-md-inline"
/>
<CashCoin className="d-inline d-md-none" />
<CashCoin className="me-1" />
</>
) : 'Connect'}
</Dropdown.Toggle>
<Dropdown.Menu>
{this.state.address && (
<div className="d-block d-md-none">
<Dropdown.Header className="text-truncate">{this.addressName()}</Dropdown.Header>
<Dropdown.Item>
<Dropdown.Item as="button">
<CopyToClipboard text={this.state.address}
onCopy={() => this.setCopied()}>
<Coins
Expand All @@ -661,6 +672,7 @@ class App extends React.Component {
{this.state.wallet ? (
<>
<Dropdown.Item
as="button"
disabled={!this.state.wallet?.hasPermission(this.state.address, 'Send')}
onClick={() => this.setState({ showSendModal: true })}
>
Expand All @@ -669,16 +681,16 @@ class App extends React.Component {
</>
) : (
<>
<Dropdown.Item onClick={() => this.connect(true)} disabled={!window.keplr}>Connect Keplr Extension</Dropdown.Item>
<Dropdown.Item as="button" onClick={() => this.connect(true)} disabled={!window.keplr}>Connect Keplr Extension</Dropdown.Item>
{/* <Dropdown.Item onClick={() => this.connect(true)} disabled={!window.falcon}>Connect Falcon Wallet</Dropdown.Item> */}
</>

)}
<Dropdown.Item onClick={() => this.setState({ showAddressModal: true })}>Saved Addresses</Dropdown.Item>
<Dropdown.Item as="button" onClick={() => this.setState({ showAddressModal: true })}>Saved Addresses</Dropdown.Item>
{this.state.address && (
<>
<Dropdown.Divider />
<Dropdown.Item onClick={this.disconnect}>Disconnect</Dropdown.Item>
<Dropdown.Item as="button" onClick={this.disconnect}>Disconnect</Dropdown.Item>
</>
)}
</Dropdown.Menu>
Expand Down
2 changes: 1 addition & 1 deletion src/components/ClaimRewards.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ function ClaimRewards(props) {

return (
<>
<Dropdown.Item disabled={props.disabled || !hasPermission()} onClick={() => claim()}>
<Dropdown.Item as="button" disabled={props.disabled || !hasPermission()} onClick={() => claim()}>
{buttonText()}
</Dropdown.Item>
</>
Expand Down
25 changes: 20 additions & 5 deletions src/components/Coins.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import _ from 'lodash'

function Coins(props) {
const { asset, coins, fullPrecision, inBaseDenom, className } = props
const { decimals, symbol } = asset
const { asset, coins, fullPrecision, inBaseDenom, hideValue, className } = props
const { decimals, symbol, prices } = asset
const { coingecko } = prices || {}

function amount(coins){
if(inBaseDenom) return coins.amount
Expand All @@ -11,6 +12,10 @@ function Coins(props) {
return _.round(coins.amount / Math.pow(10, decimals), prec).toLocaleString(undefined, {maximumFractionDigits: prec})
}

function value(coins){
return (coins.amount / Math.pow(10, decimals) * coingecko.usd).toLocaleString(undefined, { maximumFractionDigits: 2, minimumFractionDigits: 2 })
}

if(!coins || !coins.denom){
return null
}
Expand All @@ -23,9 +28,19 @@ function Coins(props) {
}

return (
<span className={['coins', className].join(' ')}>
<span className="amount">{amount(coins)}</span>&nbsp;
<span className="denom">{symbol}</span>
<span className={['d-inline-block m-0 coins', className].join(' ')}>
<span>
<span className="amount">{amount(coins)}</span>&nbsp;
<span className="denom">{symbol}</span>
</span>
{!!coingecko?.usd && !hideValue && !!coins.amount && (
<>
<br />
<em className="text-muted">
<span className="amount">${value(coins)}</span>
</em>
</>
)}
</span>
)
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/CountdownRestake.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ function CountdownRestake(props) {
renderer={countdownRenderer}
/>
{maxAmount && (
<p>Grant remaining: <Coins coins={{amount: maxAmount, denom: props.network.denom}} asset={network.baseAsset} /></p>
<p>Grant remaining: <Coins coins={{amount: maxAmount, denom: props.network.denom}} asset={network.baseAsset} fullPrecision={true} hideValue={true} /></p>
)}
</div>
</TooltipIcon>
Expand Down
2 changes: 1 addition & 1 deletion src/components/DelegateForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ class DelegateForm extends React.Component {
</div>
{this.props.availableBalance &&
<div className="form-text text-end"><span role="button" onClick={() => this.setAvailableAmount()}>
Available: <Coins coins={this.props.availableBalance} asset={this.props.network.baseAsset} />
Available: <Coins coins={this.props.availableBalance} asset={this.props.network.baseAsset} fullPrecision={true} hideValue={true} />
</span></div>
}
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Delegations.js
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,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}
commission={this.state.commission}
stargateClient={this.props.stargateClient}
Expand Down
6 changes: 3 additions & 3 deletions src/components/GrantModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ import { buildExecMessage } from '../utils/Helpers.mjs';

function GrantModal(props) {
const { show, network, address, wallet } = props
const isNanoLedger = props.wallet?.getIsNanoLedger()
const isNanoLedger = wallet?.getIsNanoLedger()
const defaultExpiry = moment().add(1, 'year')
const [loading, setLoading] = useState(false);
const [error, setError] = useState()
const [state, setState] = useState({ maxTokensValue: '', expiryDateValue: defaultExpiry.format('YYYY-MM-DD') });
const [showLedger, setShowLedger] = useState(isNanoLedger)

const { daemon_name, chain_id } = network.chain.chainData
const { daemon_name, chain_id } = network.chain.data

useEffect(() => {
setState({
Expand All @@ -37,7 +37,7 @@ function GrantModal(props) {
messageTypeValue: messageTypes[0],
customMessageTypeValue: '',
})
setShowLedger(isNanoLedger)
setShowLedger(wallet?.getIsNanoLedger())
setError(null)
}, [address])

Expand Down
2 changes: 1 addition & 1 deletion src/components/Grants.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ function Grants(props) {
case "/cosmos.staking.v1beta1.StakeAuthorization":
return (
<small>
Maximum: {maxTokens ? <Coins coins={maxTokens} asset={network.baseAsset} /> : 'unlimited'}<br />
Maximum: {maxTokens ? <Coins coins={maxTokens} asset={network.baseAsset} fullPrecision={true} hideValue={true} /> : 'unlimited'}<br />
Validators: {grant.authorization.allow_list.address.join(', ')}
</small>
)
Expand Down
21 changes: 16 additions & 5 deletions src/components/NetworkChecks.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@ import React from 'react';
import _ from 'lodash'
import { CheckCircle, XCircle, InfoCircle } from "react-bootstrap-icons";
import TooltipIcon from './TooltipIcon.js';
import Coingecko from '../assets/coingecko.png'

function NetworkChecks(props) {
const { network, error, skipConnected } = props
const baseAsset = network?.baseAsset
const price = baseAsset?.prices?.coingecko

function renderCheck({ title, failTitle, description, failDescription, state, successClass, failClass, identifier }) {
function renderCheck({ title, failTitle, description, failDescription, state, successClass, failClass, identifier, icon }) {
const className = state ? (successClass || 'success') : (failClass || 'warning')

const content = (
<div className="small">
{state ? (
{icon ? icon : state ? (
<CheckCircle className="me-2 mb-1" />
) : (
failClass === 'danger' ? <XCircle className="me-2 mb-1" /> : <InfoCircle className="me-2 mb-1" />
Expand Down Expand Up @@ -55,17 +58,25 @@ function NetworkChecks(props) {
return (
<ul className={className} style={props.style}>
{([
renderCheck({
title: <strong>{`$${price && price.usd.toLocaleString(undefined, { maximumFractionDigits: 8, minimumFractionDigits: 2 })}`}</strong>,
failTitle: 'Price Unknown',
state: price?.usd,
identifier: 'price',
icon: <img src={Coingecko} style={{width: '1em', height: '1em'}} className="me-2 mb-1" />,
failClass: 'success',
}),
renderCheck({
title: <strong>{`${Math.round(network.estimatedApr * 100).toLocaleString()}% APR`}</strong>,
failTitle: 'APR Unknown',
state: network.estimatedApr,
identifier: 'apr'
identifier: 'apr',
}),
renderCheck({
!skipConnected && renderCheck({
title: 'API connected',
failTitle: 'API offline',
failDescription: error,
state: (skipConnected ? network.online : network.connected) && !error,
state: network.connected && !error,
failClass: 'danger',
identifier: 'network'
}),
Expand Down
7 changes: 6 additions & 1 deletion src/components/NetworkSelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ function NetworkSelect(props) {
})
}, [props.networks, selectedNetwork])

const price = props.network?.baseAsset?.prices?.coingecko

return (
<>
<Button onClick={handleOpen} variant="link" className="d-flex flex-nowrap text-nowrap align-items-center text-reset text-decoration-none border-secondary btn-outline-light" role="button">
Expand All @@ -123,8 +125,11 @@ function NetworkSelect(props) {
<div className="avatar avatar-sm rounded-circle text-white">
<img alt={props.network.prettyName} src={props.network.image} height={30} width={30} />
</div>
<div className="d-none d-md-block ms-2">
<div className="d-none d-md-block mx-2">
<span className="h6">{props.network.prettyName}</span>
{!!price?.usd && (
<em className="text-muted small">&nbsp; ${price.usd.toLocaleString(undefined, { maximumFractionDigits: 4, minimumFractionDigits: 2 })}</em>
)}
</div>
<div className="d-none d-md-block ms-2">
{props.network.authzSupport
Expand Down
2 changes: 1 addition & 1 deletion src/components/RevokeGrant.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ function RevokeGrant(props) {
}

return (
<Dropdown.Item disabled={disabled()} onClick={() => revoke()} >
<Dropdown.Item as="button" disabled={disabled()} onClick={() => revoke()} >
{buttonText}
</Dropdown.Item>
)
Expand Down
4 changes: 2 additions & 2 deletions src/components/SendModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ function SendModal(props) {
</div>
{props.balance &&
<div className="form-text text-end"><span role="button" onClick={() => setAvailableAmount()}>
Available: <Coins coins={props.balance} asset={network.baseAsset} />
Available: <Coins coins={props.balance} asset={network.baseAsset} fullPrecision={true} hideValue={true} />
</span></div>
}
</div>
Expand All @@ -200,7 +200,7 @@ function SendModal(props) {
<p className="text-end">
{!loading
? (
<Button type="submit" className="btn btn-primary ms-2" disabled={!valid()}>Send {coinAmount() && <Coins coins={coinAmount()} asset={network.baseAsset} />}</Button>
<Button type="submit" className="btn btn-primary ms-2" disabled={!valid()}>Send {coinAmount() && <Coins coins={coinAmount()} asset={network.baseAsset} fullPrecision={true} hideValue={true} />}</Button>
)
: <Button className="btn btn-primary" type="button" disabled>
<span className="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>&nbsp;
Expand Down
2 changes: 1 addition & 1 deletion src/components/ValidatorDelegate.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ function ValidatorDelegate(props) {
<tr>
<td scope="row">Current Rewards</td>
<td>
<Coins coins={{ amount: props.rewards, denom: network.denom }} asset={network.baseAsset} fullPrecision={true} />
<Coins coins={props.rewards && { amount: props.rewards, denom: network.denom }} asset={network.baseAsset} fullPrecision={true} />
</td>
</tr>
{!!props.commission && (
Expand Down
8 changes: 4 additions & 4 deletions src/components/ValidatorGrants.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ function ValidatorGrants(props) {
return (
<>
<p className="small">{operator.moniker} will be able to carry out the following transactions on your behalf.</p>
<p className="small"><strong>Delegate</strong> - allowed to delegate <em>{maxTokensDenom() ? <Coins coins={{ amount: maxTokensDenom(), denom: network.denom }} asset={network.baseAsset} /> : 'any amount'}</em> to <em>{!state.validators ? 'any validator' : !state.validators.length || (state.validators.length === 1 && state.validators.includes(operator.address)) ? 'only their own validator' : 'validators ' + state.validators.join(', ')}</em>.</p>
<p className="small"><strong>Delegate</strong> - allowed to delegate <em>{maxTokensDenom() ? <Coins coins={{ amount: maxTokensDenom(), denom: network.denom }} asset={network.baseAsset} fullPrecision={true} hideValue={true} /> : 'any amount'}</em> to <em>{!state.validators ? 'any validator' : !state.validators.length || (state.validators.length === 1 && state.validators.includes(operator.address)) ? 'only their own validator' : 'validators ' + state.validators.join(', ')}</em>.</p>
<p className="small">This grant will expire automatically on <em>{state.expiryDateValue}</em>.</p>
<p className="small">REStake only re-delegates {operator.moniker}'s accrued rewards and tries not to touch your balance.</p>
<p className="small"><em>REStake previously required a Withdraw grant but this is no longer necessary.</em></p>
Expand Down Expand Up @@ -191,20 +191,20 @@ function ValidatorGrants(props) {
<tr>
<td scope="row">Minimum Reward</td>
<td>
<Coins coins={minimumReward()} asset={network.baseAsset} />
<Coins coins={minimumReward()} asset={network.baseAsset} fullPrecision={true} hideValue={true} />
</td>
</tr>
<tr>
<td scope="row">Current Rewards</td>
<td>
<Coins coins={{ amount: props.rewards, denom: network.denom }} asset={network.baseAsset} />
<Coins coins={{ amount: props.rewards, denom: network.denom }} asset={network.baseAsset} fullPrecision={true} />
</td>
</tr>
{state.maxTokens && (
<tr>
<td scope="row">Grant Remaining</td>
<td className={!props.rewards || larger(state.maxTokens, props.rewards) ? 'text-success' : 'text-danger'}>
<Coins coins={{ amount: state.maxTokens, denom: network.denom }} asset={network.baseAsset} />
<Coins coins={{ amount: state.maxTokens, denom: network.denom }} asset={network.baseAsset} fullPrecision={true} />
</td>
</tr>
)}
Expand Down
2 changes: 1 addition & 1 deletion src/components/ValidatorProfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ function ValidatorProfile(props) {
<td scope="row">REStake</td>
<td>
{!!operator ? (
<span className="p-0">{operator.runTimesString()} (<Coins coins={minimumReward()} asset={network.baseAsset} /> min)</span>
<span className="p-0">{operator.runTimesString()} (<Coins coins={minimumReward()} asset={network.baseAsset} fullPrecision={true} hideValue={true} /> min)</span>
) :
<TooltipIcon icon={<XCircle className="opacity-50 p-0" />} identifier={validator.operator_address} tooltip="This validator is not a REStake operator" />
}
Expand Down
Loading

0 comments on commit d2cb257

Please sign in to comment.