diff --git a/package-lock.json b/package-lock.json
index f33356b..d56ca5c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -40,6 +40,7 @@
"postcss": "^8.4.35",
"prettier": "^3.2.5",
"tailwindcss": "^3.4.1",
+ "ts-unused-exports": "^10.0.1",
"typescript": "^5.3.3"
}
},
@@ -9525,6 +9526,100 @@
"node": ">=8"
}
},
+ "node_modules/ts-unused-exports": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/ts-unused-exports/-/ts-unused-exports-10.0.1.tgz",
+ "integrity": "sha512-nWG8Y96pKem01Hw4j4+Mwuy+L0/9sKT7D61Q+OS3cii9ocQACuV6lu00B9qpiPhF4ReVWw3QYHDqV8+to2wbsg==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "tsconfig-paths": "^3.9.0"
+ },
+ "bin": {
+ "ts-unused-exports": "bin/ts-unused-exports"
+ },
+ "funding": {
+ "url": "https://github.com/pzavolinsky/ts-unused-exports?sponsor=1"
+ },
+ "peerDependencies": {
+ "typescript": ">=3.8.3"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": false
+ }
+ }
+ },
+ "node_modules/ts-unused-exports/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/ts-unused-exports/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/ts-unused-exports/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/ts-unused-exports/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/ts-unused-exports/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ts-unused-exports/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/tsconfig-paths": {
"version": "3.15.0",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
diff --git a/package.json b/package.json
index 298d088..6dc1413 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,7 @@
"scripts": {
"build": "next build",
"dev": "PORT=4000 next dev",
- "lint": "npm run lint:eslint && npm run lint:prettier",
+ "lint": "bash ./scripts/check_unused_exports.sh && npm run lint:eslint && npm run lint:prettier",
"lint:eslint": "eslint . --ext .ts,.tsx",
"lint:prettier": "prettier --check .",
"start": "next start",
@@ -45,6 +45,7 @@
"postcss": "^8.4.35",
"prettier": "^3.2.5",
"tailwindcss": "^3.4.1",
+ "ts-unused-exports": "^10.0.1",
"typescript": "^5.3.3"
}
}
diff --git a/scripts/check_unused_exports.sh b/scripts/check_unused_exports.sh
new file mode 100755
index 0000000..e5d43dc
--- /dev/null
+++ b/scripts/check_unused_exports.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+set -e
+
+./node_modules/.bin/ts-unused-exports tsconfig.json \
+ --excludePathsFromReport='next.config' \
+ --excludePathsFromReport='tailwind.config' \
+ --excludePathsFromReport='.*pages.*' \
+ --excludePathsFromReport='.*\.next\.*'
diff --git a/src/features/staking/components/main-page.tsx b/src/features/staking/components/main-page.tsx
index ae1fd3c..d6d234e 100644
--- a/src/features/staking/components/main-page.tsx
+++ b/src/features/staking/components/main-page.tsx
@@ -9,15 +9,15 @@ import { toast } from "react-toastify";
import {
claimRewardsAction,
- setRedelegateAction,
stakeValidatorAction,
unstakeValidatorAction,
} from "../context/actions";
import { useStaking } from "../context/hooks";
+import { getTotalDelegation } from "../context/selectors";
import type { StakingState } from "../context/state";
import type { StakeAddresses } from "../lib/core/base";
-import { formatCoin } from "../lib/core/coins";
import { chainId } from "../lib/core/constants";
+import { formatCoin } from "../lib/formatters";
import { keybaseClient } from "../lib/utils/keybase-client";
import DebugAccount from "./debug-account";
@@ -99,6 +99,8 @@ function StakingPage() {
[validators],
);
+ const totalDelegation = getTotalDelegation(staking.state);
+
return (
<>
@@ -120,6 +122,11 @@ function StakingPage() {
Tokens: {formatCoin(tokens)}
)}
+ {totalDelegation && (
+
+ Total delegation: {formatCoin(totalDelegation)}
+
+ )}
{isInfoLoading && Loading ...
}
{!!delegations?.items.length && (
@@ -198,24 +205,6 @@ function StakingPage() {
Claim rewards
)}
-
);
diff --git a/src/features/staking/components/validator-page.tsx b/src/features/staking/components/validator-page.tsx
index 1ae1b82..208b863 100644
--- a/src/features/staking/components/validator-page.tsx
+++ b/src/features/staking/components/validator-page.tsx
@@ -1,6 +1,5 @@
"use client";
-import BigNumber from "bignumber.js";
import { BondStatus } from "cosmjs-types/cosmos/staking/v1beta1/staking";
import Link from "next/link";
import { useSearchParams } from "next/navigation";
@@ -8,6 +7,8 @@ import { useEffect, useState } from "react";
import { getValidatorDetailsAction } from "../context/actions";
import { useStaking } from "../context/hooks";
+import { getVotingPowerPerc } from "../context/selectors";
+import { formatCommission, formatVotingPowerPerc } from "../lib/formatters";
import { keybaseClient } from "../lib/utils/keybase-client";
export default function ValidatorPage() {
@@ -15,8 +16,6 @@ export default function ValidatorPage() {
const address = searchParams.get("address");
const stakingRef = useStaking();
- const { pool } = stakingRef.staking.state;
-
const [logo, setLogo] = useState(null);
const [validatorDetails, setValidatorDetails] = useStateLoading ...;
}
- const parseCommissionRate = (commissionRate: string) => {
- const comission = new BigNumber(commissionRate)
- .div(new BigNumber(10).pow(18))
- .toNumber();
-
- return `${(comission * 100).toFixed(0)}%`;
- };
-
- const votingPowerPercentage = (() => {
- const validatorTokens = validatorDetails.tokens;
-
- if (!validatorTokens || typeof pool?.bondedTokens !== "string") {
- return null;
- }
-
- const perc = new BigNumber(validatorTokens)
- .div(new BigNumber(pool.bondedTokens))
- .toNumber();
-
- const percNum = perc < 0.0001 ? "<0.1" : (perc * 100).toFixed(1);
+ const votingPowerPerc = getVotingPowerPerc(
+ validatorDetails.tokens,
+ stakingRef.staking.state,
+ );
- return `${percNum}%`;
- })();
+ const votingPowerPercStr = formatVotingPowerPerc(votingPowerPerc);
return (
@@ -87,7 +69,17 @@ export default function ValidatorPage() {
{validatorDetails.description.website}
Commission:{" "}
- {parseCommissionRate(validatorDetails.commission.commissionRates.rate)}
+ {formatCommission(validatorDetails.commission.commissionRates.rate)}
+
+
+ Max commission:{" "}
+ {formatCommission(validatorDetails.commission.commissionRates.maxRate)}
+
+
+ Max commission change:{" "}
+ {formatCommission(
+ validatorDetails.commission.commissionRates.maxChangeRate,
+ )}
Jailed: {validatorDetails.jailed.toString()}
@@ -96,9 +88,7 @@ export default function ValidatorPage() {
? "Bonded"
: validatorDetails.status}
- {votingPowerPercentage && (
-
Voting Power: {votingPowerPercentage}
- )}
+ {votingPowerPercStr &&
Voting Power: {votingPowerPercStr}
}
Back
);
diff --git a/src/features/staking/context/actions.ts b/src/features/staking/context/actions.ts
index bfcdc95..8834378 100644
--- a/src/features/staking/context/actions.ts
+++ b/src/features/staking/context/actions.ts
@@ -156,7 +156,9 @@ export const claimRewardsAction = async (
await fetchStakingDataAction(addresses.delegator, staking);
};
-export const setRedelegateAction = async (
+// @TODO
+// eslint-disable-next-line
+const setRedelegateAction = async (
delegatorAddress: string,
client: AbstraxionSigningClient,
staking: StakingContextType,
diff --git a/src/features/staking/context/provider.tsx b/src/features/staking/context/provider.tsx
index 7e2d031..ea4e4b1 100644
--- a/src/features/staking/context/provider.tsx
+++ b/src/features/staking/context/provider.tsx
@@ -7,7 +7,7 @@ import { useStakingSync } from "./hooks";
import { reducer } from "./reducer";
import { StakingContext, defaultState } from "./state";
-export const Wrapper = ({ children }: PropsWithChildren) => {
+const Wrapper = ({ children }: PropsWithChildren) => {
useStakingSync();
// eslint-disable-next-line react/jsx-no-useless-fragment
diff --git a/src/features/staking/context/selectors.ts b/src/features/staking/context/selectors.ts
new file mode 100644
index 0000000..58effe0
--- /dev/null
+++ b/src/features/staking/context/selectors.ts
@@ -0,0 +1,31 @@
+import BigNumber from "bignumber.js";
+
+import { sumAllCoins } from "../lib/core/coins";
+import type { StakingState } from "./state";
+
+export const getTotalDelegation = (state: StakingState) => {
+ const { delegations } = state;
+
+ if (!delegations?.items.length) {
+ return null;
+ }
+
+ const delegationCoins = delegations.items.map((d) => d.balance);
+
+ return sumAllCoins(delegationCoins);
+};
+
+export const getVotingPowerPerc = (
+ validatorTokens: string,
+ state: StakingState,
+) => {
+ const { pool } = state;
+
+ if (!validatorTokens || typeof pool?.bondedTokens !== "string") {
+ return null;
+ }
+
+ return new BigNumber(validatorTokens)
+ .div(new BigNumber(pool.bondedTokens))
+ .toNumber();
+};
diff --git a/src/features/staking/lib/core/base.ts b/src/features/staking/lib/core/base.ts
index 67f324d..c46437c 100644
--- a/src/features/staking/lib/core/base.ts
+++ b/src/features/staking/lib/core/base.ts
@@ -64,6 +64,9 @@ export const getValidatorDetails = async (address: string) => {
return promise;
};
+// @TODO: This returns the unbonding time
+// const params = await queryClient.staking.params();
+
let poolRequest: null | Promise = null;
export const getPool = async () => {
@@ -210,15 +213,6 @@ export const unstakeAmount = async (
.catch(handleTxError);
};
-export const getUnbonding = async (
- address: string,
- validatorAddress: string,
-) => {
- const queryClient = await getStakingQueryClient();
-
- return queryClient.staking.unbondingDelegation(address, validatorAddress);
-};
-
export const claimRewards = async (
addresses: StakeAddresses,
client: NonNullable,
diff --git a/src/features/staking/lib/core/coins.ts b/src/features/staking/lib/core/coins.ts
index 1a5ac7c..97137f7 100644
--- a/src/features/staking/lib/core/coins.ts
+++ b/src/features/staking/lib/core/coins.ts
@@ -24,23 +24,15 @@ export const normaliseCoin = (coin: Coin) => {
};
};
-export const formatCoin = (coin: Coin) => {
- const resolved = normaliseCoin(coin);
- const amount = new BigNumber(resolved.amount);
-
- return `${amount.toFormat()} ${resolved.denom}`;
-};
-
-export const getEmptyXionCoin = () =>
- ({ amount: "0", denom: "xion" }) satisfies Coin;
+const getEmptyXionCoin = () => ({ amount: "0", denom: "xion" }) satisfies Coin;
export const sumAllCoins = (coins: Coin[]) =>
coins.reduce(
(acc, coin) => ({
- amount: (
- parseFloat(acc.amount) + parseFloat(normaliseCoin(coin).amount)
- ).toString(),
- denom: coin.denom,
+ amount: new BigNumber(acc.amount)
+ .plus(normaliseCoin(coin).amount)
+ .toString(),
+ denom: acc.denom,
}),
getEmptyXionCoin(),
);
diff --git a/src/features/staking/lib/formatters.ts b/src/features/staking/lib/formatters.ts
new file mode 100644
index 0000000..fedc0c5
--- /dev/null
+++ b/src/features/staking/lib/formatters.ts
@@ -0,0 +1,29 @@
+import type { Coin } from "@cosmjs/stargate";
+import BigNumber from "bignumber.js";
+
+import { normaliseCoin } from "./core/coins";
+
+export const formatCoin = (coin: Coin) => {
+ const resolved = normaliseCoin(coin);
+ const amount = new BigNumber(resolved.amount);
+
+ return `${amount.toFormat()} ${resolved.denom}`;
+};
+
+export const formatVotingPowerPerc = (perc: null | number) => {
+ if (typeof perc !== "number" || Number.isNaN(perc)) {
+ return null;
+ }
+
+ const percNum = perc < 0.0001 ? "<0.1" : (perc * 100).toFixed(1);
+
+ return `${percNum}%`;
+};
+
+export const formatCommission = (commissionRate: string) => {
+ const comission = new BigNumber(commissionRate)
+ .div(new BigNumber(10).pow(18))
+ .toNumber();
+
+ return `${(comission * 100).toFixed(0)}%`;
+};