diff --git a/packages/fetch-extension/src/index.tsx b/packages/fetch-extension/src/index.tsx index 186bf25c6a..a60d3d5354 100644 --- a/packages/fetch-extension/src/index.tsx +++ b/packages/fetch-extension/src/index.tsx @@ -376,7 +376,10 @@ ReactDOM.render( path="/setting/chat/readRecipt" element={} /> - } /> + } + /> } diff --git a/packages/fetch-extension/src/pages/main/stake.tsx b/packages/fetch-extension/src/pages/main/stake.tsx index 339467e500..9fbdf769b3 100644 --- a/packages/fetch-extension/src/pages/main/stake.tsx +++ b/packages/fetch-extension/src/pages/main/stake.tsx @@ -211,7 +211,7 @@ export const StakeView: FunctionComponent = observer(() => { chainId: chainStore.current.chainId, chainName: chainStore.current.chainName, }); - navigate("/validators"); + navigate("/validators/validator"); }} > diff --git a/packages/fetch-extension/src/pages/main/tx-button.tsx b/packages/fetch-extension/src/pages/main/tx-button.tsx index 284a3f2c7f..1063d7726e 100644 --- a/packages/fetch-extension/src/pages/main/tx-button.tsx +++ b/packages/fetch-extension/src/pages/main/tx-button.tsx @@ -199,7 +199,7 @@ export const TxButtonView: FunctionComponent = observer(() => { { onMouseLeave={() => { setIsActiveStake(false); }} + data-loading={["undelegate", "redelegate", "delegate"].includes( + accountInfo.txTypeInProgress + )} > { return memoConfig.error != null || feeConfig.error != null; })(); - console.log("@@@@#!3", signDocHelper.signDocWrapper?.isADR36SignDoc); - return ( { const navigate = useNavigate(); - + const location = useLocation(); + const operation = location.pathname.split("/")[2]; const [validators, setValidators] = useState<{ [key in string]: ValidatorData; }>({}); @@ -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 @@ -88,9 +90,39 @@ export const ValidatorList: FunctionComponent = observer(() => { alternativeTitle="Stake" onBackButton={() => navigate("/")} > -

- Validators -

+
+
+ navigate(`/validators/${ValidatorOperation.VALIDATOR}`) + } + > + Validators +
+ +
navigate(`/validators/${ValidatorOperation.MY_STAKE}`)} + > + My Stake +
+
search @@ -102,8 +134,7 @@ export const ValidatorList: FunctionComponent = observer(() => { />
- - {loading ? ( + {loading && (
{
Loading Validators
- ) : filteredValidators.length ? ( - filteredValidators - .sort((a, b) => sortValidators(a, b)) - .map((validator: ValidatorData) => ( - - )) - ) : ( -
No Validators Found
+ )} + + {!loading && operation === ValidatorOperation.VALIDATOR && ( + + )} + {!loading && operation === ValidatorOperation.MY_STAKE && ( + )}
); diff --git a/packages/fetch-extension/src/pages/validator-list/my-validator-card/index.tsx b/packages/fetch-extension/src/pages/validator-list/my-validator-card/index.tsx new file mode 100644 index 0000000000..79c86ef9ff --- /dev/null +++ b/packages/fetch-extension/src/pages/validator-list/my-validator-card/index.tsx @@ -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 ( +
+ navigate(`/validators/${validator.operator_address}/stake`) + } + > +
+
+ {validator.description.moniker} +
+ + {validator.operator_address} +
+ } + > + + {formatAddress(validator.operator_address)} + + +
+
+
+ Staked + + {shortenMintingNumber(validator.amount.toDec().toString(), 0)} + {validator.amount.currency.coinDenom} + +
+
+ Commission + {commisionRate}% +
+
+ Status + {status} +
+
+ + View in Explorer + + + ); +}; diff --git a/packages/fetch-extension/src/pages/validator-list/my-validator-card/validators.module.scss b/packages/fetch-extension/src/pages/validator-list/my-validator-card/validators.module.scss new file mode 100644 index 0000000000..d848d5ee07 --- /dev/null +++ b/packages/fetch-extension/src/pages/validator-list/my-validator-card/validators.module.scss @@ -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; +} diff --git a/packages/fetch-extension/src/pages/validator-list/my-validators/index.tsx b/packages/fetch-extension/src/pages/validator-list/my-validators/index.tsx new file mode 100644 index 0000000000..362f33ba8d --- /dev/null +++ b/packages/fetch-extension/src/pages/validator-list/my-validators/index.tsx @@ -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 ( + + {filteredValidators.length ? ( + filteredValidators + .filter(filterValidators) + .sort(sortValidators) + .map((validator: ValidatorData) => ( + + )) + ) : ( +
No Validators Found
+ )} +
+ ); +}; diff --git a/packages/fetch-extension/src/pages/validator-list/style.module.scss b/packages/fetch-extension/src/pages/validator-list/style.module.scss index 570013b901..a6a464b2bf 100644 --- a/packages/fetch-extension/src/pages/validator-list/style.module.scss +++ b/packages/fetch-extension/src/pages/validator-list/style.module.scss @@ -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; diff --git a/packages/fetch-extension/src/pages/validator-list/validators/index.tsx b/packages/fetch-extension/src/pages/validator-list/validators/index.tsx new file mode 100644 index 0000000000..1b2832ea58 --- /dev/null +++ b/packages/fetch-extension/src/pages/validator-list/validators/index.tsx @@ -0,0 +1,37 @@ +import { Staking } from "@keplr-wallet/stores"; +import { CoinPretty } from "@keplr-wallet/unit"; +import React from "react"; +import { useStore } from "../../../stores"; +import { ValidatorCard } from "../validator-card"; + +type ValidatorData = Staking.Validator & { amount: CoinPretty }; + +export const ValidatorsList = ({ + filteredValidators, +}: { + filteredValidators: ValidatorData[]; +}) => { + const { chainStore } = useStore(); + + const sortValidators = (a: ValidatorData, b: ValidatorData) => { + return parseFloat(b.delegator_shares) - parseFloat(a.delegator_shares); + }; + + return ( + + {filteredValidators.length ? ( + filteredValidators + .sort((a, b) => sortValidators(a, b)) + .map((validator: ValidatorData) => ( + + )) + ) : ( +
No Validators Found
+ )} +
+ ); +}; diff --git a/packages/fetch-extension/src/pages/validator/index.tsx b/packages/fetch-extension/src/pages/validator/index.tsx index 5ef06cec70..b450caaf55 100644 --- a/packages/fetch-extension/src/pages/validator/index.tsx +++ b/packages/fetch-extension/src/pages/validator/index.tsx @@ -1,13 +1,20 @@ +import { Staking } from "@keplr-wallet/stores"; import { HeaderLayout } from "@layouts/header-layout"; +import { observer } from "mobx-react-lite"; import React, { FunctionComponent, useMemo } from "react"; import { useLocation, useNavigate } from "react-router"; -import style from "./style.module.scss"; -import { Staking } from "@keplr-wallet/stores"; import { useStore } from "../../stores"; -import { ValidatorDetails } from "./validator-details"; -import { observer } from "mobx-react-lite"; import { Stake } from "./stake"; +import style from "./style.module.scss"; +import { Transfer } from "./transfer"; import { Unstake } from "./unstake"; +import { ValidatorDetails } from "./validator-details"; + +enum ValidatorOperation { + STAKE = "stake", + UNSTAKE = "unstake", + TRANSFER = "transfer", +} export const Validator: FunctionComponent = observer(() => { const navigate = useNavigate(); @@ -60,8 +67,8 @@ export const Validator: FunctionComponent = observer(() => { navigate("/validators")} + alternativeTitle={operation.toLocaleUpperCase()} + onBackButton={() => navigate(-1)} >
{validator && ( @@ -86,8 +93,12 @@ export const Validator: FunctionComponent = observer(() => {
navigate(`/validators/${validatorAddress}/stake`)} > @@ -97,8 +108,14 @@ export const Validator: FunctionComponent = observer(() => {
navigate(`/validators/${validatorAddress}/unstake`) @@ -106,12 +123,44 @@ export const Validator: FunctionComponent = observer(() => { > Unstake
+
+ navigate(`/validators/${validatorAddress}/transfer`) + } + > + Transfer +
- {operation == "stake" ? ( + {operation == ValidatorOperation.STAKE && ( - ) : ( + )} + {operation == ValidatorOperation.UNSTAKE && ( )} + {operation == ValidatorOperation.TRANSFER && ( + validator.operator_address != validatorAddress + )} + /> + )}
diff --git a/packages/fetch-extension/src/pages/validator/stake-complete.tsx b/packages/fetch-extension/src/pages/validator/stake-complete.tsx index 9818e28e6b..d45a4262da 100644 --- a/packages/fetch-extension/src/pages/validator/stake-complete.tsx +++ b/packages/fetch-extension/src/pages/validator/stake-complete.tsx @@ -73,7 +73,7 @@ export const StakeComplete: FunctionComponent = observer(() => { + + + ); +}); diff --git a/packages/fetch-extension/src/pages/validator/unstake.tsx b/packages/fetch-extension/src/pages/validator/unstake.tsx index 0c878a03ef..a3dda5272b 100644 --- a/packages/fetch-extension/src/pages/validator/unstake.tsx +++ b/packages/fetch-extension/src/pages/validator/unstake.tsx @@ -70,42 +70,42 @@ export const Unstake: FunctionComponent<{ }, [intl, error]); const notification = useNotification(); + + const txnResult = { + onBroadcasted: () => { + notification.push({ + type: "primary", + placement: "top-center", + duration: 2, + content: `Transaction broadcasted`, + canDelete: true, + transition: { + duration: 0.25, + }, + }); + }, + onFulfill: (tx: any) => { + const istxnSuccess = tx.code ? false : true; + notification.push({ + type: istxnSuccess ? "success" : "danger", + placement: "top-center", + duration: 5, + content: istxnSuccess + ? `Transaction Completed` + : `Transaction Failed: ${tx.log}`, + canDelete: true, + transition: { + duration: 0.25, + }, + }); + }, + }; + const stakeClicked = async () => { try { - await account.cosmos.sendUndelegateMsg( - amountConfig.amount, - validatorAddress, - memoConfig.memo, - feeConfig.toStdFee(), - undefined, - { - onBroadcasted: () => { - notification.push({ - type: "primary", - placement: "top-center", - duration: 2, - content: `Transaction broadcasted`, - canDelete: true, - transition: { - duration: 0.25, - }, - }); - }, - onFulfill: () => { - notification.push({ - type: "success", - placement: "top-center", - duration: 5, - content: `Transaction Completed`, - canDelete: true, - transition: { - duration: 0.25, - }, - }); - navigate("/stake-complete/" + validatorAddress); - }, - } - ); + await account.cosmos + .makeUndelegateTx(amountConfig.amount, validatorAddress) + .send(feeConfig.toStdFee(), memoConfig.memo, undefined, txnResult); } catch (e) { notification.push({ type: "danger", @@ -117,6 +117,7 @@ export const Unstake: FunctionComponent<{ duration: 0.25, }, }); + } finally { navigate("/", { replace: true }); } }; @@ -132,7 +133,7 @@ export const Unstake: FunctionComponent<{ amountConfig.toggleIsMax(); }} - >{`Balance: ${balance.trim(true).maxDecimals(18).toString()}`} + >{`Staked: ${balance.trim(true).maxDecimals(6).toString()}`}