Skip to content

Commit

Permalink
feat: add display of rewards
Browse files Browse the repository at this point in the history
  • Loading branch information
icfor committed Feb 27, 2024
1 parent 7a90de8 commit 5972ced
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 32 deletions.
63 changes: 42 additions & 21 deletions src/features/staking/components/logged-in.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,28 +84,49 @@ function StakingPage() {

return (
<div key={delegation.validatorAddress}>
<div>{formatCoin(delegation.amount)}</div>
<div>Delegated: {formatCoin(delegation.balance)}</div>
{delegation.rewards && (
<div>Rewards: {formatCoin(delegation.rewards)}</div>
)}
<div>{moniker || delegation.validatorAddress}</div>
<Button
onClick={() => {
setIsLoading(true);

if (!client) return;

setIsLoading(true);

const addresses: StakeAddresses = {
delegator: account.bech32Address,
validator: validator.operatorAddress,
};

unstakeValidator(addresses, client, staking).then(() => {
setIsLoading(false);
});
}}
>
Undelegate
</Button>
<div className="flex flex-row gap-4">
<Button
onClick={() => {
setIsLoading(true);

if (!client) return;

setIsLoading(true);

const addresses: StakeAddresses = {
delegator: account.bech32Address,
validator: validator.operatorAddress,
};

unstakeValidator(addresses, client, staking).then(() => {
setIsLoading(false);
});
}}
>
Undelegate
</Button>
{delegation.rewards && (
<Button
onClick={() => {
// @TODO
}}
>
Claim rewards
</Button>
)}
<Button
onClick={() => {
// @TODO
}}
>
Redelelegate
</Button>
</div>
</div>
);
})}
Expand Down
31 changes: 26 additions & 5 deletions src/features/staking/context/actions.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type { StakingContextType } from ".";
import { normaliseCoin } from "../lib/coins";
import type { SigningClient, StakeAddresses } from "../lib/core";
import {
getBalance,
getDelegations,
getRewards,
getValidatorsList,
stakeAmount,
unstakeAmount,
Expand All @@ -17,7 +19,29 @@ export const fetchStakingData = async (
const [balance, validators, delegations] = await Promise.all([
getBalance(address),
getValidatorsList(),
getDelegations(address),
getDelegations(address).then((newDelegations) =>
Promise.all(
newDelegations.delegationResponses.map(async (del) => ({
balance: del.balance,
rewards: await getRewards(
address,
del.delegation.validatorAddress,
).then((rewards) =>
rewards.reduce(
(acc, reward) => ({
amount: (
parseFloat(acc.amount) +
parseFloat(normaliseCoin(reward).amount)
).toString(),
denom: reward.denom,
}),
{ amount: "0", denom: "xion" },
),
),
validatorAddress: del.delegation.validatorAddress,
})),
),
),
]);

staking.dispatch(setTokens(balance));
Expand All @@ -29,10 +53,7 @@ export const fetchStakingData = async (
staking.dispatch(
addDelegations({
currentPage: 0,
items: delegations.delegationResponses.map((del) => ({
amount: del.balance,
validatorAddress: del.delegation.validatorAddress,
})),
items: delegations,
}),
);
} catch (error) {
Expand Down
3 changes: 2 additions & 1 deletion src/features/staking/context/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ type Paginated<T> = {
} | null;

type Delegation = {
amount: Coin;
balance: Coin;
rewards: Coin;
validatorAddress: string;
};

Expand Down
14 changes: 13 additions & 1 deletion src/features/staking/context/provider.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import type { PropsWithChildren } from "react";
import { useReducer } from "react";
import { useEffect, useReducer } from "react";

import { StakingContext, defaultState } from ".";
import { useStakingSync } from "./hooks";
Expand All @@ -14,9 +14,21 @@ export const Wrapper = ({ children }: PropsWithChildren) => {
return <>{children}</>;
};

declare global {
interface Window {
stakingContext: {
state: typeof defaultState;
};
}
}

export const StakingProvider = ({ children }: PropsWithChildren) => {
const [state, dispatch] = useReducer(reducer, defaultState);

useEffect(() => {
window.stakingContext = { state };
}, [state]);

return (
<StakingContext.Provider value={{ dispatch, state }}>
<Wrapper>{children}</Wrapper>
Expand Down
9 changes: 6 additions & 3 deletions src/features/staking/lib/coins.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Coin } from "@cosmjs/stargate";
import BigNumber from "bignumber.js";

const resolveCoin = (coin: Coin) => {
export const normaliseCoin = (coin: Coin) => {
if (coin.denom?.toUpperCase() === "UXION") {
const num = new BigNumber(coin.amount);

Expand All @@ -18,11 +18,14 @@ const resolveCoin = (coin: Coin) => {
};
}

return coin;
return {
...coin,
denom: coin.denom?.toUpperCase(),
};
};

export const formatCoin = (coin: Coin) => {
const resolved = resolveCoin(coin);
const resolved = normaliseCoin(coin);
const amount = new BigNumber(resolved.amount);

return `${amount.toFormat()} ${resolved.denom}`;
Expand Down
44 changes: 43 additions & 1 deletion src/features/staking/lib/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,18 @@ import type {
import {
QueryClient,
StargateClient,
setupDistributionExtension,
setupIbcExtension,
setupStakingExtension,
} from "@cosmjs/stargate";
import { Tendermint34Client } from "@cosmjs/tendermint-rpc";
import BigNumber from "bignumber.js";
import {
MsgDelegate,
MsgUndelegate,
} from "cosmjs-types/cosmos/staking/v1beta1/tx";

import { normaliseCoin } from "./coins";
import { rpcEndpoint } from "./constants";

export type SigningClient = NonNullable<
Expand All @@ -25,7 +29,12 @@ export type SigningClient = NonNullable<
const getStakingQueryClient = async () => {
const cometClient = await Tendermint34Client.connect(rpcEndpoint);

return QueryClient.withExtensions(cometClient, setupStakingExtension);
return QueryClient.withExtensions(
cometClient,
setupStakingExtension,
setupDistributionExtension,
setupIbcExtension,
);
};

export const getValidatorsList = async () => {
Expand All @@ -46,6 +55,39 @@ export const getDelegations = async (address: string) => {
return await queryClient.staking.delegatorDelegations(address);
};

export const getRewards = async (address: string, validatorAddress: string) => {
const queryClient = await getStakingQueryClient();

const rewards = await queryClient.distribution.delegationRewards(
address,
validatorAddress,
);

const rewardsData = await Promise.all(
rewards.rewards.map(async (reward) => {
if (reward.denom.indexOf("ibc/") == 0) {
const coin = await queryClient.ibc.transfer.denomTrace(
reward.denom.substring(4),
);

reward.denom = coin.denomTrace?.baseDenom || reward.denom;
}

return reward;
}),
);

return rewardsData
.filter((reward) => reward.denom?.toUpperCase() === "UXION")
.map((reward) => ({
amount: new BigNumber(reward.amount)
.div(new BigNumber(10).pow(18)) // Rewards with cosmjs have 18 decimal places
.toString(),
denom: reward.denom,
}))
.map((r) => normaliseCoin(r));
};

export type StakeAddresses = {
delegator: string;
validator: string;
Expand Down

0 comments on commit 5972ced

Please sign in to comment.