Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: redelegate #318

Merged
merged 8 commits into from
Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/fetch-extension/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,10 @@ ReactDOM.render(
path="/setting/chat/readRecipt"
element={<ReadRecipt />}
/>
<Route path="/validators" element={<ValidatorList />} />
<Route
path="/validators/:operation"
element={<ValidatorList />}
/>
<Route
path="/validators/:validator_address/:operation"
element={<Validator />}
Expand Down
2 changes: 1 addition & 1 deletion packages/fetch-extension/src/pages/main/stake.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ export const StakeView: FunctionComponent = observer(() => {
chainId: chainStore.current.chainId,
chainName: chainStore.current.chainName,
});
navigate("/validators");
navigate("/validators/validator");
}}
>
<FormattedMessage id="main.stake.button.stake" />
Expand Down
5 changes: 4 additions & 1 deletion packages/fetch-extension/src/pages/main/tx-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ export const TxButtonView: FunctionComponent = observer(() => {
<Link
to={
isStakableInApp
? "/validators"
? "/validators/validator"
: chainStore.current.walletUrlForStaking || ""
}
target={!isStakableInApp ? "_blank" : ""}
Expand Down Expand Up @@ -232,6 +232,9 @@ export const TxButtonView: FunctionComponent = observer(() => {
onMouseLeave={() => {
setIsActiveStake(false);
}}
data-loading={["undelegate", "redelegate", "delegate"].includes(
accountInfo.txTypeInProgress
)}
>
<img
src={isActiveStake ? activeStake : stake}
Expand Down
2 changes: 0 additions & 2 deletions packages/fetch-extension/src/pages/sign/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,6 @@ export const SignPage: FunctionComponent = observer(() => {
return memoConfig.error != null || feeConfig.error != null;
})();

console.log("@@@@#!3", signDocHelper.signDocWrapper?.isADR36SignDoc);

return (
<HeaderLayout
showChainName={alternativeTitle == null}
Expand Down
78 changes: 52 additions & 26 deletions packages/fetch-extension/src/pages/validator-list/index.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
import searchIcon from "@assets/icon/search.png";
import { Staking } from "@keplr-wallet/stores";
import { CoinPretty } from "@keplr-wallet/unit";
import { HeaderLayout } from "@layouts/header-layout";
import classnames from "classnames";
import { observer } from "mobx-react-lite";
import React, { FunctionComponent, useEffect, useState } from "react";
import { useNavigate } from "react-router";
import { useLocation, useNavigate } from "react-router";
import { useStore } from "../../stores";
import { MyValidatorsList } from "./my-validators";
import style from "./style.module.scss";
import { ValidatorCard } from "./validator-card";
import { CoinPretty } from "@keplr-wallet/unit";
import { ValidatorsList } from "./validators";

type ValidatorData = Staking.Validator & { amount: CoinPretty };

enum ValidatorOperation {
VALIDATOR = "validator",
MY_STAKE = "myStake",
}

export const ValidatorList: FunctionComponent = observer(() => {
const navigate = useNavigate();

const location = useLocation();
const operation = location.pathname.split("/")[2];
const [validators, setValidators] = useState<{
[key in string]: ValidatorData;
}>({});
Expand Down Expand Up @@ -62,10 +68,6 @@ export const ValidatorList: FunctionComponent = observer(() => {
fetchValidators();
}, [queries.cosmos.queryValidators, queryDelegations]);

const sortValidators = (a: ValidatorData, b: ValidatorData) => {
return parseFloat(b.delegator_shares) - parseFloat(a.delegator_shares);
};

const handleFilterValidators = (searchValue: string) => {
const filteredValidators = Object.values(validators).filter((validator) =>
searchValue?.trim().length
Expand All @@ -88,9 +90,39 @@ export const ValidatorList: FunctionComponent = observer(() => {
alternativeTitle="Stake"
onBackButton={() => navigate("/")}
>
<p className={classnames("h2", "my-0", "font-weight-normal")}>
Validators
</p>
<div className={style["tabList"]}>
<div
className={style["tab"]}
style={{
borderBottom:
operation == ValidatorOperation.VALIDATOR
? "2px solid #D43BF6"
: "",
color:
operation == ValidatorOperation.VALIDATOR ? "#D43BF6" : "#000000",
}}
onClick={() =>
navigate(`/validators/${ValidatorOperation.VALIDATOR}`)
}
>
Validators
</div>

<div
className={style["tab"]}
style={{
borderBottom:
operation == ValidatorOperation.MY_STAKE
? "2px solid #3B82F6"
: "",
color:
operation == ValidatorOperation.MY_STAKE ? "#3B82F6" : "#000000",
}}
onClick={() => navigate(`/validators/${ValidatorOperation.MY_STAKE}`)}
>
My Stake
</div>
</div>
<div className={style["searchContainer"]}>
<div className={style["searchBox"]}>
<img draggable={false} src={searchIcon} alt="search" />
Expand All @@ -102,8 +134,7 @@ export const ValidatorList: FunctionComponent = observer(() => {
/>
</div>
</div>

{loading ? (
{loading && (
<div
style={{
textAlign: "center",
Expand All @@ -118,18 +149,13 @@ export const ValidatorList: FunctionComponent = observer(() => {
<br />
Loading Validators
</div>
) : filteredValidators.length ? (
filteredValidators
.sort((a, b) => sortValidators(a, b))
.map((validator: ValidatorData) => (
<ValidatorCard
validator={validator}
chainID={chainStore.current.chainId}
key={validator.operator_address}
/>
))
) : (
<div style={{ textAlign: "center" }}>No Validators Found</div>
)}

{!loading && operation === ValidatorOperation.VALIDATOR && (
<ValidatorsList filteredValidators={filteredValidators} />
)}
{!loading && operation === ValidatorOperation.MY_STAKE && (
<MyValidatorsList filteredValidators={filteredValidators} />
)}
</HeaderLayout>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { ToolTip } from "@components/tooltip";
import { Staking } from "@keplr-wallet/stores";
import { CoinPretty } from "@keplr-wallet/unit";
import { formatAddress, shortenMintingNumber } from "@utils/format";
import React from "react";
import { useNavigate } from "react-router";
import { CHAIN_ID_DORADO, CHAIN_ID_FETCHHUB } from "../../../config.ui.var";
import styleValidators from "./validators.module.scss";

export const URL: { [key in string]: string } = {
[CHAIN_ID_DORADO]: "https://fetchstation.azoyalabs.com/dorado/validators",
[CHAIN_ID_FETCHHUB]: "https://fetchstation.azoyalabs.com/mainnet/validators",
};

export const MyValidatorCard = ({
validator,
chainID,
}: {
validator: Staking.Validator & { amount: CoinPretty };
chainID: string;
}) => {
const navigate = useNavigate();

const status = validator.status.split("_")[2].toLowerCase();
const commisionRate = (
parseFloat(validator.commission.commission_rates.rate) * 100
).toFixed(2);
return (
<div
className={styleValidators["item"]}
onClick={() =>
navigate(`/validators/${validator.operator_address}/stake`)
}
>
<div
className={styleValidators["row"]}
style={{ borderBottom: "1px solid lightgray" }}
>
<div className={styleValidators["label"]}>
{validator.description.moniker}
</div>
<ToolTip
trigger="hover"
options={{ placement: "bottom" }}
tooltip={
<div className={styleValidators["tooltip"]}>
{validator.operator_address}
</div>
}
>
<span className={styleValidators["address"]}>
{formatAddress(validator.operator_address)}
</span>
</ToolTip>
</div>
<div className={styleValidators["row"]}>
<div className={styleValidators["col"]}>
<span className={styleValidators["label"]}>Staked</span>
<span style={{ textTransform: "none" }}>
{shortenMintingNumber(validator.amount.toDec().toString(), 0)}
{validator.amount.currency.coinDenom}
</span>
</div>
<div className={styleValidators["col"]}>
<span className={styleValidators["label"]}>Commission</span>
<span>{commisionRate}%</span>
</div>
<div className={styleValidators["col"]}>
<span className={styleValidators["label"]}>Status</span>
<span>{status}</span>
</div>
</div>
<a
href={`${URL[chainID]}/${validator.operator_address}`}
target="_blank"
rel="noreferrer"
style={{ fontSize: "12px" }}
>
View in Explorer
</a>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
.col {
text-align: center;
display: flex;
flex-direction: column;
gap: 4px;
font-size: 12px;
text-transform: capitalize;
}

.row {
display: flex;
justify-content: space-between;
width: 100%;
}

.label {
font-weight: bold;
text-transform: capitalize;
}

.address {
font-style: italic;
font-size: 12px;
}

.avatar {
width: 80px;
height: 80px;
line-height: initial;
text-align: center;
color: rgb(255, 255, 255);
border-radius: 100%;
background: rgb(214, 26, 127);
}
.item {
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
padding: 10px;
background: #ffffff;
border-radius: 6px;
cursor: pointer;
margin: 10px 0px;
}

.item:hover {
box-shadow: 0 0 100px 30px #e0e0e0 inset;
}

.tooltip {
font-weight: 400;
font-size: 10px;
line-height: 10px;

padding: 2px 4px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Staking } from "@keplr-wallet/stores";
import { CoinPretty } from "@keplr-wallet/unit";
import React from "react";
import { useStore } from "../../../stores";
import { MyValidatorCard } from "../my-validator-card";

type ValidatorData = Staking.Validator & { amount: CoinPretty };

export const MyValidatorsList = ({
filteredValidators,
}: {
filteredValidators: ValidatorData[];
}) => {
const { chainStore } = useStore();

const filterValidators = (validator: ValidatorData) => {
return validator.amount
.toDec()
.gt(new CoinPretty(validator.amount.currency, "0").toDec());
};

const sortValidators = (a: ValidatorData, b: ValidatorData) => {
return (
parseFloat(b.amount.toDec().toString()) -
parseFloat(a.amount.toDec().toString())
);
};

return (
<React.Fragment>
{filteredValidators.length ? (
filteredValidators
.filter(filterValidators)
.sort(sortValidators)
.map((validator: ValidatorData) => (
<MyValidatorCard
validator={validator}
chainID={chainStore.current.chainId}
key={validator.operator_address}
/>
))
) : (
<div style={{ textAlign: "center" }}>No Validators Found</div>
)}
</React.Fragment>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,17 @@
animation: pathRect 2s cubic-bezier(0.785, 0.135, 0.15, 0.86) infinite;
}

.tabList {
display: flex;
flex-direction: row;
}

.tab {
width: 50%;
text-align: center;
cursor: pointer;
}

@keyframes pathRect {
25% {
stroke-dashoffset: 64;
Expand Down
Loading
Loading