-
- [{pool.ticker}] {pool.name}
+
+ [{pool.ticker}] {pool.name}{" "}
+
+ {loading ? "delegating" : "delegate"}
+ {loading ? : null}
+
{pool.description}
diff --git a/client/src/entities/dto.ts b/client/src/entities/dto.ts
index eb04c74b..aa416850 100644
--- a/client/src/entities/dto.ts
+++ b/client/src/entities/dto.ts
@@ -1,5 +1,25 @@
import { DeliveredReward } from "./common.entities";
-import { PoolInfo } from "./vm.entities";
+import { Assets, ClaimableToken, PoolInfo, VmPoolInfo } from "./vm.entities";
+
+export interface GetRewardsDto {
+ claimable_tokens: ClaimableToken[];
+ pool_info: VmPoolInfo;
+ total_rewards?: number;
+ consolidated_promises?: Assets;
+ consolidated_rewards?: Assets;
+ nfts?: any[];
+ assets?: Assets;
+ min_balance?: number;
+ vending_address?: string;
+ withdrawal_fee?: string;
+ withdraw_all_tokens_deposit?: number;
+ project_locked_rewards?: {
+ consolidated_promises: Assets;
+ consolidated_rewards: Assets;
+ nfts: any[];
+ assets: Assets;
+ };
+}
export interface GetQueueDto {
pending_tx: number;
@@ -17,3 +37,24 @@ export interface GetDeliveredRewardsDto {
export interface ServerErrorDto {
error: string;
}
+
+export namespace StakeTxDto {
+ export interface GetTxRequest {
+ poolId: string;
+ address: string;
+ }
+
+ export interface GetTxResponse {
+ witness: string;
+ txBody: string;
+ }
+
+ export interface PostSignedTxRequest {
+ signedWitness: string;
+ txBody: string;
+ }
+
+ export interface PostSignedTxResponse {
+ tx: string;
+ }
+}
diff --git a/client/src/entities/vm.entities.ts b/client/src/entities/vm.entities.ts
index 18b4d2e0..dc516a62 100644
--- a/client/src/entities/vm.entities.ts
+++ b/client/src/entities/vm.entities.ts
@@ -1,12 +1,6 @@
export interface GetRewardsDto {
claimable_tokens: ClaimableToken[];
- pool_info: {
- delegated_pool_name: string;
- delegated_pool_description: string;
- total_balance: string;
- delegated_pool_ticker: string;
- delegated_pool_logo: string;
- };
+ pool_info: VmPoolInfo;
total_rewards?: number;
consolidated_promises?: Assets;
consolidated_rewards?: Assets;
@@ -24,6 +18,15 @@ export interface GetRewardsDto {
};
}
+export interface VmPoolInfo {
+ delegated_pool_name: string;
+ delegated_pool_description: string;
+ total_balance: string;
+ delegated_pool_ticker: string;
+ delegated_pool_logo: string;
+ isWhitelisted: boolean;
+}
+
export interface GetCustomRewards {
request_id: string;
deposit: number;
diff --git a/client/src/hooks/cardano/claim/useClaimReward.tsx b/client/src/hooks/cardano/claim/useClaimReward.tsx
new file mode 100644
index 00000000..afe19015
--- /dev/null
+++ b/client/src/hooks/cardano/claim/useClaimReward.tsx
@@ -0,0 +1,194 @@
+import { useSelector } from "react-redux";
+
+import { RootState } from "src/store";
+
+import { useEffect, useState } from "react";
+import { useDispatch } from "react-redux";
+import { useNavigate } from "react-router-dom";
+
+import {
+ InfoModalTypes,
+ ModalTypes,
+ PageRoute,
+} from "src/entities/common.entities";
+import { ClaimableToken, VmPoolInfo } from "src/entities/vm.entities";
+import useErrorHandler from "src/hooks/useErrorHandler";
+import { showModal } from "src/reducers/globalSlice";
+import { getCustomRewards, getRewards } from "src/services/claim";
+import { getStakeKey } from "src/services/common";
+
+export default function useClaimReward() {
+ const dispatch = useDispatch();
+ const navigate = useNavigate();
+ const { handleError } = useErrorHandler();
+ const connectedWallet = useSelector(
+ (state: RootState) => state.wallet.walletApi
+ );
+ const isWrongNetwork = useSelector(
+ (state: RootState) => state.wallet.isWrongNetwork
+ );
+
+ const [searchAddress, setSearchAddress] = useState("");
+ const [claimableTokens, setClaimableTokens] = useState
([]);
+ const [poolInfo, setPoolInfo] = useState(null);
+ const [isCheckRewardLoading, setIsCheckRewardLoading] = useState(false);
+ const [isClaimRewardLoading, setIsClaimRewardLoading] = useState(false);
+ const [stakeAddress, setStakeAddress] = useState("");
+ const [numberOfSelectedTokens, setNumberOfSelectedTokens] = useState(0);
+
+ useEffect(() => {
+ async function init() {
+ if (connectedWallet?.wallet?.api && !isWrongNetwork) {
+ setSearchAddress(await connectedWallet.getAddress());
+ }
+ }
+ init();
+ }, [connectedWallet?.wallet?.api, connectedWallet, isWrongNetwork]);
+
+ useEffect(() => {
+ setNumberOfSelectedTokens(
+ claimableTokens.reduce((agg, i) => {
+ if (i.selected) {
+ agg += 1;
+ }
+ return agg;
+ }, 0)
+ );
+ }, [claimableTokens]);
+
+ const selectAllClaimableTokens = () => {
+ const updatedClaimableTokens = [...claimableTokens];
+ if (numberOfSelectedTokens < claimableTokens.length) {
+ updatedClaimableTokens.forEach((token) => (token.selected = true));
+ } else {
+ updatedClaimableTokens.forEach((token) => (token.selected = false));
+ }
+ setClaimableTokens(updatedClaimableTokens);
+ };
+
+ const handleTokenSelect = (position: number) => {
+ const updatedClaimableTokens = [...claimableTokens];
+ updatedClaimableTokens[position].selected =
+ !updatedClaimableTokens[position].selected;
+ setClaimableTokens(updatedClaimableTokens);
+ };
+
+ const selectAll = () => {
+ const updatedClaimableTokens = [...claimableTokens];
+ if (numberOfSelectedTokens < claimableTokens.length) {
+ updatedClaimableTokens.forEach((token) => (token.selected = true));
+ } else {
+ updatedClaimableTokens.forEach((token) => (token.selected = false));
+ }
+ setClaimableTokens(updatedClaimableTokens);
+ };
+
+ const checkRewards = async () => {
+ setIsCheckRewardLoading(true);
+ try {
+ /**
+ * check if the inserted address is cardano address, we want the stake address
+ * if it is cardano address, get the staking address
+ */
+ let address = await getStakeKey(searchAddress);
+
+ address = address.staking_address;
+
+ setStakeAddress(address);
+ const getRewardsDto = await getRewards(address);
+ if (getRewardsDto == null) {
+ throw new Error("Something went wrong when checking reward");
+ }
+ if (getRewardsDto.claimable_tokens.length !== 0) {
+ setClaimableTokens(
+ getRewardsDto.claimable_tokens
+ .map((token) => {
+ token.selected = false;
+ return token;
+ })
+ .sort((a, b) => {
+ if (a.premium === b.premium) {
+ if (a.ticker < b.ticker) {
+ return -1;
+ } else {
+ return 1;
+ }
+ } else {
+ return a.premium ? -1 : 1;
+ }
+ })
+ );
+ setPoolInfo(getRewardsDto.pool_info);
+ setIsCheckRewardLoading(false);
+ } else {
+ dispatch(
+ showModal({
+ modalType: ModalTypes.info,
+ details: {
+ text: "No rewards found for the account, yet.",
+ type: InfoModalTypes.info,
+ },
+ })
+ );
+ }
+ } catch (e: any) {
+ handleError(e);
+ } finally {
+ setIsCheckRewardLoading(false);
+ }
+ };
+
+ const claimRewards = async () => {
+ if (numberOfSelectedTokens === 0) return;
+
+ setIsClaimRewardLoading(true);
+ let selectedPremiumToken = false;
+
+ const selectedTokenId: string[] = [];
+ claimableTokens.forEach((token) => {
+ if (token.selected) {
+ if (token.premium) {
+ selectedPremiumToken = true;
+ }
+ selectedTokenId.push(token.assetId);
+ }
+ });
+
+ try {
+ const res = await getCustomRewards(
+ stakeAddress,
+ stakeAddress.slice(0, 40),
+ selectedTokenId.join(","),
+ selectedPremiumToken
+ );
+ if (res == null) throw new Error();
+
+ let depositInfoUrl = `${PageRoute.depositCardano}?stakeAddress=${stakeAddress}&withdrawAddress=${res.withdrawal_address}&requestId=${res.request_id}&selectedTokens=${numberOfSelectedTokens}&unlock=${selectedPremiumToken}&isWhitelisted=${res.is_whitelisted}`;
+ navigate(depositInfoUrl, { replace: true });
+ } catch (e) {
+ handleError(e);
+ } finally {
+ setIsClaimRewardLoading(false);
+ }
+ };
+
+ const cancelClaim = async () => {
+ setClaimableTokens([]);
+ };
+
+ return {
+ searchAddress,
+ setSearchAddress,
+ checkRewards,
+ claimRewards,
+ selectAll,
+ cancelClaim,
+ claimableTokens,
+ selectAllClaimableTokens,
+ handleTokenSelect,
+ numberOfSelectedTokens,
+ isCheckRewardLoading,
+ isClaimRewardLoading,
+ poolInfo,
+ };
+}
diff --git a/client/src/hooks/cardano/useStakeToPool.tsx b/client/src/hooks/cardano/useStakeToPool.tsx
new file mode 100644
index 00000000..9544f9fa
--- /dev/null
+++ b/client/src/hooks/cardano/useStakeToPool.tsx
@@ -0,0 +1,39 @@
+import { useState } from "react";
+import { useSelector } from "react-redux";
+import { createStakeTx, submitStakeTx } from "src/services/common";
+import { RootState } from "src/store";
+import useErrorHandler from "../useErrorHandler";
+
+export default function useStakeToPool() {
+ const [loading, setLoading] = useState(false);
+ const { handleError } = useErrorHandler();
+ const connectedWallet = useSelector(
+ (state: RootState) => state.wallet.walletApi
+ );
+
+ async function stakeToPool(poolId: string, callback?: () => void) {
+ setLoading(true);
+ try {
+ if (connectedWallet == null) {
+ throw new Error("Please connect your wallet to delegate");
+ }
+ const address = await connectedWallet.getBech32Address();
+ const { witness, txBody } = await createStakeTx({ poolId, address });
+ const signedWitness = await connectedWallet.signTx(witness);
+ const { tx } = await submitStakeTx({ signedWitness, txBody });
+ await connectedWallet.wallet?.api.submitTx(tx);
+ if (callback != null) {
+ callback();
+ }
+ } catch (error) {
+ handleError(error);
+ } finally {
+ setLoading(false);
+ }
+ }
+
+ return {
+ stakeToPool,
+ loading,
+ };
+}
diff --git a/client/src/hooks/useWallet.tsx b/client/src/hooks/useWallet.tsx
index 6cd149a5..ccc57fba 100644
--- a/client/src/hooks/useWallet.tsx
+++ b/client/src/hooks/useWallet.tsx
@@ -27,7 +27,7 @@ const useWallet = () => {
const walletApi = await getWalletApi();
if (!walletKey) {
- dispatch(connectWalletRedux(walletApi));
+ dispatch(connectWalletRedux(undefined));
dispatch(setIsWrongNetwork(false));
localStorage.removeItem("wallet-provider");
return;
diff --git a/client/src/pages/Cardano/Claim/index.tsx b/client/src/pages/Cardano/Claim/index.tsx
index f8b03987..3cc113fc 100644
--- a/client/src/pages/Cardano/Claim/index.tsx
+++ b/client/src/pages/Cardano/Claim/index.tsx
@@ -1,356 +1,25 @@
-import { faStar } from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { KeyboardEvent, useCallback, useEffect, useState } from "react";
-import { useDispatch, useSelector } from "react-redux";
-import { useNavigate } from "react-router-dom";
-
-import Spinner from "src/components/Spinner";
-import {
- InfoModalTypes,
- ModalTypes,
- PageRoute,
-} from "src/entities/common.entities";
-import { ClaimableToken } from "src/entities/vm.entities";
-import { showModal } from "src/reducers/globalSlice";
-import { getCustomRewards, getRewards } from "src/services/claim";
-import { RootState } from "src/store";
-
-import ClaimableTokenBox from "src/components/Claim/ClaimableTokenBox";
+import CheckRewardInput from "src/components/Claim/CheckRewardInput";
+import RewardsView from "src/components/Claim/RewardsView";
+import useClaimReward from "src/hooks/cardano/claim/useClaimReward";
import { useQueue } from "src/hooks/cardano/claim/useQueue";
-import useErrorHandler from "src/hooks/useErrorHandler";
-import { getStakeKey } from "src/services/common";
function Claim() {
- const dispatch = useDispatch();
- const navigate = useNavigate();
- const connectedWallet = useSelector(
- (state: RootState) => state.wallet.walletApi
- );
- const { handleError } = useErrorHandler();
-
- const isWrongNetwork = useSelector(
- (state: RootState) => state.wallet.isWrongNetwork
- );
- const [hideCheck, setHideCheck] = useState(false);
- const [hideStakingInfo, setHideStakingInfo] = useState(true);
-
- const [claimableTokens, setClaimableTokens] = useState([]);
- const [poolInfo, setPoolInfo] = useState(null);
- const [numberOfSelectedTokens, setNumberOfSelectedTokens] = useState(0);
-
- const [searchAddress, setSearchAddress] = useState("");
- const [rewardsLoader, setRewardsLoader] = useState(false);
- const [stakeAddress, setStakeAddress] = useState("");
- const [claimMyRewardLoading, setClaimMyRewardLoading] =
- useState(false);
+ const {
+ searchAddress,
+ setSearchAddress,
+ checkRewards,
+ isCheckRewardLoading,
+ isClaimRewardLoading,
+ selectAll,
+ cancelClaim,
+ claimableTokens,
+ handleTokenSelect,
+ numberOfSelectedTokens,
+ claimRewards,
+ poolInfo,
+ } = useClaimReward();
const queue = useQueue();
- useEffect(() => {
- if (claimableTokens.length) {
- setHideStakingInfo(false);
- } else {
- setHideStakingInfo(true);
- }
- }, [claimableTokens]);
-
- useEffect(() => {
- async function init() {
- if (connectedWallet?.wallet?.api && !isWrongNetwork) {
- setSearchAddress(await connectedWallet.getAddress());
- setHideCheck(false);
- setHideStakingInfo(true);
- }
- }
-
- init();
- }, [connectedWallet?.wallet?.api, connectedWallet, isWrongNetwork]);
-
- const getNumberOfSelectedTokens = useCallback(() => {
- return claimableTokens.reduce((prev, token) => {
- if (token.selected) {
- prev += 1;
- }
- return prev;
- }, 0);
- }, [claimableTokens]);
-
- /**
- * select/unselect all tokens
- */
- const selectAll = () => {
- const updatedClaimableTokens = [...claimableTokens];
- if (numberOfSelectedTokens < claimableTokens.length) {
- updatedClaimableTokens.forEach((token) => (token.selected = true));
- } else {
- updatedClaimableTokens.forEach((token) => (token.selected = false));
- }
- setClaimableTokens(updatedClaimableTokens);
- setNumberOfSelectedTokens(getNumberOfSelectedTokens());
- };
-
- /**
- * handle token select
- */
- const handleTokenSelect = (position: number) => {
- const updatedClaimableTokens = [...claimableTokens];
- updatedClaimableTokens[position].selected =
- !updatedClaimableTokens[position].selected;
- setClaimableTokens(updatedClaimableTokens);
- setNumberOfSelectedTokens(getNumberOfSelectedTokens());
- };
-
- const checkRewards = async () => {
- if (searchAddress) {
- setRewardsLoader(true);
- try {
- /**
- * check if the inserted address is cardano address
- * we want the stake address
- * if it is cardano address, get the staking address
- */
- let address = await getStakeKey(searchAddress);
-
- address = address.staking_address;
-
- setStakeAddress(address);
- const getRewardsDto = await getRewards(address);
- if (getRewardsDto == null) throw new Error();
- if (getRewardsDto.claimable_tokens.length !== 0) {
- setClaimableTokens(
- getRewardsDto.claimable_tokens
- .map((token) => {
- token.selected = false;
- return token;
- })
- .sort((a, b) => {
- if (a.premium === b.premium) {
- if (a.ticker < b.ticker) {
- return -1;
- } else {
- return 1;
- }
- } else {
- return a.premium ? -1 : 1;
- }
- })
- );
- setPoolInfo(getRewardsDto.pool_info);
- setRewardsLoader(false);
- } else {
- dispatch(
- showModal({
- modalType: ModalTypes.info,
- details: {
- text: "No rewards found for the account, yet.",
- type: InfoModalTypes.info,
- },
- })
- );
- }
- } catch (e: any) {
- handleError(e);
- } finally {
- setRewardsLoader(false);
- }
- }
- };
-
- const claimMyRewards = async () => {
- if (numberOfSelectedTokens === 0) return;
-
- setClaimMyRewardLoading(true);
- let selectedPremiumToken = false;
-
- const selectedTokenId: string[] = [];
- claimableTokens.forEach((token) => {
- if (token.selected) {
- if (token.premium) {
- selectedPremiumToken = true;
- }
- selectedTokenId.push(token.assetId);
- }
- });
-
- try {
- const res = await getCustomRewards(
- stakeAddress,
- stakeAddress.slice(0, 40),
- selectedTokenId.join(","),
- selectedPremiumToken
- );
- if (res == null) throw new Error();
-
- let depositInfoUrl = `${PageRoute.depositCardano}?stakeAddress=${stakeAddress}&withdrawAddress=${res.withdrawal_address}&requestId=${res.request_id}&selectedTokens=${numberOfSelectedTokens}&unlock=${selectedPremiumToken}&isWhitelisted=${res.is_whitelisted}`;
- navigate(depositInfoUrl, { replace: true });
- } catch (e) {
- handleError(e);
- } finally {
- setClaimMyRewardLoading(false);
- }
- };
-
- const cancelClaim = async () => {
- setClaimableTokens([]);
- };
-
- const renderStakeInfo = () => {
- if (poolInfo != null) {
- return (
- <>
- {poolInfo.delegated_pool_logo ? (
-
- ) : (
- ""
- )}
-
-
- Currently staking
- {poolInfo.total_balance} ADA
- with
-
- {poolInfo.delegated_pool_name}
- [{poolInfo.delegated_pool_ticker}]
-
-
-
- >
- );
- } else {
- return <>Unregistered>;
- }
- };
-
- function renderCheckRewardsStep() {
- if (!hideCheck) {
- return (
-
-
Enter your wallet/stake address or $handle to view your rewards
-
) =>
- setSearchAddress((e.target as HTMLInputElement).value)
- }
- onKeyDown={(e) => {
- if (e.key === "Enter") {
- checkRewards();
- }
- }}
- disabled={
- !hideStakingInfo ||
- (typeof connectedWallet?.wallet?.api !== "undefined" &&
- !isWrongNetwork)
- }
- >
-
-
- Check my rewards
- {rewardsLoader ? (
-
-
-
- ) : null}
-
-
- Cancel
-
-
-
- );
- } else {
- return null;
- }
- }
-
- function renderStakingInfoStep() {
- if (!hideStakingInfo) {
- return (
-
-
- {renderStakeInfo()}
-
-
-
-
-
- These tokens incur a TosiFee when claiming
-
-
- {claimableTokens.map((token, index) => {
- return (
-
- );
- })}
-
-
-
-
Selected {numberOfSelectedTokens} token
-
-
- {numberOfSelectedTokens === claimableTokens.length
- ? "Unselect All"
- : "Select All"}
-
-
- Claim my rewards
- {claimMyRewardLoading ? (
-
-
-
- ) : null}
-
-
-
-
- );
- } else {
- return null;
- }
- }
-
return (
<>
@@ -360,8 +29,23 @@ function Claim() {
- {renderCheckRewardsStep()}
- {renderStakingInfoStep()}
+
+
>
);
diff --git a/client/src/reducers/walletSlice.ts b/client/src/reducers/walletSlice.ts
index 8510c0cd..ef1796a9 100644
--- a/client/src/reducers/walletSlice.ts
+++ b/client/src/reducers/walletSlice.ts
@@ -27,12 +27,15 @@ export const walletSlice = createSlice({
name: "wallet",
initialState,
reducers: {
- connectWallet: (state, action: PayloadAction