Skip to content

Commit

Permalink
Merge pull request #141 from eco-stake/multiple-assets
Browse files Browse the repository at this point in the history
Support multiple assets
  • Loading branch information
tombeynon authored Feb 23, 2025
2 parents ec33290 + a632959 commit 60eac15
Show file tree
Hide file tree
Showing 27 changed files with 703 additions and 588 deletions.
18 changes: 0 additions & 18 deletions src/assets/skip-white.svg

This file was deleted.

46 changes: 0 additions & 46 deletions src/assets/skip.svg

This file was deleted.

21 changes: 12 additions & 9 deletions src/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import _ from 'lodash'
import AlertMessage from './AlertMessage'
import NetworkSelect from './NetworkSelect'
import Delegations from './Delegations';
import Coins from './Coins'
import Coin from './Coin'
import About from './About'

import {
Expand All @@ -21,7 +21,7 @@ import {
DropletFill,
DropletHalf,
CashCoin,
Coin,
Coin as CoinIcon,
EnvelopePaper,
Stars,
WrenchAdjustableCircle,
Expand Down Expand Up @@ -298,7 +298,8 @@ class App extends React.Component {
return this.restClient().getBalance(this.state.address)
.then(
(balances) => {
const balance = balances?.find(
balances = _.compact(balances || [])
const balance = balances.find(
(element) => element.denom === this.props.network.denom
) || { denom: this.props.network.denom, amount: 0 };
this.setState({
Expand Down Expand Up @@ -585,7 +586,7 @@ class App extends React.Component {
<>
<div className="nav-item px-2 border-end text-center">
<Nav.Link eventKey="delegations">
<Coin className="mb-1 me-1" /><span className="d-none d-sm-inline"> Stake</span>
<CoinIcon className="mb-1 me-1" /><span className="d-none d-sm-inline"> Stake</span>
</Nav.Link>
</div>
<div className="nav-item px-2 border-end text-center">
Expand Down Expand Up @@ -682,10 +683,11 @@ class App extends React.Component {
<li className="nav-item px-3 border-end align-items-center d-none d-md-flex">
<div role="button" onClick={() => this.showWalletModal({activeTab: this.state.wallet ? 'wallet' : 'saved'})}>
{this.state.balance ? (
<Coins
coins={this.state.balance}
<Coin
{...this.state.balance}
asset={this.props.network.baseAsset}
className="small text-end"
showImage={false}
/>
) : (
<Spinner animation="border" role="status" className="spinner-border-sm text-secondary">
Expand All @@ -709,10 +711,11 @@ class App extends React.Component {
<div className="d-block d-md-none">
<Dropdown.Header className="text-truncate">{this.addressName()}</Dropdown.Header>
<Dropdown.Item as="button" onClick={() => this.showWalletModal({activeTab: this.state.wallet ? 'wallet' : 'saved'})}>
<Coins
coins={this.state.balance}
<Coin
{...this.state.balance}
asset={this.props.network.baseAsset}
className="small"
showImage={false}
/>
</Dropdown.Item>
<Dropdown.Divider />
Expand Down Expand Up @@ -876,7 +879,7 @@ class App extends React.Component {
network={this.props.network}
address={this.state.address}
wallet={this.state.wallet}
balance={this.state.balance}
balances={this.state.balances}
favouriteAddresses={this.favouriteAddresses()}
onHide={() => this.setState({ showSendModal: false })}
onSend={this.onSend}
Expand Down
86 changes: 22 additions & 64 deletions src/components/ClaimRewards.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import {
Button
} from 'react-bootstrap'

import { add, subtract, multiply, divide, bignumber } from 'mathjs'
import { MsgDelegate } from "../messages/MsgDelegate.mjs";
import { MsgWithdrawDelegatorReward } from "../messages/MsgWithdrawDelegatorReward.mjs";
import { MsgWithdrawValidatorCommission } from "../messages/MsgWithdrawValidatorCommission.mjs";
import { smallerEq } from "mathjs";

function ClaimRewards(props) {
const { network, address, wallet, rewards } = props
Expand All @@ -17,47 +17,16 @@ function ClaimRewards(props) {
props.setError()
props.setLoading(true)

const validatorRewards = mapRewards()
const gasSimMessages = buildMessages(validatorRewards)

let gas
try {
gas = await wallet.simulate(gasSimMessages)
} catch (error) {
props.setLoading(false)
props.setError('Failed to broadcast: ' + error.message)
return
}

const fee = wallet.getFee(gas)
const feeAmount = fee.amount[0].amount

const totalReward = validatorRewards.reduce((sum, validatorReward) => add(sum, bignumber(validatorReward.reward)), 0);
const adjustedValidatorRewards = validatorRewards.map(validatorReward => {
const shareOfFee = multiply(divide(bignumber(validatorReward.reward), totalReward), feeAmount); // To take a proportional amount from each validator relative to total reward
return {
validatorAddress: validatorReward.validatorAddress,
reward: subtract(validatorReward.reward, shareOfFee),
}
})

if(!props.commission && (adjustedValidatorRewards.length < 1 || adjustedValidatorRewards.some(validatorReward => validatorReward.reward <= 0))) {
props.setLoading(false)
props.setError('Reward is too low')
return
}

let messages = buildMessages(adjustedValidatorRewards)
let messages
try {
gas = gas || await wallet.simulate(messages)
messages = await buildMessages()
} catch (error) {
props.setLoading(false)
props.setError('Failed to broadcast: ' + error.message)
props.setError(error.message)
return
}
console.log(messages, gas)

wallet.signAndBroadcastWithoutBalanceCheck(messages, gas).then((result) => {
wallet.signAndBroadcastWithoutBalanceCheck(messages).then((result) => {
console.log("Successfully broadcasted:", result);
props.setLoading(false)
props.onClaimRewards(result)
Expand All @@ -68,47 +37,36 @@ function ClaimRewards(props) {
})
}

function mapRewards() {
if (!rewards) return [];

const validatorRewards = rewards
.map(reward => {
return {
validatorAddress: reward.validator_address,
reward: rewardAmount(reward, network.denom),
}
})
.filter(validatorReward => validatorReward.reward );

return validatorRewards;
}

// Expects a map of string -> string (validator -> reward)
function buildMessages(validatorRewards){
return validatorRewards.map(validatorReward => {
function buildMessages(){
const messages = rewards.map(validatorRewards => {
let valMessages = []

if(props.restake){
const denomReward = rewardAmount(validatorRewards, network.denom)
if(smallerEq(denomReward, 0)){
throw new Error(`You have no ${network.symbol} rewards to compound`)
}
valMessages.push(new MsgDelegate({
delegatorAddress: address,
validatorAddress: validatorReward.validatorAddress,
amount: coin(validatorReward.reward, network.denom)
validatorAddress: validatorRewards.validator_address,
amount: coin(denomReward, network.denom)
}))
}else{
valMessages.push(new MsgWithdrawDelegatorReward({
delegatorAddress: address,
validatorAddress: validatorReward.validatorAddress
validatorAddress: validatorRewards.validator_address
}))
}

if (props.commission) {
valMessages.push(new MsgWithdrawValidatorCommission({
validatorAddress: validatorReward.validatorAddress
}))
if (props.commission) {
valMessages.push(new MsgWithdrawValidatorCommission({
validatorAddress: validatorRewards.validator_address
}))
}
}

return execableMessage(valMessages, wallet.address, address)
return valMessages
}).flat()

return execableMessage(messages, wallet.address, address)
}

function hasPermission(){
Expand Down
68 changes: 68 additions & 0 deletions src/components/Coin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import _ from 'lodash'
import { divide, bignumber, round, format } from 'mathjs'
import { truncateDenom } from '../utils/Helpers.mjs'

function Coin(props) {
const { amount, denom, asset, fullPrecision, showValue = true, showImage = true, className } = props
let { decimals, symbol, prices } = asset || {}
const { coingecko } = prices || {}
symbol = symbol || (denom && truncateDenom(denom?.toUpperCase()))

function decimalAmount(){
if(decimals){
return round(divide(bignumber(amount), Math.pow(10, decimals)), precision())
}else{
return round(bignumber(amount), 0)
}
}

function formattedAmount(){
return separator(format(decimalAmount(), {notation: 'fixed'}))
}

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

function separator(stringNum) {
var str = stringNum.split(".");
str[0] = str[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
return str.join(".");
}

function precision(){
if(fullPrecision) return decimals;
if(props.precision) return props.precision;
if(amount >= (1000 * Math.pow(10, decimals))) return 2
if(amount >= (100 * Math.pow(10, decimals))) return 3
return 6
}

if(!denom){
return null
}

const classNames = ['coins', className]
if(showImage){
classNames.push('d-inline-block align-top')
}

return (
<span className={_.compact(classNames).join(' ')}>
<span className={showImage ? `d-flex align-items-center` : undefined} title={!showValue && !!coingecko?.usd && !!amount ? `$${value()}` : ''}>
{showImage && asset?.image && (
<img src={asset.image} height={15} className={`rounded-circle me-1 image`} />
)}
<span className="amount">{formattedAmount()}</span>&nbsp;
<small className="denom">{symbol}</small>
</span>
{showValue && !!coingecko?.usd && !!amount && (
<div>
<span className="value"><em className="text-muted">${value()}</em></span>
</div>
)}
</span>
)
}

export default Coin;
Loading

0 comments on commit 60eac15

Please sign in to comment.