From 1ed652939d91e072cc1c4193d50745d8f19e6214 Mon Sep 17 00:00:00 2001 From: aryelciu001 Date: Sat, 11 Mar 2023 23:34:07 +0800 Subject: [PATCH 01/11] chore: use hook to show modal --- client/src/hooks/useModal.tsx | 49 +++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 client/src/hooks/useModal.tsx diff --git a/client/src/hooks/useModal.tsx b/client/src/hooks/useModal.tsx new file mode 100644 index 00000000..743dd163 --- /dev/null +++ b/client/src/hooks/useModal.tsx @@ -0,0 +1,49 @@ +import { useDispatch } from "react-redux"; +import { InfoModalTypes, ModalTypes } from "src/entities/common.entities"; +import { showModal } from "src/reducers/globalSlice"; + +export default function useModal() { + const dispatch = useDispatch(); + + const showInfoModal = (text: string) => { + dispatch( + showModal({ + modalType: ModalTypes.info, + details: { + text: text, + type: InfoModalTypes.info, + }, + }) + ); + }; + + const showErrorModal = (text: string) => { + dispatch( + showModal({ + modalType: ModalTypes.info, + details: { + text: text, + type: InfoModalTypes.failure, + }, + }) + ); + }; + + const showSuccessModal = (text: string) => { + dispatch( + showModal({ + modalType: ModalTypes.info, + details: { + text: text, + type: InfoModalTypes.success, + }, + }) + ); + }; + + return { + showInfoModal, + showErrorModal, + showSuccessModal, + }; +} From bc7e634b0263c2ac7cc5a9a76f5c4ee6cd436674 Mon Sep 17 00:00:00 2001 From: aryelciu001 Date: Sun, 12 Mar 2023 18:14:09 +0800 Subject: [PATCH 02/11] feat: im feeling lucky function and max token for claim --- .../hooks/cardano/claim/useClaimReward.tsx | 67 +++++++++++++------ client/src/utils/index.tsx | 17 +++++ 2 files changed, 65 insertions(+), 19 deletions(-) diff --git a/client/src/hooks/cardano/claim/useClaimReward.tsx b/client/src/hooks/cardano/claim/useClaimReward.tsx index 248f9532..c0e2fd4c 100644 --- a/client/src/hooks/cardano/claim/useClaimReward.tsx +++ b/client/src/hooks/cardano/claim/useClaimReward.tsx @@ -1,11 +1,6 @@ -import { useSelector } from "react-redux"; - -import { RootState } from "src/store"; - import { useEffect, useState } from "react"; -import { useDispatch } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import { useNavigate } from "react-router-dom"; - import { InfoModalTypes, ModalTypes, @@ -13,14 +8,18 @@ import { } from "src/entities/common.entities"; import { ClaimableToken, VmPoolInfo } from "src/entities/vm.entities"; import useErrorHandler from "src/hooks/useErrorHandler"; +import useModal from "src/hooks/useModal"; import { showModal } from "src/reducers/globalSlice"; import { getCustomRewards, getRewards } from "src/services/claim"; import { getStakeKey } from "src/services/common"; +import { RootState } from "src/store"; +import { shuffleArray } from "src/utils"; export default function useClaimReward() { const dispatch = useDispatch(); const navigate = useNavigate(); const { handleError } = useErrorHandler(); + const { showInfoModal } = useModal(); const connectedWalletAddress = useSelector( (state: RootState) => state.wallet.walletAddress ); @@ -35,6 +34,8 @@ export default function useClaimReward() { const [isClaimRewardLoading, setIsClaimRewardLoading] = useState(false); const [stakeAddress, setStakeAddress] = useState(""); const [numberOfSelectedTokens, setNumberOfSelectedTokens] = useState(0); + /** default max number of token to claim */ + const [maxTokenSelected, setMaxTokenSelected] = useState(1000); useEffect(() => { setSearchAddress(isWrongNetwork ? "" : connectedWalletAddress); @@ -51,33 +52,61 @@ export default function useClaimReward() { ); }, [claimableTokens]); - const selectAllClaimableTokens = () => { + const handleTokenSelect = (position: number) => { const updatedClaimableTokens = [...claimableTokens]; - if (numberOfSelectedTokens < claimableTokens.length) { - updatedClaimableTokens.forEach((token) => (token.selected = true)); - } else { - updatedClaimableTokens.forEach((token) => (token.selected = false)); + + if ( + !updatedClaimableTokens[position].selected && + numberOfSelectedTokens === maxTokenSelected + ) { + showInfoModal( + `You have selected the maximum number of tokens to claim (${maxTokenSelected}). + Please deselect other tokens first` + ); + return; } - setClaimableTokens(updatedClaimableTokens); - }; - const handleTokenSelect = (position: number) => { - const updatedClaimableTokens = [...claimableTokens]; updatedClaimableTokens[position].selected = !updatedClaimableTokens[position].selected; setClaimableTokens(updatedClaimableTokens); }; const selectAll = () => { + const positions = [...Array(claimableTokens.length).keys()].slice( + 0, + maxTokenSelected + ); + const updatedClaimableTokens = [...claimableTokens]; - if (numberOfSelectedTokens < claimableTokens.length) { - updatedClaimableTokens.forEach((token) => (token.selected = true)); + if ( + numberOfSelectedTokens < + Math.min(maxTokenSelected, claimableTokens.length) + ) { + positions.forEach( + (position) => (updatedClaimableTokens[position].selected = true) + ); } else { - updatedClaimableTokens.forEach((token) => (token.selected = false)); + positions.forEach( + (position) => (updatedClaimableTokens[position].selected = false) + ); } setClaimableTokens(updatedClaimableTokens); }; + const selectRandomTokens = () => { + const positions = shuffleArray([ + ...Array(claimableTokens.length).keys(), + ]).slice(0, maxTokenSelected); + + const updatedClaimableTokens = [...claimableTokens]; + updatedClaimableTokens.forEach((token) => (token.selected = false)); + positions.forEach( + (position) => (updatedClaimableTokens[position].selected = true) + ); + + setClaimableTokens(updatedClaimableTokens); + }; + const checkRewards = async () => { setIsCheckRewardLoading(true); try { @@ -177,9 +206,9 @@ export default function useClaimReward() { checkRewards, claimRewards, selectAll, + selectRandomTokens, cancelClaim, claimableTokens, - selectAllClaimableTokens, handleTokenSelect, numberOfSelectedTokens, isCheckRewardLoading, diff --git a/client/src/utils/index.tsx b/client/src/utils/index.tsx index 5829b22b..3962f323 100644 --- a/client/src/utils/index.tsx +++ b/client/src/utils/index.tsx @@ -48,3 +48,20 @@ export const hexToUTF8 = (hex: string): string => { } return new TextDecoder().decode(ints); }; + +export const shuffleArray = (array: any[]): any[] => { + array = [...array]; + let currentIndex = array.length, + randomIndex; + + while (currentIndex != 0) { + randomIndex = Math.floor(Math.random() * currentIndex); + currentIndex--; + [array[currentIndex], array[randomIndex]] = [ + array[randomIndex], + array[currentIndex], + ]; + } + + return array; +}; From 1eb46d92c9c2c15310fcf334ff4ac073ef84b552 Mon Sep 17 00:00:00 2001 From: aryelciu001 Date: Sun, 12 Mar 2023 18:25:57 +0800 Subject: [PATCH 03/11] feat: prepare for max token support --- client/src/entities/dto.ts | 5 ++ client/src/entities/vm.ts | 12 +++ .../hooks/cardano/claim/useClaimReward.tsx | 80 +++++++++---------- client/src/services/common.ts | 6 +- server/index.ts | 8 +- 5 files changed, 62 insertions(+), 49 deletions(-) create mode 100644 client/src/entities/vm.ts diff --git a/client/src/entities/dto.ts b/client/src/entities/dto.ts index 3e5fb84b..78648bb9 100644 --- a/client/src/entities/dto.ts +++ b/client/src/entities/dto.ts @@ -1,4 +1,5 @@ import { DeliveredReward } from "./common.entities"; +import { VmTypes } from "./vm"; import { Assets, ClaimableToken, PoolInfo, VmPoolInfo } from "./vm.entities"; export interface GetRewardsDto { @@ -89,4 +90,8 @@ export namespace Dto { addressInBech32: string; }; } + + export interface GetVmSettings extends Base { + response: VmTypes.Settings; + } } diff --git a/client/src/entities/vm.ts b/client/src/entities/vm.ts new file mode 100644 index 00000000..cff3df73 --- /dev/null +++ b/client/src/entities/vm.ts @@ -0,0 +1,12 @@ +export namespace VmTypes { + export interface Settings { + withdrawal_fee: number; + epoch: number; + switching_epoch: boolean; + frontend_version: string; + backend_version: string; + min_balance: number; + confirmations_required: number; + max_assets_in_request?: number; + } +} diff --git a/client/src/hooks/cardano/claim/useClaimReward.tsx b/client/src/hooks/cardano/claim/useClaimReward.tsx index c0e2fd4c..fda47997 100644 --- a/client/src/hooks/cardano/claim/useClaimReward.tsx +++ b/client/src/hooks/cardano/claim/useClaimReward.tsx @@ -1,17 +1,12 @@ import { useEffect, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; import { useNavigate } from "react-router-dom"; -import { - InfoModalTypes, - ModalTypes, - PageRoute, -} from "src/entities/common.entities"; +import { PageRoute } from "src/entities/common.entities"; import { ClaimableToken, VmPoolInfo } from "src/entities/vm.entities"; import useErrorHandler from "src/hooks/useErrorHandler"; import useModal from "src/hooks/useModal"; -import { showModal } from "src/reducers/globalSlice"; import { getCustomRewards, getRewards } from "src/services/claim"; -import { getStakeKey } from "src/services/common"; +import { getSettings, getStakeKey } from "src/services/common"; import { RootState } from "src/store"; import { shuffleArray } from "src/utils"; @@ -110,51 +105,48 @@ export default function useClaimReward() { 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) { + + const [getRewardsResponse, vmSettings] = await Promise.all([ + getRewards(address), + getSettings(), + ]); + + if (getRewardsResponse == 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; - } + + if (getRewardsResponse.claimable_tokens.length === 0) { + showInfoModal("No rewards found for the account, yet."); + return; + } + + if (vmSettings.max_assets_in_request) { + setMaxTokenSelected(vmSettings.max_assets_in_request); + } + + setClaimableTokens( + getRewardsResponse.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 a.premium ? -1 : 1; + return 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, - }, + } else { + return a.premium ? -1 : 1; + } }) - ); - } + ); + setPoolInfo(getRewardsResponse.pool_info); + setIsCheckRewardLoading(false); } catch (e: any) { handleError(e); } finally { diff --git a/client/src/services/common.ts b/client/src/services/common.ts index 42e2f1ab..32b9d855 100644 --- a/client/src/services/common.ts +++ b/client/src/services/common.ts @@ -11,8 +11,10 @@ export async function getFeatures() { return response.data; } -export async function getSettings() { - const response = await axios.get(`/api/getsettings`); +export async function getSettings(): Promise { + const response = await axios.get( + `/api/getsettings` + ); return response.data; } diff --git a/server/index.ts b/server/index.ts index ed4e008c..dd343e36 100644 --- a/server/index.ts +++ b/server/index.ts @@ -7,15 +7,18 @@ import express, { Request, Response } from "express"; import * as _ from "lodash"; import url from "url"; import { + Dto, GetDeliveredRewardsDto, GetPoolsDto, GetQueueDto, ServerErrorDto, } from "../client/src/entities/dto"; import { Tip, TransactionStatus } from "../client/src/entities/koios.entities"; +import { VmTypes } from "../client/src/entities/vm"; import { PoolInfo } from "../client/src/entities/vm.entities"; import errorHandlerMiddleware, { errorHandlerWrapper, + typedErrorHandlerWrapper, } from "./middlewares/error-handler"; import TxRouter from "./routes/tx"; import UtilRouter from "./routes/util"; @@ -32,7 +35,6 @@ import { getRewards, getTokens, ITosiFeatures, - IVMSettings, postFromKoios, sanitizeString, translateAdaHandle, @@ -163,8 +165,8 @@ app.get( app.get( "/api/getsettings", oapi.path(resp200Ok), - errorHandlerWrapper(async (_req: Request, res: Response) => { - const settings: IVMSettings = await getFromVM("get_settings"); + typedErrorHandlerWrapper(async (_, res) => { + const settings = await getFromVM("get_settings"); return res.status(200).send(settings); }) ); From 0b04c6aa62eae8afbe86e19c79e4d40ec8186085 Mon Sep 17 00:00:00 2001 From: aryelciu001 Date: Sun, 12 Mar 2023 18:57:07 +0800 Subject: [PATCH 04/11] feat: banner for admin on server --- client/src/entities/dto.ts | 13 +++++++++++++ server/index.ts | 4 ++++ server/routes/admin.ts | 34 ++++++++++++++++++++++++++++++++++ server/utils/cache.ts | 4 ++++ 4 files changed, 55 insertions(+) create mode 100644 server/routes/admin.ts diff --git a/client/src/entities/dto.ts b/client/src/entities/dto.ts index 78648bb9..e1d0bc67 100644 --- a/client/src/entities/dto.ts +++ b/client/src/entities/dto.ts @@ -94,4 +94,17 @@ export namespace Dto { export interface GetVmSettings extends Base { response: VmTypes.Settings; } + + export interface PostBannerText extends Base { + body: { + text: string; + adminKey: string; + }; + } + + export interface GetBannerText extends Base { + response: { + text: string; + }; + } } diff --git a/server/index.ts b/server/index.ts index dd343e36..b6cee1bd 100644 --- a/server/index.ts +++ b/server/index.ts @@ -20,6 +20,7 @@ import errorHandlerMiddleware, { errorHandlerWrapper, typedErrorHandlerWrapper, } from "./middlewares/error-handler"; +import AdminRouter from "./routes/admin"; import TxRouter from "./routes/tx"; import UtilRouter from "./routes/util"; import { @@ -50,6 +51,8 @@ export const VM_KOIOS_URL = process.env.KOIOS_URL_TESTNET || process.env.KOIOS_URL; export const CARDANO_NETWORK = process.env.CARDANO_NETWORK || CardanoNetwork.preview; +export const TOSIDROP_ADMIN_KEY = + process.env.TOSIDROP_ADMIN_KEY || "admin key is not set"; const CLOUDFLARE_PSK = process.env.CLOUDFLARE_PSK; const LOG_TYPE = process.env.LOG_TYPE || "dev"; const PORT = process.env.PORT || 3000; @@ -118,6 +121,7 @@ const resp200Ok500Bad = { app.use("/api/tx", TxRouter); app.use("/api/util", UtilRouter); +app.use("/api/admin", AdminRouter); app.get( "/api/getprices", diff --git a/server/routes/admin.ts b/server/routes/admin.ts new file mode 100644 index 00000000..5f4d56a3 --- /dev/null +++ b/server/routes/admin.ts @@ -0,0 +1,34 @@ +import express from "express"; +import { TOSIDROP_ADMIN_KEY } from ".."; +import { Dto } from "../../client/src/entities/dto"; +import { typedErrorHandlerWrapper } from "../middlewares/error-handler"; +import { persistentCache } from "../utils/cache"; +import { createErrorWithCode, HttpStatusCode } from "../utils/error"; +const router = express.Router(); + +router.post( + "/banner", + typedErrorHandlerWrapper(async (req, res) => { + const { adminKey, text } = req.body; + + if (adminKey !== TOSIDROP_ADMIN_KEY) { + throw createErrorWithCode(HttpStatusCode.UNAUTHORIZED, "Wrong admin key"); + } + + persistentCache.set("banner", text); + + return res.status(200).send({}); + }) +); + +router.get( + "/banner", + typedErrorHandlerWrapper(async (_, res) => { + const text = persistentCache.get("banner") as string; + return res.status(200).send({ + text: text ?? "", + }); + }) +); + +export default router; diff --git a/server/utils/cache.ts b/server/utils/cache.ts index 27ac5732..ef915a65 100644 --- a/server/utils/cache.ts +++ b/server/utils/cache.ts @@ -9,3 +9,7 @@ export const longTermCache = new LRUCache({ ttl: 1000 * 60 * 60 * 24, max: 10, }); + +export const persistentCache = new LRUCache({ + max: 10, +}); From 545aa9606769bda40b2ef1627c8e110c8022c3f8 Mon Sep 17 00:00:00 2001 From: aryelciu001 Date: Fri, 17 Mar 2023 21:34:35 +0800 Subject: [PATCH 05/11] feat: banner UI --- client/src/components/Banner.tsx | 11 +++++++++++ client/src/components/Header/index.tsx | 3 +++ client/src/hooks/useBanner.tsx | 21 +++++++++++++++++++++ client/src/services/common.ts | 9 +++++++++ client/src/styles.scss | 4 ++++ client/src/variables.scss | 2 ++ 6 files changed, 50 insertions(+) create mode 100644 client/src/components/Banner.tsx create mode 100644 client/src/hooks/useBanner.tsx diff --git a/client/src/components/Banner.tsx b/client/src/components/Banner.tsx new file mode 100644 index 00000000..903f4b5d --- /dev/null +++ b/client/src/components/Banner.tsx @@ -0,0 +1,11 @@ +import useBanner from "src/hooks/useBanner"; + +export default function Banner() { + const { bannerText } = useBanner(); + + return bannerText ? ( +
+ {bannerText} +
+ ) : null; +} diff --git a/client/src/components/Header/index.tsx b/client/src/components/Header/index.tsx index 8c4bf076..fed90969 100644 --- a/client/src/components/Header/index.tsx +++ b/client/src/components/Header/index.tsx @@ -9,6 +9,7 @@ import logoLight from "src/assets/tosidrop-light.png"; import { Blockchain, Themes } from "src/entities/common.entities"; import { toggleMenu, toggleTheme } from "src/reducers/globalSlice"; import { RootState } from "src/store"; +import Banner from "../Banner"; import BlockchainSelector from "../BlockchainSelector"; import CardanoWalletSelector from "../WalletSelector/CardanoWalletSelector"; import ErgoWalletSelector from "../WalletSelector/ErgoWalletSelector"; @@ -32,6 +33,8 @@ function Header() { return ( <> + + {/* Web header */}
diff --git a/client/src/hooks/useBanner.tsx b/client/src/hooks/useBanner.tsx new file mode 100644 index 00000000..6e762ccf --- /dev/null +++ b/client/src/hooks/useBanner.tsx @@ -0,0 +1,21 @@ +import { useEffect, useState } from "react"; +import { getBannerText } from "src/services/common"; + +export default function useBanner() { + const [bannerText, setBannerText] = useState(""); + + useEffect(() => { + (async () => { + const bannerText = await getBannerText(); + try { + if (bannerText) { + setBannerText(bannerText); + } + } catch (e) {} + })(); + }, []); + + return { + bannerText, + }; +} diff --git a/client/src/services/common.ts b/client/src/services/common.ts index 32b9d855..a203581c 100644 --- a/client/src/services/common.ts +++ b/client/src/services/common.ts @@ -120,3 +120,12 @@ export async function getBech32Address({ ); return response.data.addressInBech32; } + +export async function getBannerText(): Promise< + Dto.GetBannerText["response"]["text"] +> { + const response = await axios.get( + `/api/admin/banner` + ); + return response.data.text; +} diff --git a/client/src/styles.scss b/client/src/styles.scss index 1a87db7c..a9459ce6 100644 --- a/client/src/styles.scss +++ b/client/src/styles.scss @@ -109,6 +109,10 @@ strong { background-color: var(--primary); } +.background-see-through { + background-color: var(--primary-see-through); +} + .body-background { background: var(--body-background); } diff --git a/client/src/variables.scss b/client/src/variables.scss index 2082a471..21c27c3c 100644 --- a/client/src/variables.scss +++ b/client/src/variables.scss @@ -40,6 +40,7 @@ $avenir-font-medium: "Avenir Next Cyr Medium", sans-serif; #002c69; --primary: #181c1e; --primary-hover: #272c2ea9; + --primary-see-through: rgba(24, 28, 30, 0.5); --tooltip-background: #9b9b9c; --tooltip-font-color: #181c1e; --font-color: #515355; @@ -100,6 +101,7 @@ $avenir-font-medium: "Avenir Next Cyr Medium", sans-serif; linear-gradient(0deg, rgba(226, 216, 250, 0.12), rgba(226, 216, 250, 0.12)), #bcd4f2; --primary: #ffffff; + --primary-see-through: rgba(255, 255, 255, 0.5); --primary-hover: #dbdbdb; --tooltip-background: #9b9b9c; --tooltip-font-color: #ffffff; From 61530835b2be88871427077dffba8b7514b07fb2 Mon Sep 17 00:00:00 2001 From: aryelciu001 Date: Sun, 19 Mar 2023 16:28:28 +0800 Subject: [PATCH 06/11] chore: set timeout for minswap api --- client/src/entities/dto.ts | 5 +++++ client/src/entities/minswap.ts | 17 +++++++++++++++++ server/index.ts | 10 ---------- server/service/minswap.ts | 32 ++++++++++++++++++++++++++++++++ server/utils/helpers.ts | 8 +++++++- 5 files changed, 61 insertions(+), 11 deletions(-) create mode 100644 client/src/entities/minswap.ts create mode 100644 server/service/minswap.ts diff --git a/client/src/entities/dto.ts b/client/src/entities/dto.ts index 78648bb9..55bb4d61 100644 --- a/client/src/entities/dto.ts +++ b/client/src/entities/dto.ts @@ -1,4 +1,5 @@ import { DeliveredReward } from "./common.entities"; +import { MinswapTypes } from "./minswap"; import { VmTypes } from "./vm"; import { Assets, ClaimableToken, PoolInfo, VmPoolInfo } from "./vm.entities"; @@ -94,4 +95,8 @@ export namespace Dto { export interface GetVmSettings extends Base { response: VmTypes.Settings; } + + export interface GetMinswapPriceInfoMap extends Base { + response: MinswapTypes.PriceInfoMap; + } } diff --git a/client/src/entities/minswap.ts b/client/src/entities/minswap.ts new file mode 100644 index 00000000..0ad809fc --- /dev/null +++ b/client/src/entities/minswap.ts @@ -0,0 +1,17 @@ +export namespace MinswapTypes { + export interface PriceInfoMap { + [key: string]: PriceInfo; + } + + export interface PriceInfo { + base_id: string; + base_name: string; + base_symbol: string; + quote_id: string; + quote_name: string; + quote_symbol: string; + last_price: string; + base_volume: string; + quote_volume: string; + } +} diff --git a/server/index.ts b/server/index.ts index dd343e36..c7f74b89 100644 --- a/server/index.ts +++ b/server/index.ts @@ -31,7 +31,6 @@ import { getFromVM, getPoolMetadata, getPools, - getPrices, getRewards, getTokens, ITosiFeatures, @@ -119,15 +118,6 @@ const resp200Ok500Bad = { app.use("/api/tx", TxRouter); app.use("/api/util", UtilRouter); -app.get( - "/api/getprices", - oapi.path(resp200Ok), - errorHandlerWrapper(async (_req: Request, res: Response) => { - const prices = await getPrices(); - return res.status(200).send(prices); - }) -); - app.get( "/api/getpools", oapi.path(resp200Ok), diff --git a/server/service/minswap.ts b/server/service/minswap.ts new file mode 100644 index 00000000..f34e494c --- /dev/null +++ b/server/service/minswap.ts @@ -0,0 +1,32 @@ +require("dotenv").config(); +import axios, { AxiosRequestConfig } from "axios"; +import { MinswapTypes } from "../../client/src/entities/minswap"; +import { shortTermCache } from "../utils/cache"; + +const MIN_PAIRS_API = + process.env.MIN_PAIRS_API || + "https://api-mainnet-prod.minswap.org/coinmarketcap/v2/pairs"; + +export namespace MinswapService { + export async function getPrices(): Promise { + let prices = shortTermCache.get("prices") as MinswapTypes.PriceInfoMap; + + if (prices == null) { + try { + const axiosRequestConfig: AxiosRequestConfig = + { + method: "GET", + url: MIN_PAIRS_API, + timeout: 10000, + }; + prices = (await axios(axiosRequestConfig)).data; + shortTermCache.set("prices", prices); + } catch (error: unknown) { + console.warn("WARNING: Fail to fetch price info from minswap"); + prices = {}; + } + } + + return prices; + } +} diff --git a/server/utils/helpers.ts b/server/utils/helpers.ts index 16b647ef..fb6ec6fd 100644 --- a/server/utils/helpers.ts +++ b/server/utils/helpers.ts @@ -21,6 +21,7 @@ import { VmDeliveredReward, VmTokenInfoMap, } from "../../client/src/entities/vm.entities"; +import { MinswapService } from "../service/minswap"; import { longTermCache, shortTermCache } from "./cache"; import { createErrorWithCode, HttpStatusCode } from "./error"; @@ -164,6 +165,10 @@ export async function getTokens(): Promise { return tokenInfo; } +/** + * @deprecated replaced by {@link MinswapService.getPrices} + * the new function has timeout and handle error better + */ export async function getPrices(): Promise { let prices = shortTermCache.get("prices") as GetPricePairs; if (prices == null) { @@ -211,8 +216,9 @@ export async function getRewards(stakeAddress: string) { const [getRewardsResponse, tokens, prices] = await Promise.all([ getFromVM(`get_rewards&staking_address=${stakeAddress}`), getTokens(), - getPrices(), + MinswapService.getPrices(), ]); + if (getRewardsResponse == null) return; if (tokens == null) return; From 70f95f16d4773eb6402acf5d6b6bee5f7a58a199 Mon Sep 17 00:00:00 2001 From: aryelciu001 Date: Sun, 19 Mar 2023 16:33:59 +0800 Subject: [PATCH 07/11] chore: error logger --- server/middlewares/error-handler.ts | 7 ++++--- server/service/logger.ts | 13 +++++++++++++ server/service/minswap.ts | 5 +++-- 3 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 server/service/logger.ts diff --git a/server/middlewares/error-handler.ts b/server/middlewares/error-handler.ts index 9efadba6..b3a7ba73 100644 --- a/server/middlewares/error-handler.ts +++ b/server/middlewares/error-handler.ts @@ -1,5 +1,6 @@ import { NextFunction, Request, Response } from "express"; import { Dto } from "../../client/src/entities/dto"; +import { LoggerService } from "../service/logger"; import { createErrorResponse, ErrorWithCode, @@ -16,14 +17,14 @@ export default async function errorHandlerMiddleware( let errorMessage: string; if (error instanceof ErrorWithCode) { errorMessage = error.message; - console.log(`Error at ${req.url}: ${errorMessage}\n${error.stack}`); + LoggerService.error(`Error at ${req.url}: ${errorMessage}\n${error.stack}`); statusCode = error.code; } else if (error instanceof Error) { errorMessage = error.message; - console.log(`Error at ${req.url}: ${errorMessage}\n${error.stack}`); + LoggerService.error(`Error at ${req.url}: ${errorMessage}\n${error.stack}`); } else { errorMessage = JSON.stringify(error); - console.log(`Error at ${req.url}: ${errorMessage}`); + LoggerService.error(`Error at ${req.url}: ${errorMessage}`); } return res.status(statusCode).send(createErrorResponse(errorMessage)); } diff --git a/server/service/logger.ts b/server/service/logger.ts new file mode 100644 index 00000000..16924a2b --- /dev/null +++ b/server/service/logger.ts @@ -0,0 +1,13 @@ +export namespace LoggerService { + export function warn(text: string) { + console.warn(`WARNING: ${text}`); + } + + export function error(text: string) { + console.error(`ERROR: ${text}`); + } + + export function log(text: string) { + console.error(`LOG: ${text}`); + } +} diff --git a/server/service/minswap.ts b/server/service/minswap.ts index f34e494c..aa15ed3f 100644 --- a/server/service/minswap.ts +++ b/server/service/minswap.ts @@ -2,6 +2,7 @@ require("dotenv").config(); import axios, { AxiosRequestConfig } from "axios"; import { MinswapTypes } from "../../client/src/entities/minswap"; import { shortTermCache } from "../utils/cache"; +import { LoggerService } from "./logger"; const MIN_PAIRS_API = process.env.MIN_PAIRS_API || @@ -16,13 +17,13 @@ export namespace MinswapService { const axiosRequestConfig: AxiosRequestConfig = { method: "GET", - url: MIN_PAIRS_API, + url: "https://hub.dummyapis.com/delay?seconds=30", timeout: 10000, }; prices = (await axios(axiosRequestConfig)).data; shortTermCache.set("prices", prices); } catch (error: unknown) { - console.warn("WARNING: Fail to fetch price info from minswap"); + LoggerService.warn("Fail to fetch price info from minswap"); prices = {}; } } From 4c967e8312d06793fb66cb6a86611e0ec2170aaa Mon Sep 17 00:00:00 2001 From: Boris P Date: Sun, 19 Mar 2023 04:39:28 -0600 Subject: [PATCH 08/11] fix: url un-hardcode --- server/service/minswap.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/service/minswap.ts b/server/service/minswap.ts index aa15ed3f..caef6a0b 100644 --- a/server/service/minswap.ts +++ b/server/service/minswap.ts @@ -17,7 +17,7 @@ export namespace MinswapService { const axiosRequestConfig: AxiosRequestConfig = { method: "GET", - url: "https://hub.dummyapis.com/delay?seconds=30", + url: MIN_PAIRS_API, timeout: 10000, }; prices = (await axios(axiosRequestConfig)).data; From aa6e49dc83a00407909112497a20355938a5da07 Mon Sep 17 00:00:00 2001 From: Boris P Date: Sun, 19 Mar 2023 04:46:55 -0600 Subject: [PATCH 09/11] fix: merge issue --- client/src/entities/dto.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/entities/dto.ts b/client/src/entities/dto.ts index 145b50fb..31777aa8 100644 --- a/client/src/entities/dto.ts +++ b/client/src/entities/dto.ts @@ -98,7 +98,8 @@ export namespace Dto { export interface GetMinswapPriceInfoMap extends Base { response: MinswapTypes.PriceInfoMap; - + } + export interface PostBannerText extends Base { body: { text: string; From 726d17b55535f3e4ea706a0a9105cd4d1b4dda1f Mon Sep 17 00:00:00 2001 From: aryelciu001 Date: Tue, 4 Apr 2023 22:53:52 +0800 Subject: [PATCH 10/11] feat: im feeling lucky --- client/src/components/Claim/RewardsView.tsx | 17 +++++++++++--- .../hooks/cardano/claim/useClaimReward.tsx | 22 +++++-------------- client/src/pages/Cardano/Claim/index.tsx | 4 ++++ 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/client/src/components/Claim/RewardsView.tsx b/client/src/components/Claim/RewardsView.tsx index 19fd549f..23c60ea8 100644 --- a/client/src/components/Claim/RewardsView.tsx +++ b/client/src/components/Claim/RewardsView.tsx @@ -12,6 +12,8 @@ interface Props { isLoadingClaimReward: boolean; selectAll: () => void; poolInfo?: any; + maxTokenSelected: number; + selectRandomTokens: any; } export default function RewardsView({ @@ -22,6 +24,8 @@ export default function RewardsView({ isLoadingClaimReward, selectAll, poolInfo, + maxTokenSelected, + selectRandomTokens, }: Props) { if (claimableTokens.length > 0) { return ( @@ -93,17 +97,24 @@ export default function RewardsView({ className={"background flex flex-row items-center p-5 rounded-2xl"} >
Selected {numberOfSelectedTokens} token
-
+
+
From 43fdfc28a121480bf77147a8853016f647d97f9f Mon Sep 17 00:00:00 2001 From: aryelciu001 Date: Tue, 4 Apr 2023 23:36:43 +0800 Subject: [PATCH 11/11] remove select all btn --- client/src/components/Claim/RewardsView.tsx | 13 ++++++------- client/src/hooks/cardano/claim/useClaimReward.tsx | 11 ++++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/client/src/components/Claim/RewardsView.tsx b/client/src/components/Claim/RewardsView.tsx index 23c60ea8..61550693 100644 --- a/client/src/components/Claim/RewardsView.tsx +++ b/client/src/components/Claim/RewardsView.tsx @@ -100,19 +100,18 @@ export default function RewardsView({
+ +