+
- {settingMenuToggle && (
+ {openedSetting && (
)}
diff --git a/packages/web/src/components/swap/swap-card/SwapCard.spec.tsx b/packages/web/src/components/swap/swap-card/SwapCard.spec.tsx
deleted file mode 100644
index 15186c1f4..000000000
--- a/packages/web/src/components/swap/swap-card/SwapCard.spec.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import { render } from "@testing-library/react";
-import { Provider as JotaiProvider } from "jotai";
-import GnoswapThemeProvider from "@providers/gnoswap-theme-provider/GnoswapThemeProvider";
-import SwapCard from "./SwapCard";
-import {
- coinList,
- dummyAutoRouterInfo,
- dummySwapGasInfo,
-} from "@containers/swap-container/SwapContainer";
-import { DEVICE_TYPE } from "@styles/media";
-
-describe("SwapCard Component", () => {
- it("SwapCard render", () => {
- const mockProps = {
- search: () => null,
- keyword: "",
- gnosAmount: "1",
- isConnected: true,
- autoRouter: false,
- showAutoRouter: () => null,
- swapGasInfo: dummySwapGasInfo,
- swapInfo: true,
- showSwapInfo: () => null,
- autoRouterInfo: dummyAutoRouterInfo,
- settingMenuToggle: true,
- onSettingMenu: () => null,
- tolerance: "",
- changeTolerance: () => null,
- tokenModal: true,
- onSelectTokenModal: () => null,
- swapOpen: true,
- onConfirmModal: () => null,
- coinList: coinList(),
- changeToken: () => null,
- selectToken: () => null,
- submitSwap: () => null,
- resetTolerance: () => null,
- breakpoint: DEVICE_TYPE.WEB,
- handleCopyClipBoard: () => null,
- submit: false,
- isFetching: true,
- copied: true,
- swapResult: { success: true, transaction: "https//:naver.com" },
- from: {
- token: "USDCoin",
- symbol: "USDC",
- amount: "121",
- price: "$0.00",
- gnosExchangePrice: "1250",
- usdExchangePrice: "($1541.55)",
- balance: "0",
- logoURI:
- "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
- },
- to: {
- token: "HEX",
- symbol: "HEX",
- amount: "5000",
- price: "$0.00",
- gnosExchangePrice: "1250",
- usdExchangePrice: "($1541.55)",
- balance: "0",
- logoURI:
- "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39/logo.png",
- },
- };
-
- render(
-
-
-
-
- ,
- );
- });
-});
diff --git a/packages/web/src/components/swap/swap-card/SwapCard.stories.tsx b/packages/web/src/components/swap/swap-card/SwapCard.stories.tsx
index b33274a92..e847831b2 100644
--- a/packages/web/src/components/swap/swap-card/SwapCard.stories.tsx
+++ b/packages/web/src/components/swap/swap-card/SwapCard.stories.tsx
@@ -2,13 +2,82 @@ import React from "react";
import { ComponentStory, ComponentMeta } from "@storybook/react";
import SwapCard from "./SwapCard";
import { css } from "@emotion/react";
-import {
- coinList,
- dummyAutoRouterInfo,
- dummySwapGasInfo,
-} from "@containers/swap-container/SwapContainer";
-import { action } from "@storybook/addon-actions";
-import { DEVICE_TYPE } from "@styles/media";
+import { SwapSummaryInfo } from "@models/swap/swap-summary-info";
+import PoolData from "@repositories/pool/mock/pools.json";
+import { SwapRouteInfo } from "@models/swap/swap-route-info";
+
+const pools = PoolData.pools;
+
+const swapSummaryInfo: SwapSummaryInfo = {
+ tokenA: {
+ chainId: "test3",
+ address: "0x111111111117dC0aa78b770fA6A738034120C302",
+ path: "gno.land/r/demo/1inch",
+ name: "1inch",
+ symbol: "1INCH",
+ decimals: 6,
+ logoURI: "https://assets.coingecko.com/coins/images/13469/thumb/1inch-token.png?1608803028",
+ priceId: "1inch",
+ createdAt: "1999-01-01T00:00:01Z"
+ },
+ tokenB: {
+ chainId: "test3",
+ address: "0x111111111117dC0aa78b770fA6A738034120C302",
+ path: "gno.land/r/demo/1inch",
+ name: "1inch",
+ symbol: "1INCH",
+ decimals: 6,
+ logoURI: "https://assets.coingecko.com/coins/images/13469/thumb/1inch-token.png?1608803028",
+ priceId: "1inch",
+ createdAt: "1999-01-01T00:00:01Z"
+ },
+ swapDirection: "EXACT_IN",
+ swapRate: 1.14,
+ swapRateUSD: 1.14,
+ priceImpact: 0.3,
+ guaranteedAmount: {
+ amount: 45124,
+ currency: "GNOT"
+ },
+ gasFee: {
+ amount: 0.000001,
+ currency: "GNOT"
+ },
+ gasFeeUSD: 0.1
+};
+
+const swapRouteInfos: SwapRouteInfo[] = [{
+ from: {
+ chainId: "test3",
+ address: "0x111111111117dC0aa78b770fA6A738034120C302",
+ path: "gno.land/r/demo/1inch",
+ name: "1inch",
+ symbol: "1INCH",
+ decimals: 6,
+ logoURI: "https://assets.coingecko.com/coins/images/13469/thumb/1inch-token.png?1608803028",
+ priceId: "1inch",
+ createdAt: "1999-01-01T00:00:01Z"
+ },
+ to: {
+ chainId: "test3",
+ address: "0x111111111117dC0aa78b770fA6A738034120C302",
+ path: "gno.land/r/demo/1inch",
+ name: "1inch",
+ symbol: "1INCH",
+ decimals: 6,
+ logoURI: "https://assets.coingecko.com/coins/images/13469/thumb/1inch-token.png?1608803028",
+ priceId: "1inch",
+ createdAt: "1999-01-01T00:00:01Z"
+ },
+ gasFee: {
+ amount: 0.000001,
+ currency: "GNOT"
+ },
+ gasFeeUSD: 0.1,
+ pools,
+ version: "V1",
+ weight: 100,
+}];
export default {
title: "swap/SwapCard",
@@ -25,57 +94,9 @@ const Template: ComponentStory
= args => (
export const Default = Template.bind({});
Default.args = {
- search: action("search"),
- keyword: "",
- gnosAmount: "1500",
- isConnected: true,
- autoRouter: true,
- showAutoRouter: action("onClick"),
- swapGasInfo: dummySwapGasInfo,
- swapInfo: true,
- showSwapInfo: action("onClick"),
- autoRouterInfo: dummyAutoRouterInfo,
- settingMenuToggle: true,
- onSettingMenu: action("onSettingMenu"),
- tolerance: "",
- changeTolerance: action("changeTolerance"),
- tokenModal: true,
- onSelectTokenModal: action("onClick"),
- swapOpen: true,
- onConfirmModal: action("onClick"),
- submitSwap: action("submitSwap"),
- coinList: coinList(),
- changeToken: action("changeToken"),
- selectToken: action("selectToken"),
- resetTolerance: action("resetTolerance"),
- handleCopyClipBoard: action("handleCopyClipBoard"),
- breakpoint: DEVICE_TYPE.WEB,
- submit: false,
- copied: true,
- isFetching: true,
- swapResult: { success: true, transaction: "https//:naver.com" },
- from: {
- token: "USDCoin",
- symbol: "USDC",
- amount: "121",
- price: "$0.00",
- gnosExchangePrice: "1250",
- usdExchangePrice: "($1541.55)",
- balance: "0",
- logoURI:
- "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
- },
- to: {
- token: "HEX",
- symbol: "HEX",
- amount: "5000",
- price: "$0.00",
- gnosExchangePrice: "1250",
- usdExchangePrice: "($1541.55)",
- balance: "0",
- logoURI:
- "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39/logo.png",
- },
+ swapSummaryInfo,
+ swapRouteInfos,
+ swapButtonText: "Swap"
};
const wrapper = () => css`
diff --git a/packages/web/src/components/swap/swap-card/SwapCard.tsx b/packages/web/src/components/swap/swap-card/SwapCard.tsx
index b60736a8a..410c12f23 100644
--- a/packages/web/src/components/swap/swap-card/SwapCard.tsx
+++ b/packages/web/src/components/swap/swap-card/SwapCard.tsx
@@ -3,178 +3,160 @@ import Button, { ButtonHierarchy } from "@components/common/button/Button";
import { SwapCardWrapper } from "./SwapCard.styles";
import SwapCardHeader from "../swap-card-header/SwapCardHeader";
import SwapCardContent from "../swap-card-content/SwapCardContent";
-import {
- AutoRouterInfo,
- tokenInfo,
- SwapData,
- SwapGasInfo,
-} from "@containers/swap-container/SwapContainer";
import ConfirmSwapModal from "../confirm-swap-modal/ConfirmSwapModal";
-import { DEVICE_TYPE } from "@styles/media";
-
-export interface TokenInfo {
- token: string;
- symbol: string;
- amount: string;
- price: string;
- gnosExchangePrice: string;
- usdExchangePrice: string;
- balance: string;
- logoURI: string;
-}
+import { TokenModel } from "@models/token/token-model";
+import { SwapTokenInfo } from "@models/swap/swap-token-info";
+import { SwapSummaryInfo } from "@models/swap/swap-summary-info";
+import { SwapRouteInfo } from "@models/swap/swap-route-info";
+import { SwapResultInfo } from "@models/swap/swap-result-info";
+import { FontsKey } from "@constants/font.constant";
interface SwapCardProps {
- search: (e: React.ChangeEvent) => void;
- keyword: string;
- isConnected: boolean;
- from: TokenInfo;
- to: TokenInfo;
- gnosAmount: string;
- swapInfo: boolean;
- showSwapInfo: () => void;
- autoRouter: boolean;
- showAutoRouter: () => void;
- swapGasInfo: SwapGasInfo;
- autoRouterInfo: AutoRouterInfo;
- settingMenuToggle: boolean;
- onSettingMenu: () => void;
- tolerance: string;
- changeTolerance: (e: React.ChangeEvent) => void;
- tokenModal: boolean;
- onSelectTokenModal: () => void;
- swapOpen: boolean;
- onConfirmModal: () => void;
- coinList: tokenInfo[];
- changeToken: (token: tokenInfo, type: string) => void;
- selectToken: (e: string) => void;
- submitSwap: (event: React.MouseEvent) => void;
- breakpoint: DEVICE_TYPE;
- submit: boolean;
- isFetching: boolean;
- swapResult: SwapData | null;
- resetTolerance: () => void;
- handleCopyClipBoard: (text: string) => void;
+ connectedWallet: boolean;
copied: boolean;
+ swapTokenInfo: SwapTokenInfo;
+ swapSummaryInfo: SwapSummaryInfo | null;
+ swapRouteInfos: SwapRouteInfo[];
+ isAvailSwap: boolean;
+ swapButtonText: string;
+ submitted: boolean;
+ swapResult: SwapResultInfo | null;
+ openedConfirmModal: boolean;
+
+ changeTokenA: (token: TokenModel) => void;
+ changeTokenAAmount: (value: string) => void;
+ changeTokenB: (token: TokenModel) => void;
+ changeTokenBAmount: (value: string) => void;
+ changeSlippage: (value: string) => void;
+
+ switchSwapDirection: () => void;
+ openConfirmModal: () => void;
+ openConnectWallet: () => void;
+ closeModal: () => void;
+ copyURL: () => void;
+ swap: () => void;
}
const SwapCard: React.FC = ({
- search,
- keyword,
- from,
- to,
- gnosAmount,
- isConnected,
- swapInfo,
- showSwapInfo,
- autoRouter,
- showAutoRouter,
- swapGasInfo,
- autoRouterInfo,
- settingMenuToggle,
- onSettingMenu,
- tolerance,
- changeTolerance,
- tokenModal,
- onSelectTokenModal,
- swapOpen,
- onConfirmModal,
- coinList,
- changeToken,
- selectToken,
- submitSwap,
- breakpoint,
- submit,
- isFetching,
- swapResult,
- resetTolerance,
- handleCopyClipBoard,
+ connectedWallet,
copied,
+ swapTokenInfo,
+ swapSummaryInfo,
+ swapRouteInfos,
+ isAvailSwap,
+ swapButtonText,
+ submitted,
+ swapResult,
+ openedConfirmModal,
+ changeTokenA,
+ changeTokenAAmount,
+ changeTokenB,
+ changeTokenBAmount,
+ changeSlippage,
+ switchSwapDirection,
+ openConfirmModal,
+ openConnectWallet,
+ closeModal,
+ copyURL,
+ swap,
}) => {
+
return (
<>
- {isConnected ? (
- Number(gnosAmount) - Number(from.gnosExchangePrice) > 0 ? (
-
- ) : (
-
- )
- ) : (
-
- {swapOpen && (
+
+ {openedConfirmModal && swapSummaryInfo && (
)}
>
);
};
+interface SwapButtonProps {
+ connectedWallet: boolean;
+ isAvailSwap: boolean;
+ text: string;
+ openConfirmModal: () => void;
+ openConnectWallet: () => void;
+}
+
+const SwapButton: React.FC = ({
+ connectedWallet,
+ isAvailSwap,
+ text,
+ openConfirmModal,
+ openConnectWallet,
+}) => {
+
+ const defaultStyle = {
+ fullWidth: true,
+ fontType: "body7" as FontsKey,
+ hierarchy: ButtonHierarchy.Primary,
+ height: 56,
+ };
+
+ if (!connectedWallet) {
+ return (
+
+ );
+ }
+
+ if (!isAvailSwap) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+};
+
export default SwapCard;
diff --git a/packages/web/src/components/token/token-swap/TokenSwap.spec.tsx b/packages/web/src/components/token/token-swap/TokenSwap.spec.tsx
deleted file mode 100644
index c407a4d4b..000000000
--- a/packages/web/src/components/token/token-swap/TokenSwap.spec.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import { render } from "@testing-library/react";
-import { Provider as JotaiProvider } from "jotai";
-import GnoswapThemeProvider from "@providers/gnoswap-theme-provider/GnoswapThemeProvider";
-import TokenSwap, { TokenSwapProps } from "./TokenSwap";
-
-describe("TokenSwap Component", () => {
- it("TokenSwap render", () => {
- const args: TokenSwapProps = {
- from: {
- token: {
- path: "USDCoin",
- name: "USDCoin",
- symbol: "USDC",
- logoURI:
- "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
- },
- amount: "121",
- price: "$0.00",
- balance: "0",
- },
- to: {
- token: {
- path: "HEX",
- name: "HEX",
- symbol: "HEX",
- logoURI:
- "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39/logo.png",
- },
- amount: "5000",
- price: "$0.00",
- balance: "0",
- },
- connected: true,
- connectWallet: () => { return; },
- swapNow: () => { return; },
- };
-
- render(
-
-
-
-
- ,
- );
- });
-});
\ No newline at end of file
diff --git a/packages/web/src/components/token/token-swap/TokenSwap.stories.tsx b/packages/web/src/components/token/token-swap/TokenSwap.stories.tsx
index b8a3aa6f9..007c51d8c 100644
--- a/packages/web/src/components/token/token-swap/TokenSwap.stories.tsx
+++ b/packages/web/src/components/token/token-swap/TokenSwap.stories.tsx
@@ -17,11 +17,15 @@ export const Default = Template.bind({});
Default.args = {
from: {
token: {
- path: "USDCoin",
- name: "USDCoin",
- symbol: "USDC",
- logoURI:
- "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
+ chainId: "dev",
+ createdAt: "2023-10-10T08:48:46+09:00",
+ name: "Gnoswap",
+ address: "g1sqaft388ruvsseu97r04w4rr4szxkh4nn6xpax",
+ path: "gno.land/r/gnos",
+ decimals: 4,
+ symbol: "GNOS",
+ logoURI: "https://s2.coinmarketcap.com/static/img/coins/64x64/5994.png",
+ priceId: "gno.land/r/gnos"
},
amount: "121",
price: "$0.00",
@@ -29,11 +33,15 @@ Default.args = {
},
to: {
token: {
- path: "HEX",
- name: "HEX",
- symbol: "HEX",
- logoURI:
- "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39/logo.png",
+ chainId: "dev",
+ createdAt: "2023-10-10T08:48:46+09:00",
+ name: "Gnoswap",
+ address: "g1sqaft388ruvsseu97r04w4rr4szxkh4nn6xpax",
+ path: "gno.land/r/gnos",
+ decimals: 4,
+ symbol: "GNOS",
+ logoURI: "https://s2.coinmarketcap.com/static/img/coins/64x64/5994.png",
+ priceId: "gno.land/r/gnos"
},
amount: "5000",
price: "$0.00",
diff --git a/packages/web/src/components/token/token-swap/TokenSwap.tsx b/packages/web/src/components/token/token-swap/TokenSwap.tsx
index 7b6a90b76..ad323bd15 100644
--- a/packages/web/src/components/token/token-swap/TokenSwap.tsx
+++ b/packages/web/src/components/token/token-swap/TokenSwap.tsx
@@ -4,18 +4,18 @@ import IconSettings from "@components/common/icons/IconSettings";
import Button, { ButtonHierarchy } from "@components/common/button/Button";
import SelectPairButton from "@components/common/select-pair-button/SelectPairButton";
import IconSwapArrowDown from "@components/common/icons/IconSwapArrowDown";
-import { TokenInfo } from "@models/token/token-info";
import IconLink from "@components/common/icons/IconLink";
+import { TokenModel } from "@models/token/token-model";
export interface TokenSwapProps {
from: {
- token: TokenInfo;
+ token: TokenModel;
amount: string;
price: string;
balance: string;
};
to: {
- token: TokenInfo;
+ token: TokenModel;
amount: string;
price: string;
balance: string;
@@ -92,7 +92,7 @@ const TokenSwap: React.FC = ({ from, to, connected, connectWalle
className="amount-text"
value={fromAmount}
onChange={onChangeFromAmount}
- placeholder={fromAmount === "" ? "0" : ""}
+ placeholder="0"
/>
@@ -109,7 +109,7 @@ const TokenSwap: React.FC
= ({ from, to, connected, connectWalle
className="amount-text"
value={toAmount}
onChange={onChangeToAmount}
- placeholder={toAmount === "" ? "0" : ""}
+ placeholder="0"
/>
diff --git a/packages/web/src/constants/option.constant.ts b/packages/web/src/constants/option.constant.ts
index 31aaa7662..406f7db6f 100644
--- a/packages/web/src/constants/option.constant.ts
+++ b/packages/web/src/constants/option.constant.ts
@@ -1,12 +1,59 @@
import { ValuesType } from "utility-types";
+export type SwapFeeTierType =
+ | "FEE_100"
+ | "FEE_500"
+ | "FEE_3000"
+ | "FEE_10000"
+ | "NONE";
+
+export interface SwapFeeTierInfo {
+ type: SwapFeeTierType;
+ fee: number;
+ rateStr: string;
+ description: string;
+}
+
+export const SwapFeeTierInfoMap: Record
= {
+ FEE_100: {
+ type: "FEE_100",
+ fee: 100,
+ rateStr: "0.01%",
+ description: "Best for very stable pairs",
+ },
+ FEE_500: {
+ type: "FEE_500",
+ fee: 500,
+ rateStr: "0.05%",
+ description: "Best for stable pairs",
+ },
+ FEE_3000: {
+ type: "FEE_3000",
+ fee: 3000,
+ rateStr: "0.3%",
+ description: "Best for most pairs",
+ },
+ FEE_10000: {
+ type: "FEE_10000",
+ fee: 10000,
+ rateStr: "1%",
+ description: "Best for exotic pairs",
+ },
+ NONE: {
+ type: "NONE",
+ fee: 0,
+ rateStr: "-",
+ description: "-",
+ },
+} as const;
+
export const FEE_RATE_OPTION = {
FEE_01: "0.01",
FEE_05: "0.05",
FEE_3: "0.3",
FEE_1: "1",
} as const;
-export type FEE_RATE_OPTION = ValuesType;
+export type FeeRateOption = ValuesType;
export const STAKED_OPTION = {
NONE: "NONE",
@@ -77,3 +124,14 @@ export const PriceRangeTooltip: {
"A passive price range of [-50% ~ +100%] for moderate risks & returns.",
Custom: undefined,
};
+
+export const DEFAULT_SLIPPAGE = 0.5;
+
+export type AddLiquiditySubmitType =
+ | "CREATE_POOL"
+ | "ADD_LIQUIDITY"
+ | "CONNECT_WALLET"
+ | "INVALID_PAIR"
+ | "ENTER_AMOUNT"
+ | "INSUFFICIENT_BALANCE"
+ | "INVALID_RANGE";
diff --git a/packages/web/src/constants/swap.constant.ts b/packages/web/src/constants/swap.constant.ts
new file mode 100644
index 000000000..36e6b0e4b
--- /dev/null
+++ b/packages/web/src/constants/swap.constant.ts
@@ -0,0 +1,3 @@
+export const MIN_PRICE_X96 = "4295128740";
+export const MAX_PRICE_X96 =
+ "1461446703485210103287273052203988822378723970341";
diff --git a/packages/web/src/containers/best-pools-container/BestPoolsContainer.tsx b/packages/web/src/containers/best-pools-container/BestPoolsContainer.tsx
index ba03cafca..fb283f224 100644
--- a/packages/web/src/containers/best-pools-container/BestPoolsContainer.tsx
+++ b/packages/web/src/containers/best-pools-container/BestPoolsContainer.tsx
@@ -1,11 +1,11 @@
import React from "react";
import BestPools from "@components/token/best-pools/BestPools";
-import { FEE_RATE_OPTION } from "@constants/option.constant";
+import { SwapFeeTierType } from "@constants/option.constant";
import { type TokenPairInfo } from "@models/token/token-pair-info";
export interface BestPool {
tokenPair: TokenPairInfo;
- feeRate: FEE_RATE_OPTION;
+ feeRate: SwapFeeTierType;
tvl: string;
apr: string;
}
@@ -27,7 +27,7 @@ export const bestPoolsInit: BestPool = {
"https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
},
},
- feeRate: FEE_RATE_OPTION.FEE_05,
+ feeRate: "FEE_100",
tvl: "$12,908.25M",
apr: "120.52%",
};
diff --git a/packages/web/src/containers/earn-add-liquidity-container/EarnAddLiquidityContainer.tsx b/packages/web/src/containers/earn-add-liquidity-container/EarnAddLiquidityContainer.tsx
index 3ec5e4152..572dcea77 100644
--- a/packages/web/src/containers/earn-add-liquidity-container/EarnAddLiquidityContainer.tsx
+++ b/packages/web/src/containers/earn-add-liquidity-container/EarnAddLiquidityContainer.tsx
@@ -1,21 +1,21 @@
-import React, { useCallback, useMemo, useState } from "react";
+import React, { useCallback, useEffect, useMemo, useState } from "react";
import EarnAddLiquidity from "@components/earn-add/earn-add-liquidity/EarnAddLiquidity";
-import { FEE_RATE_OPTION, PriceRangeType } from "@constants/option.constant";
-import { DUMMY_FEE_TIERS, DUMMY_POOL_TICKS, DUMMY_PRICE_RANGE_MAP } from "./earn-add-liquidity-dummy";
-import { TokenInfo } from "@models/token/token-info";
+import { AddLiquiditySubmitType, PriceRangeType, SwapFeeTierType } from "@constants/option.constant";
import { useTokenAmountInput } from "@hooks/token/use-token-amount-input";
-
-export interface AddLiquidityFeeTier {
- feeRate: string;
- description: string;
- range: string;
-}
+import { TokenModel } from "@models/token/token-model";
+import { PoolModel } from "@models/pool/pool-model";
+import { useWallet } from "@hooks/wallet/use-wallet";
+import BigNumber from "bignumber.js";
+import { useSlippage } from "@hooks/common/use-slippage";
+import { useTokenData } from "@hooks/token/use-token-data";
+import { useEarnAddLiquidityConfirmModal } from "@hooks/token/use-earn-add-liquidity-confirm-modal";
export interface AddLiquidityPriceRage {
+ type: PriceRangeType;
range: {
- minTick: string;
+ minTick: number;
minPrice: string;
- maxTick: string;
+ maxTick: number;
maxPrice: string;
}
apr?: string;
@@ -33,83 +33,136 @@ export interface PriceRangeSummary {
estimatedApr: string;
}
-const feeTiers = DUMMY_FEE_TIERS;
-
-const priceRangeMap = DUMMY_PRICE_RANGE_MAP;
-
-const ticks = DUMMY_POOL_TICKS;
+export const SWAP_FEE_TIERS: SwapFeeTierType[] = [
+ "FEE_100",
+ "FEE_500",
+ "FEE_3000",
+ "FEE_10000",
+];
-const token0Data = {
- path: "1",
- logoURI: "https://s2.coinmarketcap.com/static/img/coins/64x64/1.png",
- name: "Bitcoin",
- symbol: "BTC",
-};
-
-const token1Data = {
- path: "2",
- logoURI: "https://s2.coinmarketcap.com/static/img/coins/64x64/2.png",
- name: "Ethereum",
- symbol: "ETH",
+const TEMP_CUSTOM_PRICE_RANGE: AddLiquidityPriceRage = {
+ type: "Custom",
+ range: {
+ minTick: 6600,
+ maxTick: 10200,
+ minPrice: "1.2840093675402746",
+ maxPrice: "2.1169206358924533",
+ },
+ apr: "0",
};
const EarnAddLiquidityContainer: React.FC = () => {
- const [tokenA, setToken0] = useState(token0Data);
- const [tokenB, setToken1] = useState(token1Data);
- const token0AmountInput = useTokenAmountInput(tokenA);
- const token1AmountInput = useTokenAmountInput(tokenB);
- const [feeRate, setFeeRate] = useState();
- const [priceRange, setPriceRange] = useState();
+ const [tokenA, setTokenA] = useState(null);
+ const [tokenB, setTokenB] = useState(null);
+ const [startPrice] = useState("130621891405341611593710811006");
+ const tokenAAmountInput = useTokenAmountInput(tokenA);
+ const tokenBAmountInput = useTokenAmountInput(tokenB);
+ const [swapFeeTier, setSwapFeeTier] = useState(null);
+ const [priceRanges] = useState([TEMP_CUSTOM_PRICE_RANGE]);
+ const [priceRange, setPriceRange] = useState(null);
+ const [pools] = useState([]);
+ const { connected: connectedWallet, account, connectAdenaClient } = useWallet();
+ const { slippage } = useSlippage();
+ const { updateTokenPrices } = useTokenData();
+ const { openModal: openConfirmModal } = useEarnAddLiquidityConfirmModal({
+ tokenA,
+ tokenB,
+ tokenAAmountInput,
+ tokenBAmountInput,
+ currentPrice: startPrice,
+ priceRange,
+ slippage,
+ swapFeeTier,
+ });
const priceRangeSummary: PriceRangeSummary = useMemo(() => {
- if (!priceRange) {
- return {
- depositRatio: "-",
- feeBoost: "-",
- estimatedApr: "-",
- };
- }
return {
- depositRatio: "40.2% ETH / 59.8% GNOS",
- feeBoost: "x10.23",
- estimatedApr: "19.22%",
+ depositRatio: "-",
+ feeBoost: "-",
+ estimatedApr: "-",
};
- }, [priceRange]);
+ }, []);
- const selectFeeTier = useCallback((feeRate: FEE_RATE_OPTION) => {
- setFeeRate(feeRate);
+ const submitType: AddLiquiditySubmitType = useMemo(() => {
+ if (!connectedWallet) {
+ return "CONNECT_WALLET";
+ }
+ if (!swapFeeTier) {
+ return "ENTER_AMOUNT";
+ }
+ if (!priceRange) {
+ return "INVALID_RANGE";
+ }
+ if (!account?.balances || account.balances.length === 0) {
+ return "INSUFFICIENT_BALANCE";
+ }
+ if (BigNumber(account.balances[0].amount).isLessThanOrEqualTo(1)) {
+ return "INSUFFICIENT_BALANCE";
+ }
+ if (BigNumber(tokenAAmountInput.amount).isLessThanOrEqualTo(0)) {
+ return "ENTER_AMOUNT";
+ }
+ if (BigNumber(tokenBAmountInput.amount).isLessThanOrEqualTo(0)) {
+ return "ENTER_AMOUNT";
+ }
+ return "CREATE_POOL";
+ }, [account?.balances, connectedWallet, priceRange, swapFeeTier, tokenAAmountInput.amount, tokenBAmountInput.amount]);
+
+ useEffect(() => {
+ updateTokenPrices();
}, []);
- const selectPriceRange = useCallback((priceRange: PriceRangeType) => {
+ const selectSwapFeeTier = useCallback((swapFeeTier: SwapFeeTierType) => {
+ setSwapFeeTier(swapFeeTier);
+ }, []);
+
+ const changePriceRange = useCallback((priceRange: AddLiquidityPriceRage) => {
setPriceRange(priceRange);
}, []);
- const changeToken0 = useCallback((token: TokenInfo) => {
- setToken0(token);
+ const changeTokenA = useCallback((token: TokenModel) => {
+ setTokenA(token);
}, []);
- const changeToken1 = useCallback((token: TokenInfo) => {
- setToken1(token);
+ const changeTokenB = useCallback((token: TokenModel) => {
+ setTokenB(token);
}, []);
+ const submit = useCallback(() => {
+ if (submitType === "CONNECT_WALLET") {
+ connectAdenaClient();
+ return;
+ }
+ if (submitType !== "CREATE_POOL") {
+ return;
+ }
+ if (!tokenA || !tokenB || !priceRange || !swapFeeTier) {
+ return;
+ }
+ openConfirmModal();
+ }, [submitType, tokenA, tokenB, priceRange, swapFeeTier, openConfirmModal, connectAdenaClient]);
+
return (
);
};
diff --git a/packages/web/src/containers/earn-add-liquidity-container/earn-add-liquidity-dummy.ts b/packages/web/src/containers/earn-add-liquidity-container/earn-add-liquidity-dummy.ts
deleted file mode 100644
index 51cfba15a..000000000
--- a/packages/web/src/containers/earn-add-liquidity-container/earn-add-liquidity-dummy.ts
+++ /dev/null
@@ -1,263 +0,0 @@
-import { FEE_RATE_OPTION, PriceRangeType } from "@constants/option.constant";
-import {
- AddLiquidityFeeTier,
- AddLiquidityPriceRage,
-} from "./EarnAddLiquidityContainer";
-
-export const DUMMY_FEE_TIERS: AddLiquidityFeeTier[] = [
- {
- feeRate: FEE_RATE_OPTION.FEE_01,
- description: "Best for very stable pairs",
- range: "12",
- },
- {
- feeRate: FEE_RATE_OPTION.FEE_05,
- description: "Best for stable pairs",
- range: "67",
- },
- {
- feeRate: FEE_RATE_OPTION.FEE_3,
- description: "Best for most pairs",
- range: "21",
- },
- {
- feeRate: FEE_RATE_OPTION.FEE_1,
- description: "Best for exotic pairs",
- range: "0",
- },
-];
-
-export const DUMMY_PRICE_RANGE_MAP: {
- [key in PriceRangeType]: AddLiquidityPriceRage | undefined;
-} = {
- Active: {
- range: {
- minPrice: "1300.5",
- maxPrice: "1589.5",
- minTick: "1.35",
- maxTick: "1.65",
- },
- apr: "105",
- },
- Passive: {
- range: {
- minPrice: "722.5",
- maxPrice: "2890",
- minTick: "0.75",
- maxTick: "3.0",
- },
- apr: "80",
- },
- Custom: {
- range: {
- minPrice: "722.5",
- maxPrice: "2890",
- minTick: "0.75",
- maxTick: "3.0",
- },
- apr: "80",
- },
-};
-
-export const DUMMY_POOL_TICKS = [
- {
- value: "1.073961138254532",
- price: "0.9802084688126746",
- tick: 0.9802084688126746,
- },
- {
- value: "3.073059326138406",
- price: "0.9811886772814872",
- tick: 0.9811886772814872,
- },
- {
- value: "-18.134954261462838",
- price: "0.9821698659587685",
- tick: 0.9821698659587685,
- },
- {
- value: "3.66521159311813",
- price: "0.9831520358247272",
- tick: 0.9831520358247272,
- },
- {
- value: "39.175366555249056",
- price: "0.9841351878605519",
- tick: 0.9841351878605519,
- },
- {
- value: "3.2228815099962276",
- price: "0.9851193230484123",
- tick: 0.9851193230484123,
- },
- {
- value: "48.468806410711174",
- price: "0.9861044423714606",
- tick: 0.9861044423714606,
- },
- {
- value: "54.92851483124098",
- price: "0.9870905468138319",
- tick: 0.9870905468138319,
- },
- {
- value: "34.93060212988513",
- price: "0.9880776373606457",
- tick: 0.9880776373606457,
- },
- {
- value: "58.898158538319116",
- price: "0.9890657149980062",
- tick: 0.9890657149980062,
- },
- {
- value: "42.511148168134525",
- price: "0.9900547807130041",
- tick: 0.9900547807130041,
- },
- {
- value: "59.09467848301594",
- price: "0.991044835493717",
- tick: 0.991044835493717,
- },
- {
- value: "63.57224846391675",
- price: "0.9920358803292106",
- tick: 0.9920358803292106,
- },
- {
- value: "60.51934609120303",
- price: "0.9930279162095397",
- tick: 0.9930279162095397,
- },
- {
- value: "78.35287274759652",
- price: "0.9940209441257492",
- tick: 0.9940209441257492,
- },
- {
- value: "82.07006674582651",
- price: "0.9950149650698747",
- tick: 0.9950149650698747,
- },
- {
- value: "86.64043005515893",
- price: "0.9960099800349446",
- tick: 0.9960099800349446,
- },
- {
- value: "89.17600329920441",
- price: "0.9970059900149794",
- tick: 0.9970059900149794,
- },
- {
- value: "100",
- price: "0.9980029960049942",
- tick: 0.9980029960049942,
- },
- {
- value: "100",
- price: "0.9990009990009991",
- tick: 0.9990009990009991,
- },
- {
- value: "100",
- price: "1",
- tick: 1,
- },
- {
- value: "100",
- price: "1.001",
- tick: 1.001,
- },
- {
- value: "100",
- price: "1.0020009999999997",
- tick: 1.0020009999999997,
- },
- {
- value: "87.45649170423994",
- price: "1.0030030009999997",
- tick: 1.0030030009999997,
- },
- {
- value: "83.44334373900584",
- price: "1.0040060040009995",
- tick: 1.0040060040009995,
- },
- {
- value: "80.77769393647627",
- price: "1.0050100100050003",
- tick: 1.0050100100050003,
- },
- {
- value: "67.41998453824951",
- price: "1.0060150200150053",
- tick: 1.0060150200150053,
- },
- {
- value: "75.21903648844791",
- price: "1.0070210350350202",
- tick: 1.0070210350350202,
- },
- {
- value: "78.26482882716132",
- price: "1.0080280560700552",
- tick: 1.0080280560700552,
- },
- {
- value: "55.7200253355104",
- price: "1.009036084126125",
- tick: 1.009036084126125,
- },
- {
- value: "56.96694253686708",
- price: "1.0100451202102512",
- tick: 1.0100451202102512,
- },
- {
- value: "42.085152964383454",
- price: "1.0110551653304611",
- tick: 1.0110551653304611,
- },
- {
- value: "29.59451072646806",
- price: "1.0120662204957915",
- tick: 1.0120662204957915,
- },
- {
- value: "21.211739395363708",
- price: "1.0130782867162873",
- tick: 1.0130782867162873,
- },
- {
- value: "51.47281147160517",
- price: "1.0140913650030035",
- tick: 1.0140913650030035,
- },
- {
- value: "14.497330891553844",
- price: "1.0151054563680064",
- tick: 1.0151054563680064,
- },
- {
- value: "10.731671509578433",
- price: "1.0161205618243743",
- tick: 1.0161205618243743,
- },
- {
- value: "17.80906412507565",
- price: "1.0171366823861985",
- tick: 1.0171366823861985,
- },
- {
- value: "38.586301175029746",
- price: "1.0181538190685846",
- tick: 1.0181538190685846,
- },
- {
- value: "9.50528530112382",
- price: "1.0191719728876532",
- tick: 1.0191719728876532,
- },
-];
diff --git a/packages/web/src/containers/earn-my-position-container/EarnMyPositionContainer.tsx b/packages/web/src/containers/earn-my-position-container/EarnMyPositionContainer.tsx
index f3af9b63a..5d53322c4 100644
--- a/packages/web/src/containers/earn-my-position-container/EarnMyPositionContainer.tsx
+++ b/packages/web/src/containers/earn-my-position-container/EarnMyPositionContainer.tsx
@@ -1,7 +1,8 @@
-import { generateBarAreaDatas } from "@common/utils/test-util";
import EarnMyPositions from "@components/earn/earn-my-positions/EarnMyPositions";
+import { usePoolData } from "@hooks/pool/use-pool-data";
+import { useWallet } from "@hooks/wallet/use-wallet";
import { useRouter } from "next/router";
-import React, { useCallback, useEffect, useState } from "react";
+import React, { useCallback, useEffect } from "react";
import { ValuesType } from "utility-types";
export const POSITION_CONTENT_LABEL = {
@@ -54,111 +55,6 @@ export interface PoolPosition {
ticks: string[];
}
-export const dummyPosition: PoolPosition[] = [
- {
- tokenPair: {
- tokenA: {
- path: Math.floor(Math.random() * 50 + 1).toString(),
- name: "HEX",
- symbol: "HEX",
- amount: {
- value: "18,500.18",
- denom: "gnot",
- },
- logoURI:
- "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39/logo.png",
- },
- tokenB: {
- path: Math.floor(Math.random() * 50 + 1).toString(),
- name: "USDCoin",
- symbol: "USDC",
- amount: {
- value: "18,500.18",
- denom: "gnot",
- },
- logoURI:
- "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
- },
- },
- rewards: [],
- feeRate: "0.05%",
- stakeType: "Unstaked",
- value: "$18,500.10",
- apr: "108.21%",
- inRange: false,
- currentPriceAmount: "1184.24 GNOS per ETH",
- minPriceAmount: "1.75 GNOT Per GNOS",
- maxPriceAmount: "2.25 GNOT Per GNOS",
- currentTick: 18,
- minTick: 10,
- maxTick: 110,
- minLabel: "-80%",
- maxLabel: "-10%",
- ticks: generateBarAreaDatas()
- },
- {
- tokenPair: {
- tokenA: {
- path: Math.floor(Math.random() * 50 + 1).toString(),
- name: "HEX",
- symbol: "HEX",
- amount: {
- value: "18,500.18",
- denom: "gnot",
- },
- logoURI:
- "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39/logo.png",
- },
- tokenB: {
- path: Math.floor(Math.random() * 50 + 1).toString(),
- name: "USDCoin",
- symbol: "USDC",
- amount: {
- value: "18,500.18",
- denom: "gnot",
- },
- logoURI:
- "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
- },
- },
- rewards: [
- {
- token: {
- path: Math.floor(Math.random() * 50 + 1).toString(),
- name: "HEX",
- symbol: "HEX",
- amount: {
- value: "18,500.18",
- denom: "gnot",
- },
- logoURI:
- "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39/logo.png",
- },
- amount: {
- value: "18,500.18",
- denom: "gnot",
- },
- },
- ],
- feeRate: "0.05%",
- stakeType: "Staked",
- value: "$18,500.10",
- apr: "108.21%",
- inRange: true,
- currentPriceAmount: "1184.24 GNOS per ETH",
- minPriceAmount: "1.75 GNOT Per GNOS",
- maxPriceAmount: "2.25 GNOT Per GNOS",
- currentTick: 24,
- minTick: 120,
- maxTick: 200,
- minLabel: "-30%",
- maxLabel: "50%",
- ticks: generateBarAreaDatas()
- },
-];
-
-export const dummyPositionList = () => [...dummyPosition, ...dummyPosition];
-
interface EarnMyPositionContainerProps {
loadMore?: boolean;
}
@@ -167,19 +63,16 @@ const EarnMyPositionContainer: React.FC<
EarnMyPositionContainerProps
> = () => {
const router = useRouter();
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- const [currentIndex, setCurrentIndex] = useState(0);
- const [connected, setConnected] = useState(true);
- const [positions, setPositions] = useState([]);
+ const { connected, connectAdenaClient } = useWallet();
+ const { isFetchedPositions, myPositions, updatePositions } = usePoolData();
useEffect(() => {
- const dummyPositions = dummyPositionList();
- setPositions(dummyPositions);
+ updatePositions();
}, []);
const connect = useCallback(() => {
- setConnected(true);
- }, []);
+ connectAdenaClient();
+ }, [connectAdenaClient]);
const moveEarnAdd = useCallback(() => {
router.push("/earn/add");
@@ -193,8 +86,8 @@ const EarnMyPositionContainer: React.FC<
diff --git a/packages/web/src/containers/governance-container/GovernanceContainer.tsx b/packages/web/src/containers/governance-container/GovernanceContainer.tsx
new file mode 100644
index 000000000..38c9069ae
--- /dev/null
+++ b/packages/web/src/containers/governance-container/GovernanceContainer.tsx
@@ -0,0 +1,44 @@
+// TODO : remove eslint-disable after work
+/* eslint-disable */
+import React, { useState } from "react";
+import { useQuery } from "@tanstack/react-query";
+import GovernanceSummary from "@components/governance/governance-summary/GovernanceSummary";
+
+export interface GovernanceDetailInfoProps {
+ totalXGnosIssued: string;
+ communityPool: string;
+ passedProposals: string;
+ activeProposals: string;
+}
+
+const initialGovernanceDetailInfo: GovernanceDetailInfoProps = {
+ totalXGnosIssued: "59,144,225",
+ communityPool: "2,412,148",
+ passedProposals: "42",
+ activeProposals: "2",
+};
+
+async function fetchGovernanceDetailInfo(): Promise {
+ return Promise.resolve({
+ totalXGnosIssued: "59,144,225",
+ communityPool: "2,412,148",
+ passedProposals: "42",
+ activeProposals: "2",
+ });
+}
+
+const GovernanceContainer: React.FC = () => {
+ const {
+ data: governanceDetailInfo,
+ } = useQuery({
+ queryKey: ["governanceDetailInfo"],
+ queryFn: () => {
+ return fetchGovernanceDetailInfo();
+ },
+ initialData: initialGovernanceDetailInfo,
+ });
+
+ return ;
+};
+
+export default GovernanceContainer;
diff --git a/packages/web/src/containers/header-container/HeaderContainer.tsx b/packages/web/src/containers/header-container/HeaderContainer.tsx
index 2557560c4..30d953e13 100644
--- a/packages/web/src/containers/header-container/HeaderContainer.tsx
+++ b/packages/web/src/containers/header-container/HeaderContainer.tsx
@@ -2,7 +2,7 @@
/* eslint-disable */
import Header from "@components/common/header/Header";
import { useRouter } from "next/router";
-import React, { useState, useCallback } from "react";
+import React, { useState, useCallback, useEffect } from "react";
import { MATH_NEGATIVE_TYPE } from "@constants/option.constant";
import { type TokenInfo } from "@models/token/token-info";
import { useQuery } from "@tanstack/react-query";
@@ -77,7 +77,7 @@ const HeaderContainer: React.FC = () => {
const [searchMenuToggle, setSearchMenuToggle] = useState(false);
const [keyword, setKeyword] = useState("");
const { breakpoint } = useWindowSize();
- const { account, connected, connectAdenaClient } = useWallet();
+ const { account, connected, initSession, connectAdenaClient } = useWallet();
const {
isFetched,
error,
@@ -87,6 +87,12 @@ const HeaderContainer: React.FC = () => {
queryFn: () => fetchTokens(keyword),
});
+ useEffect(() => {
+ if (window?.adena) {
+ initSession();
+ }
+ }, []);
+
const onSideMenuToggle = () => {
setSideMenuToggle(prev => !prev);
};
diff --git a/packages/web/src/containers/home-swap-container/HomeSwapContainer.tsx b/packages/web/src/containers/home-swap-container/HomeSwapContainer.tsx
index e6528b40d..70020d7a2 100644
--- a/packages/web/src/containers/home-swap-container/HomeSwapContainer.tsx
+++ b/packages/web/src/containers/home-swap-container/HomeSwapContainer.tsx
@@ -1,21 +1,68 @@
+import { SwapDirectionType } from "@common/values";
import HomeSwap from "@components/home/home-swap/HomeSwap";
+import { useSlippage } from "@hooks/common/use-slippage";
+import { useTokenData } from "@hooks/token/use-token-data";
+import { SwapTokenInfo } from "@models/swap/swap-token-info";
+import { TokenModel } from "@models/token/token-model";
+import { numberToUSD } from "@utils/number-utils";
+import BigNumber from "bignumber.js";
import { useRouter } from "next/router";
-import React, { useCallback, useEffect, useState } from "react";
+import React, { useCallback, useMemo, useState } from "react";
const HomeSwapContainer: React.FC = () => {
const router = useRouter();
- const [width, setWidth] = useState(Number);
- const handleResize = () => {
- setWidth(window.innerWidth);
- };
-
- useEffect(() => {
- handleResize();
- window.addEventListener("resize", handleResize);
- return () => {
- window.removeEventListener("resize", handleResize);
+ const { tokenPrices, balances } = useTokenData();
+ const [tokenA] = useState(null);
+ const [tokenAAmount] = useState("1000");
+ const [tokenB] = useState(null);
+ const [tokenBAmount] = useState("0");
+ const [swapDirection] = useState("EXACT_IN");
+ const { slippage } = useSlippage();
+
+ const tokenABalance = useMemo(() => {
+ if (tokenA && balances[tokenA.priceId]) {
+ return BigNumber(balances[tokenA.priceId] || 0).toFormat();
+ }
+ return "-";
+ }, [balances, tokenA]);
+
+ const tokenBBalance = useMemo(() => {
+ if (tokenB && balances[tokenB.priceId]) {
+ return BigNumber(balances[tokenB.priceId] || 0).toFormat();
+ }
+ return "-";
+ }, [balances, tokenB]);
+
+ const tokenAUSD = useMemo(() => {
+ if (!tokenA || !tokenPrices[tokenA.priceId]) {
+ return Number.NaN;
+ }
+ return BigNumber(tokenAAmount).multipliedBy(tokenPrices[tokenA.priceId].usd).toNumber();
+ }, [tokenA, tokenAAmount, tokenPrices]);
+
+ const tokenBUSD = useMemo(() => {
+ if (!tokenB || !tokenPrices[tokenB.priceId]) {
+ return Number.NaN;
+ }
+ return BigNumber(tokenBAmount).multipliedBy(tokenPrices[tokenB.priceId].usd).toNumber();
+ }, [tokenB, tokenBAmount, tokenPrices]);
+
+ const swapTokenInfo: SwapTokenInfo = useMemo(() => {
+ return {
+ tokenA,
+ tokenAAmount,
+ tokenABalance,
+ tokenAUSD,
+ tokenAUSDStr: numberToUSD(tokenAUSD),
+ tokenB,
+ tokenBAmount,
+ tokenBBalance,
+ tokenBUSD,
+ tokenBUSDStr: numberToUSD(tokenBUSD),
+ direction: swapDirection,
+ slippage
};
- }, []);
+ }, [slippage, swapDirection, tokenA, tokenAAmount, tokenABalance, tokenAUSD, tokenB, tokenBAmount, tokenBBalance, tokenBUSD]);
const swapNow = useCallback(() => {
router.push("/swap?from=GNOT&to=GNOS");
@@ -23,32 +70,8 @@ const HomeSwapContainer: React.FC = () => {
return (
);
};
diff --git a/packages/web/src/containers/incentivized-pool-card-list-container/IncentivizedPoolCardListContainer.tsx b/packages/web/src/containers/incentivized-pool-card-list-container/IncentivizedPoolCardListContainer.tsx
index 69080e65f..cda10f64c 100644
--- a/packages/web/src/containers/incentivized-pool-card-list-container/IncentivizedPoolCardListContainer.tsx
+++ b/packages/web/src/containers/incentivized-pool-card-list-container/IncentivizedPoolCardListContainer.tsx
@@ -1,9 +1,9 @@
/* eslint-disable */
-import React, { useEffect, useState } from "react";
+import React, { useEffect, useMemo, useState } from "react";
import IncentivizedPoolCardList from "@components/earn/incentivized-pool-card-list/IncentivizedPoolCardList";
import { ValuesType } from "utility-types";
-import { poolDummy } from "@components/earn/incentivized-pool-card/incentivized-pool-dummy";
import { useRouter } from "next/router";
+import { usePoolData } from "@hooks/pool/use-pool-data";
export interface PoolListProps {
logo: string[];
name: string[];
@@ -25,10 +25,11 @@ export const POOL_CONTENT_TITLE = {
export type POOL_CONTENT_TITLE = ValuesType;
const IncentivizedPoolCardListContainer: React.FC = () => {
- const [currentIndex, setCurrentIndex] = useState(0);
+ const [currentIndex] = useState(0);
+ const [page, setPage] = useState(1);
const router = useRouter();
- const [width, setWidth] = useState(Number);
const [mobile, setMobile] = useState(false);
+ const { incentivizedPools, isFetchedPools } = usePoolData();
const handleResize = () => {
if (typeof window !== "undefined") {
@@ -44,15 +45,19 @@ const IncentivizedPoolCardListContainer: React.FC = () => {
};
}, []);
- const routeItem = (id: number) => {
+ const loadMore = useMemo(() => {
+ return incentivizedPools.length > page * 8;
+ }, [incentivizedPools.length, page]);
+
+ const routeItem = (id: string) => {
router.push(`/earn/pool/${id}`);
};
return (
{ }}
currentIndex={currentIndex}
routeItem={routeItem}
diff --git a/packages/web/src/containers/pool-list-container/PoolListContainer.tsx b/packages/web/src/containers/pool-list-container/PoolListContainer.tsx
index 71c4539ac..950a3194e 100644
--- a/packages/web/src/containers/pool-list-container/PoolListContainer.tsx
+++ b/packages/web/src/containers/pool-list-container/PoolListContainer.tsx
@@ -1,13 +1,12 @@
-import React, { useCallback, useState } from "react";
+import React, { useCallback, useEffect, useMemo, useState } from "react";
import { type FeeOptions } from "@common/values/data-constant";
import PoolList from "@components/earn/pool-list/PoolList";
import { type TokenPairInfo } from "@models/token/token-pair-info";
-import { useQuery } from "@tanstack/react-query";
import { ValuesType } from "utility-types";
import { useAtom } from "jotai";
import { CommonState } from "@states/index";
import { useRouter } from "next/router";
-import { generateBarAreaDatas } from "@common/utils/test-util";
+import { usePoolData } from "@hooks/pool/use-pool-data";
export interface Pool {
poolId: string;
@@ -50,135 +49,33 @@ export const POOL_TYPE = {
export type POOL_TYPE = ValuesType;
-const SORT_PARAMS: { [key in TABLE_HEAD]: string } = {
- "Pool Name": "name",
- Liquidity: "liquidity",
- "Volume (24h)": "volume",
- "Fees (24h)": "fees",
- APR: "apr",
- Rewards: "rewards",
- "Liquidity Plot": "liquidity_plot",
-};
-
-export const dummyPoolList: Pool[] = [
- {
- poolId: Math.floor(Math.random() * 50 + 1).toString(),
- tokenPair: {
- tokenA: {
- path: Math.floor(Math.random() * 50 + 1).toString(),
- name: "HEX",
- symbol: "HEX",
- logoURI:
- "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39/logo.png",
- },
- tokenB: {
- path: Math.floor(Math.random() * 50 + 1).toString(),
- name: "USDCoin",
- symbol: "USDC",
- logoURI:
- "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
- },
- },
- feeRate: "0.01%",
- liquidity: "$12,090.41M",
- apr: "$311,421.12M",
- volume24h: "$311,421.12M",
- fees24h: "$311,421.12M",
- rewards: [
- "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xdAC17F958D2ee523a2206206994597C13D831ec7/logo.png",
- "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/logo.png",
- ],
- incentiveType: POOL_TYPE.INCENTIVIZED,
- tickInfo: {
- currentTick: 22,
- ticks: generateBarAreaDatas()
- }
- },
- {
- poolId: "2",
- tokenPair: {
- tokenA: {
- path: Math.floor(Math.random() * 50 + 1).toString(),
- name: "HEX",
- symbol: "HEX",
- logoURI:
- "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39/logo.png",
- },
- tokenB: {
- path: Math.floor(Math.random() * 50 + 1).toString(),
- name: "USDCoin",
- symbol: "USDC",
- logoURI:
- "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
- },
- },
- feeRate: "0.05%",
- liquidity: "$12,090.41M",
- apr: "$311,421.12M",
- volume24h: "$311,421.12M",
- fees24h: "$311,421.12M",
- rewards: [
- "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x4E15361FD6b4BB609Fa63C81A2be19d873717870/logo.png",
- "https://assets.coingecko.com/coins/images/29223/large/Favicon_200x200px.png?1677480836",
- ],
- incentiveType: POOL_TYPE.ALL,
- tickInfo: {
- currentTick: 29,
- ticks: generateBarAreaDatas()
- }
- },
- {
- poolId: "3",
- tokenPair: {
- tokenA: {
- path: Math.floor(Math.random() * 50 + 1).toString(),
- name: "HEX",
- symbol: "HEX",
- logoURI:
- "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39/logo.png",
- },
- tokenB: {
- path: Math.floor(Math.random() * 50 + 1).toString(),
- name: "USDCoin",
- symbol: "USDC",
- logoURI:
- "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
- },
- },
- feeRate: "0.3%",
- liquidity: "$12,090.41M",
- apr: "$311,421.12M",
- volume24h: "$311,421.12M",
- fees24h: "$311,421.12M",
- rewards: [
- "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x4E15361FD6b4BB609Fa63C81A2be19d873717870/logo.png",
- "https://assets.coingecko.com/coins/images/29223/large/Favicon_200x200px.png?1677480836",
- ],
- incentiveType: POOL_TYPE.NON_INCENTIVIZED,
- tickInfo: {
- currentTick: 14,
- ticks: generateBarAreaDatas()
- }
- },
-];
-
-async function fetchPools(
- type: POOL_TYPE, // eslint-disable-line
- page: number, // eslint-disable-line
- keyword: string, // eslint-disable-line
- sortKey?: string, // eslint-disable-line
- direction?: string, // eslint-disable-line
-): Promise {
- return new Promise(resolve => setTimeout(resolve, 2000)).then(() =>
- Promise.resolve([
- ...dummyPoolList,
- ...dummyPoolList,
- ...dummyPoolList,
- ...dummyPoolList,
- ...dummyPoolList,
- ]),
- );
-}
+// const SORT_PARAMS: { [key in TABLE_HEAD]: string } = {
+// "Pool Name": "name",
+// Liquidity: "liquidity",
+// "Volume (24h)": "volume",
+// "Fees (24h)": "fees",
+// APR: "apr",
+// Rewards: "rewards",
+// "Liquidity Plot": "liquidity_plot",
+// };
+
+// async function fetchPools(
+// type: POOL_TYPE, // eslint-disable-line
+// page: number, // eslint-disable-line
+// keyword: string, // eslint-disable-line
+// sortKey?: string, // eslint-disable-line
+// direction?: string, // eslint-disable-line
+// ): Promise {
+// return new Promise(resolve => setTimeout(resolve, 2000)).then(() =>
+// Promise.resolve([
+// ...dummyPoolList,
+// ...dummyPoolList,
+// ...dummyPoolList,
+// ...dummyPoolList,
+// ...dummyPoolList,
+// ]),
+// );
+// }
const PoolListContainer: React.FC = () => {
const [poolType, setPoolType] = useState(POOL_TYPE.ALL);
@@ -188,37 +85,38 @@ const PoolListContainer: React.FC = () => {
const [searchIcon, setSearchIcon] = useState(false);
const [breakpoint] = useAtom(CommonState.breakpoint);
const router = useRouter();
+ const { poolListInfos, isFetchedPools, updatePools } = usePoolData();
- const routeItem = (id: number) => {
+ useEffect(() => {
+ updatePools();
+ }, []);
+
+ const sortedPoolListInfos = useMemo(() => {
+ return poolListInfos.filter(info => {
+ if (poolType !== "All") {
+ return info.incentiveType === poolType;
+ }
+ if (keyword !== "") {
+ return info.tokenA.name.toLowerCase().includes(keyword.toLowerCase()) ||
+ info.tokenB.name.toLowerCase().includes(keyword.toLowerCase()) ||
+ info.tokenA.symbol.toLowerCase().includes(keyword.toLowerCase()) ||
+ info.tokenB.symbol.toLowerCase().includes(keyword.toLowerCase());
+ }
+ return true;
+ });
+ }, [keyword, poolListInfos, poolType]);
+
+ const totalPage = useMemo(() => {
+ return sortedPoolListInfos.length / 20 + 1;
+ }, [sortedPoolListInfos.length]);
+
+ const routeItem = (id: string) => {
router.push(`/earn/pool/${id}`);
};
const onTogleSearch = () => {
setSearchIcon(prev => !prev);
};
- const {
- isFetched,
- error,
- data: pools,
- } = useQuery({
- queryKey: [
- "pools",
- poolType,
- page,
- keyword,
- sortOption?.key,
- sortOption?.direction,
- ],
- queryFn: () =>
- fetchPools(
- poolType,
- page,
- keyword,
- sortOption && SORT_PARAMS[sortOption.key],
- sortOption?.direction,
- ),
- });
-
const changePoolType = useCallback((newType: string) => {
switch (newType) {
case POOL_TYPE.ALL:
@@ -268,15 +166,14 @@ const PoolListContainer: React.FC = () => {
return (
{
+ return {
+ id: Math.floor(Math.random() * 500 + 1).toString(),
+ title: "#7 Proposal Title",
+ label: "Community Pool Spend",
+ status: statusArray[
+ Math.floor(Math.random() * statusArray.length)
+ ] as ProposalStatus,
+ timeEnd: "2023-08-01, 12:00:00 UTC+9",
+ abstainOfQuorum: 30,
+ noOfQuorum: 20,
+ currentValue: 20000,
+ maxValue: 40000,
+ yesOfQuorum: 50,
+ votingPower: 14245,
+ icon: "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
+ currency: "USDC",
+ typeVote: "YES",
+ description:
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus nisiorci, ultrices sit amet mi eget, efficitur elementum tellus. Integeraugue purus, rutrum eu pretium sit amet, varius in quam.Lorem ipsumdolor sit amet, consectetur adipiscing elit. Phasellus nisi orci,ultrices sit amet mi eget, efficitur elementum tellus. Integer auguepurus, rutrum eu pretium sit amet, varius in quam.Lorem ipsum dolor sitamet, consectetur adipiscing elit. Phasellus nisi orci, ultrices sitamet mi eget, efficitur elementum tellus. Integer augue purus, rutrum eupretium sit amet, varius in quam.Lorem ipsum dolor sit amet, consecteturadipiscing elit. Phasellus nisi orci, ultrices sit amet mi eget,efficitur elementum tellus. Integer augue purus, rutrum eu pretium sitamet, varius in quam.Lorem ipsum dolor sit amet, consectetur adipiscingelit. Phasellus nisi orci, ultrices sit amet mi eget, efficiturelementum tellus. Integer augue purus, rutrum eu pretium sit amet,varius in quam.Lorem ipsum dolor sit amet, consectetur adipiscing elit.Phasellus nisi orci, ultrices sit amet mi eget, efficitur elementumtellus. Integer augue purus, rutrum eu pretium sit amet, varius inquam.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellusnisi orci, ultrices sit amet mi eget, efficitur elementum tellus.Integer augue purus, rutrum eu pretium sit amet, varius in quam.Loremipsum dolor sit amet, consectetur adipiscing elit. Phasellus nisi orci,ultrices sit amet mi eget, efficitur elementum tellus. Integer auguepurus, rutrum eu pretium sit amet, varius in quam.Lorem ipsum dolor sitamet, consectetur adipiscing elit. Phasellus nisi orci, ultrices sitamet mi eget, efficitur elementum tellus. Integer augue purus, rutrum eupretium sit amet, varius in quam.Lorem ipsum dolor sit amet, consecteturadipiscing elit. Phasellus nisi orci, ultrices sit amet mi eget,efficitur elementum tellus. Integer augue purus, rutrum eu pretium sitamet, varius in quam.Lorem ipsum dolor sit amet, consectetur adipiscingelit. Phasellus nisi orci, ultrices sit amet mi eget, efficiturelementum tellus. Integer augue purus, rutrum eu pretium sit amet,varius in quam.Lorem ipsum dolor sit amet, consectetur adipiscing elit.Phasellus nisi orci, ultrices sit amet mi eget, efficitur elementumtellus. Integer augue purus, rutrum eu pretium sit amet, varius inquam.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellusnisi orci, ultrices sit amet mi eget, efficitur elementum tellus.Integer augue purus, rutrum eu pretium sit amet, varius in quam.Loremipsum dolor sit amet, consectetur adipiscing elit. Phasellus nisi orci,ultrices sit amet mi eget, efficitur elementum tellus. Integer auguepurus, rutrum eu pretium sit amet, varius in quam.Lorem ipsum dolor sitamet, consectetur adipiscing elit. Phasellus nisi orci, ultrices sitamet mi eget, efficitur elementum tellus. Integer augue purus, rutrum eupretium sit amet, varius in quam.",
+ };
+};
+
+async function fetchListProposal(
+ isShowCancelled: boolean,
+): Promise {
+ const data = [
+ createDummyProposalItem(),
+ createDummyProposalItem(),
+ createDummyProposalItem(),
+ createDummyProposalItem(),
+ createDummyProposalItem(),
+ createDummyProposalItem(),
+ createDummyProposalItem(),
+ ];
+ if (isShowCancelled) return data.filter(item => item.status === "CANCELLED");
+ return Promise.resolve(data);
+}
+
+async function fetchProposalDetail() {
+ return Promise.resolve(createDummyProposalItem());
+}
+
+const ProposalListContainer: React.FC = () => {
+ const [isShowCancelled, toggleShowCancelled] = useState(false);
+ const [isShowProposalModal, setIsShowProposalModal] = useState(false);
+ const [isShowCreateProposal, setIsShowCreateProposal] = useState(false);
+ const [isConnected] = useState(false);
+
+ const { breakpoint } = useWindowSize();
+
+ const { data: proposalList = [] } = useQuery({
+ queryKey: ["proposalList", isShowCancelled],
+ queryFn: async () => {
+ return await fetchListProposal(isShowCancelled);
+ },
+ });
+
+ const { data: proposalDetail } = useQuery({
+ queryKey: ["proposalDetail"],
+ queryFn: async () => {
+ return await fetchProposalDetail();
+ },
+ initialData: {
+ id: Math.floor(Math.random() * 500 + 1).toString(),
+ title: "#7 Proposal Title",
+ label: "Community Pool Spend",
+ status: "ACTIVE",
+ timeEnd: "2023-08-01, 12:00:00 UTC+9",
+ abstainOfQuorum: 30,
+ noOfQuorum: 20,
+ currentValue: 20000,
+ maxValue: 40000,
+ yesOfQuorum: 50,
+ votingPower: 14245,
+ icon: "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
+ currency: "xGNOS",
+ typeVote: "",
+ description:
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus nisiorci, ultrices sit amet mi eget, efficitur elementum tellus. Integeraugue purus, rutrum eu pretium sit amet, varius in quam.Lorem ipsumdolor sit amet, consectetur adipiscing elit. Phasellus nisi orci,ultrices sit amet mi eget, efficitur elementum tellus. Integer auguepurus, rutrum eu pretium sit amet, varius in quam.Lorem ipsum dolor sitamet, consectetur adipiscing elit. Phasellus nisi orci, ultrices sitamet mi eget, efficitur elementum tellus. Integer augue purus, rutrum eupretium sit amet, varius in quam.Lorem ipsum dolor sit amet, consecteturadipiscing elit. Phasellus nisi orci, ultrices sit amet mi eget,efficitur elementum tellus. Integer augue purus, rutrum eu pretium sitamet, varius in quam.Lorem ipsum dolor sit amet, consectetur adipiscingelit. Phasellus nisi orci, ultrices sit amet mi eget, efficiturelementum tellus. Integer augue purus, rutrum eu pretium sit amet,varius in quam.Lorem ipsum dolor sit amet, consectetur adipiscing elit.Phasellus nisi orci, ultrices sit amet mi eget, efficitur elementumtellus. Integer augue purus, rutrum eu pretium sit amet, varius inquam.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellusnisi orci, ultrices sit amet mi eget, efficitur elementum tellus.Integer augue purus, rutrum eu pretium sit amet, varius in quam.Loremipsum dolor sit amet, consectetur adipiscing elit. Phasellus nisi orci,ultrices sit amet mi eget, efficitur elementum tellus. Integer auguepurus, rutrum eu pretium sit amet, varius in quam.Lorem ipsum dolor sitamet, consectetur adipiscing elit. Phasellus nisi orci, ultrices sitamet mi eget, efficitur elementum tellus. Integer augue purus, rutrum eupretium sit amet, varius in quam.Lorem ipsum dolor sit amet, consecteturadipiscing elit. Phasellus nisi orci, ultrices sit amet mi eget,efficitur elementum tellus. Integer augue purus, rutrum eu pretium sitamet, varius in quam.Lorem ipsum dolor sit amet, consectetur adipiscingelit. Phasellus nisi orci, ultrices sit amet mi eget, efficiturelementum tellus. Integer augue purus, rutrum eu pretium sit amet,varius in quam.Lorem ipsum dolor sit amet, consectetur adipiscing elit.Phasellus nisi orci, ultrices sit amet mi eget, efficitur elementumtellus. Integer augue purus, rutrum eu pretium sit amet, varius inquam.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellusnisi orci, ultrices sit amet mi eget, efficitur elementum tellus.Integer augue purus, rutrum eu pretium sit amet, varius in quam.Loremipsum dolor sit amet, consectetur adipiscing elit. Phasellus nisi orci,ultrices sit amet mi eget, efficitur elementum tellus. Integer auguepurus, rutrum eu pretium sit amet, varius in quam.Lorem ipsum dolor sitamet, consectetur adipiscing elit. Phasellus nisi orci, ultrices sitamet mi eget, efficitur elementum tellus. Integer augue purus, rutrum eupretium sit amet, varius in quam.",
+ },
+ });
+
+ const handleClickProposalDetail = () => {
+ setIsShowProposalModal(true);
+ };
+
+ return (
+ toggleShowCancelled(!isShowCancelled)}
+ proposalDetail={proposalDetail}
+ isShowProposalModal={isShowProposalModal}
+ setIsShowProposalModal={() =>
+ setIsShowProposalModal(!isShowProposalModal)
+ }
+ breakpoint={breakpoint}
+ onClickProposalDetail={handleClickProposalDetail}
+ isShowCreateProposal={isShowCreateProposal}
+ setIsShowCreateProposal={() =>
+ setIsShowCreateProposal(!isShowCreateProposal)
+ }
+ />
+ );
+};
+
+export default ProposalListContainer;
diff --git a/packages/web/src/containers/select-token-container/SelectTokenContainer.tsx b/packages/web/src/containers/select-token-container/SelectTokenContainer.tsx
index 0e01a8634..57bb00165 100644
--- a/packages/web/src/containers/select-token-container/SelectTokenContainer.tsx
+++ b/packages/web/src/containers/select-token-container/SelectTokenContainer.tsx
@@ -1,12 +1,11 @@
import React, { useCallback, useEffect, useMemo, useState } from "react";
import SelectToken from "@components/common/select-token/SelectToken";
import { useClearModal } from "@hooks/common/use-clear-modal";
-import { TokenInfo } from "@models/token/token-info";
import { useTokenData } from "@hooks/token/use-token-data";
import { TokenModel } from "@models/token/token-model";
interface SelectTokenContainerProps {
- changeToken?: (token: TokenInfo) => void;
+ changeToken?: (token: TokenModel) => void;
}
const SelectTokenContainer: React.FC = ({
diff --git a/packages/web/src/containers/swap-container/SwapContainer.tsx b/packages/web/src/containers/swap-container/SwapContainer.tsx
index a3fa9d963..bb45ac85c 100644
--- a/packages/web/src/containers/swap-container/SwapContainer.tsx
+++ b/packages/web/src/containers/swap-container/SwapContainer.tsx
@@ -1,283 +1,278 @@
-import React, { useCallback, useState } from "react";
+import React, { useCallback, useEffect, useMemo, useState } from "react";
import SwapCard from "@components/swap/swap-card/SwapCard";
-import { useQuery } from "@tanstack/react-query";
-import { useWindowSize } from "@hooks/common/use-window-size";
import { useSwap } from "@hooks/swap/use-swap";
+import { TokenModel } from "@models/token/token-model";
+import { useTokenData } from "@hooks/token/use-token-data";
+import BigNumber from "bignumber.js";
+import { useWallet } from "@hooks/wallet/use-wallet";
+import { SwapTokenInfo } from "@models/swap/swap-token-info";
+import { SwapDirectionType, amountEmptyNumberInit } from "@common/values";
+import { SwapResultInfo } from "@models/swap/swap-result-info";
+import { SwapSummaryInfo } from "@models/swap/swap-summary-info";
+import { AmountModel } from "@models/common/amount-model";
+import { SwapRouteInfo } from "@models/swap/swap-route-info";
+import { SwapResponse } from "@repositories/swap";
+import { matchInputNumber, numberToUSD } from "@utils/number-utils";
+import { SwapError } from "@common/errors/swap";
-export interface SwapGasInfo {
- priceImpact: string;
- minReceived: string;
- gasFee: string;
- usdExchangeGasFee: string;
-}
-interface TokenInfo {
- token: string;
- symbol: string;
- amount: string;
- price: string;
- gnosExchangePrice: string;
- usdExchangePrice: string;
- balance: string;
- logoURI: string;
-}
+const SwapContainer: React.FC = () => {
+ const { connected: connectedWallet, connectAdenaClient } = useWallet();
+ const { tokenPrices, balances, updateTokenPrices, updateBalances } = useTokenData();
+ const [swapError, setSwapError] = useState(null);
+ const [tokenA, setTokenA] = useState(null);
+ const [tokenAAmount, setTokenAAmount] = useState("0");
+ const [tokenB, setTokenB] = useState(null);
+ const [tokenBAmount, setTokenBAmount] = useState("0");
+ const [swapDirection, setSwapDirection] = useState("EXACT_IN");
+ const [submitted, setSubmitted] = useState(false);
+ const [copied, setCopied] = useState(false);
+ const [swapResult, setSwapResult] = useState(null);
+ const [swapRate] = useState(1);
+ const [slippage, setSlippage] = useState(10);
+ const [gasFeeAmount] = useState(amountEmptyNumberInit);
+ const [swapRouteInfos] = useState([]);
+ const [openedConfirmModal, setOpenedConfirModal] = useState(false);
+ const { swap, getExpectedSwap } = useSwap({
+ tokenA,
+ tokenB,
+ direction: swapDirection,
+ slippage
+ });
-export const dummySwapGasInfo: SwapGasInfo = {
- priceImpact: "-0.3%",
- minReceived: "1.8445 ETH",
- gasFee: "0.002451 GNOT",
- usdExchangeGasFee: "$0.12",
-};
+ useEffect(() => {
+ updateTokenPrices();
+ });
-export interface AutoRouterInfo {
- v1fee: string[];
- v2fee: string[];
- v3fee: string[];
-}
+ const checkBalance = useCallback((token: TokenModel, amount: string) => {
+ const tokenBalance = balances[token.priceId] || 0;
+ return BigNumber(tokenBalance).isGreaterThan(amount);
+ }, [balances]);
-export const dummyAutoRouterInfo: AutoRouterInfo = {
- v1fee: ["60%", "0.05%", "0.01%"],
- v2fee: ["35%", "0.01%"],
- v3fee: ["5%", "0.3%"],
-};
+ useEffect(() => {
+ if (!tokenA || !tokenB) {
+ return;
+ }
+ const isExactIn = swapDirection === "EXACT_IN";
+ const changedAmount = isExactIn ? tokenAAmount : tokenBAmount;
+ if (Number.isNaN(changedAmount) && BigNumber(changedAmount).isGreaterThan(0)) {
+ return;
+ }
+ getExpectedSwap(changedAmount).then(result => {
+ const isError = result === null;
+ const expectedAmount = isError ? "" : result;
+ let swapError = null;
+ if (isError) {
+ swapError = new SwapError("INSUFFICIENT_BALANCE");
+ }
+ if (!checkBalance(tokenA, tokenAAmount) ||
+ !checkBalance(tokenB, tokenBAmount)) {
+ swapError = new SwapError("INSUFFICIENT_BALANCE");
+ }
-export interface tokenInfo {
- [key: string]: string;
-}
+ if (isExactIn) {
+ setTokenBAmount(expectedAmount);
+ } else {
+ setTokenAAmount(expectedAmount);
+ }
+ setSwapError(swapError);
+ });
+ }, [checkBalance, getExpectedSwap, swapDirection, tokenA, tokenAAmount, tokenB, tokenBAmount]);
-export const coinList = (): tokenInfo[] => [
- {
- logo: "https://s2.coinmarketcap.com/static/img/coins/64x64/1.png",
- name: "Bitcoin",
- symbol: "BTC",
- balance: "0.112",
- },
- {
- logo: "https://s2.coinmarketcap.com/static/img/coins/64x64/2.png",
- name: "Ethereum",
- symbol: "ETH",
- balance: "7.21",
- },
- {
- logo: "https://s2.coinmarketcap.com/static/img/coins/64x64/3.png",
- name: "Gnoland",
- symbol: "GNOT",
- balance: "109.1",
- },
- {
- logo: "https://s2.coinmarketcap.com/static/img/coins/64x64/3.png",
- name: "Gnoland2",
- symbol: "GNOS",
- balance: "1019.1",
- },
- {
- logo: "https://s2.coinmarketcap.com/static/img/coins/64x64/3.png",
- name: "Gnoland3",
- symbol: "GNOQ",
- balance: "109444.1",
- },
- {
- logo: "https://s2.coinmarketcap.com/static/img/coins/64x64/3.png",
- name: "Gnoland4",
- symbol: "GNOV",
- balance: "1094244.1",
- },
-];
+ const tokenABalance = useMemo(() => {
+ if (tokenA && !Number.isNaN(balances[tokenA.priceId])) {
+ return BigNumber(balances[tokenA.priceId] || 0).toFormat();
+ }
+ return "-";
+ }, [balances, tokenA]);
-async function fetchTokens(
- keyword: string, // eslint-disable-line
-): Promise {
- return new Promise(resolve => setTimeout(resolve, 500)).then(() =>
- Promise.resolve([...coinList()]),
- );
-}
+ const tokenBBalance = useMemo(() => {
+ if (tokenB && !Number.isNaN(balances[tokenB.priceId])) {
+ return BigNumber(balances[tokenB.priceId] || 0).toFormat();
+ }
+ return "-";
+ }, [balances, tokenB]);
-function isAmount(str: string) {
- const regex = /^\d+(\.\d*)?$/;
- return regex.test(str);
-}
+ const tokenAUSD = useMemo(() => {
+ if (!tokenA || !tokenPrices[tokenA.priceId]) {
+ return Number.NaN;
+ }
+ return BigNumber(tokenAAmount).multipliedBy(tokenPrices[tokenA.priceId].usd).toNumber();
+ }, [tokenA, tokenAAmount, tokenPrices]);
-export interface SwapData {
- success: boolean;
- transaction?: string;
-}
+ const tokenBUSD = useMemo(() => {
+ if (!tokenB || !tokenPrices[tokenB.priceId]) {
+ return Number.NaN;
+ }
+ return BigNumber(tokenBAmount).multipliedBy(tokenPrices[tokenB.priceId].usd).toNumber();
+ }, [tokenB, tokenBAmount, tokenPrices]);
-const SwapContainer: React.FC = () => {
- const { swap } = useSwap();
- const { breakpoint } = useWindowSize();
- const [keyword, setKeyword] = useState("");
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- const [gnosAmount, setGnosAmount] = useState("1500");
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- const [isConnected, setIsConnected] = useState(true);
- const [autoRouter, setAutoRouter] = useState(false);
- const [swapInfo, setSwapInfo] = useState(false);
- const [settingMenuToggle, setSettingMenuToggle] = useState(false);
- const [tolerance, setTolerance] = useState("1");
- const [tokenModal, setMokenModal] = useState(false);
- const [swapOpen, setSwapOpen] = useState(false);
- const [division, setDivision] = useState("");
- const [submit, setSubmit] = useState(false);
- const [copied, setCopied] = useState(false);
- const [swapResult, setSwapResult] = useState(null);
+ const swapButtonText = useMemo(() => {
+ if (!connectedWallet) {
+ return "Connect Wallet";
+ }
+ if (!tokenA || !tokenB) {
+ return "Insufficient Balance";
+ }
+ if (swapError) {
+ return swapError.message;
+ }
+ return "Swap";
+ }, [connectedWallet, swapError, tokenA, tokenB]);
- const { data: tokens } = useQuery({
- queryKey: [keyword],
- queryFn: () => fetchTokens(keyword),
- });
+ const openConfirmModal = useCallback(() => {
+ setOpenedConfirModal(true);
+ }, []);
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- const [from, setFrom] = useState({
- token: "USDCoin",
- symbol: "USDC",
- amount: "121",
- price: "$0.00",
- gnosExchangePrice: "1250",
- usdExchangePrice: "($1541.55)",
- balance: "0",
- logoURI:
- "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
- });
+ const openConnectWallet = useCallback(() => {
+ connectAdenaClient();
+ }, [connectAdenaClient]);
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- const [to, setTo] = useState({
- token: "HEX",
- symbol: "HEX",
- amount: "5000",
- price: "$0.00",
- gnosExchangePrice: "1250",
- usdExchangePrice: "($1541.55)",
- balance: "0",
- logoURI:
- "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39/logo.png",
- });
+ const closeModal = useCallback(() => {
+ setSubmitted(false);
+ setSwapResult(null);
+ setOpenedConfirModal(false);
+ updateBalances();
+ }, [updateBalances]);
- const changeToken = (token: tokenInfo) => {
- switch (division) {
- case "from":
- setFrom(prev => ({
- ...prev,
- logoURI: token.logo,
- token: token.name,
- symbol: token.symbol,
- balance: token.balance,
- }));
- break;
- case "to":
- setTo(prev => ({
- ...prev,
- logoURI: token.logo,
- token: token.name,
- symbol: token.symbol,
- balance: token.balance,
- }));
- break;
+ const changeTokenAAmount = useCallback((value: string) => {
+ if (!matchInputNumber(value)) {
+ return;
}
- };
+ setSwapDirection("EXACT_IN");
+ setTokenAAmount(value);
+ }, []);
- const handleCopyClipBoard = async (text: string) => {
- try {
- await navigator.clipboard.writeText(text);
- setCopied(true);
- setTimeout(() => {
- setCopied(false);
- }, 500);
- } catch (e) {
- throw new Error("Copy Error!");
+ const changeTokenBAmount = useCallback((value: string) => {
+ if (!matchInputNumber(value)) {
+ return;
}
- };
+ setSwapDirection("EXACT_OUT");
+ setTokenBAmount(value);
+ }, []);
- const onSettingMenu = () => {
- setSettingMenuToggle(prev => !prev);
- };
+ const changeSlippage = useCallback((value: string) => {
+ setSlippage(BigNumber(value).toNumber());
+ }, [setSlippage]);
- const onSelectTokenModal = () => {
- document.body.style.overflowY = tokenModal ? "auto" : "hidden";
- setMokenModal(prev => !prev);
- };
+ const swapTokenInfo: SwapTokenInfo = useMemo(() => {
+ return {
+ tokenA,
+ tokenAAmount,
+ tokenABalance,
+ tokenAUSD,
+ tokenAUSDStr: numberToUSD(tokenAUSD),
+ tokenB,
+ tokenBAmount,
+ tokenBBalance,
+ tokenBUSD,
+ tokenBUSDStr: numberToUSD(tokenBUSD),
+ direction: swapDirection,
+ slippage
+ };
+ }, [slippage, swapDirection, tokenA, tokenAAmount, tokenABalance, tokenAUSD, tokenB, tokenBAmount, tokenBBalance, tokenBUSD]);
- const onConfirmModal = () => {
- setSwapOpen(prev => !prev);
- setSwapResult(null);
- if (submit) {
- setSubmit(false);
- setTolerance("1");
+ const swapSummaryInfo: SwapSummaryInfo | null = useMemo(() => {
+ if (!tokenA || !tokenB) {
+ return null;
}
- };
-
- const changeTolerance = (e: React.ChangeEvent) => {
- const value = e.target.value;
- if (value !== "" && !isAmount(value)) return;
- setTolerance(value);
- };
+ const swapRateUSD = BigNumber(swapRate).multipliedBy(1).toNumber();
+ const gasFeeUSD = BigNumber(gasFeeAmount.amount).multipliedBy(1).toNumber();
+ return {
+ tokenA,
+ tokenB,
+ swapDirection,
+ swapRate,
+ swapRateUSD,
+ priceImpact: 0.1,
+ guaranteedAmount: {
+ amount: BigNumber(tokenBAmount).toNumber(),
+ currency: tokenB.symbol,
+ },
+ gasFee: gasFeeAmount,
+ gasFeeUSD,
+ };
+ }, [gasFeeAmount, swapDirection, swapRate, tokenA, tokenB, tokenBAmount]);
- const resetTolerance = () => {
- setTolerance("1");
- };
+ const isAvailSwap = useMemo(() => {
+ if (!tokenA || !tokenB) {
+ return false;
+ }
+ return swapError === null;
+ }, [swapError, tokenA, tokenB]);
- const selectToken = (e: string) => {
- setDivision(e);
- };
+ const changeTokenA = useCallback((token: TokenModel) => {
+ setTokenA(token);
+ }, []);
- const search = useCallback((e: React.ChangeEvent) => {
- setKeyword(e.target.value);
+ const changeTokenB = useCallback((token: TokenModel) => {
+ setTokenB(token);
}, []);
- const showSwapInfo = () => {
- autoRouter && setAutoRouter(prev => !prev);
- setSwapInfo(prev => !prev);
- };
+ const switchSwapDirection = useCallback(() => {
+ const preTokenA = tokenA ? { ...tokenA } : null;
+ const preTokenB = tokenB ? { ...tokenB } : null;
+ const changedSwapDirection = swapDirection === "EXACT_IN" ? "EXACT_OUT" : "EXACT_IN";
+
+ setTokenA(preTokenB);
+ setTokenB(preTokenA);
+ setSwapDirection(changedSwapDirection);
+ if (changedSwapDirection === "EXACT_IN") {
+ setTokenAAmount(tokenBAmount);
+ } else {
+ setTokenBAmount(tokenAAmount);
+ }
- const showAutoRouter = () => {
- setAutoRouter(prev => !prev);
+ }, [swapDirection, tokenA, tokenAAmount, tokenB, tokenBAmount]);
+
+ const copyURL = async () => {
+ try {
+ const url = `https://gnoswap.io/swap?tokenA=${tokenA?.path}&tokenB=${tokenB?.path}`;
+ await navigator.clipboard.writeText(url);
+ setCopied(true);
+ setTimeout(() => {
+ setCopied(false);
+ }, 500);
+ } catch (e) {
+ throw new Error("Copy Error!");
+ }
};
- const submitSwap = (event: React.MouseEvent) => {
- event.preventDefault();
- if (submit) {
+ function executeSwap() {
+ if (!tokenA || !tokenB) {
return;
}
- setSubmit(true);
- swap().then(success => {
+ setSubmitted(true);
+ swap(tokenAAmount, tokenBAmount).then(result => {
setSwapResult({
- success: success,
- transaction: "https://gnoscan.io"
+ success: result !== null,
+ hash: (result as SwapResponse)?.tx_hash || "",
});
});
- };
+ }
return (
);
};
diff --git a/packages/web/src/containers/token-swap-container/TokenSwapContainer.tsx b/packages/web/src/containers/token-swap-container/TokenSwapContainer.tsx
index 76e9a4720..c683abd63 100644
--- a/packages/web/src/containers/token-swap-container/TokenSwapContainer.tsx
+++ b/packages/web/src/containers/token-swap-container/TokenSwapContainer.tsx
@@ -17,11 +17,15 @@ const TokenSwapContainer: React.FC = () => {
{
}}
to={{
token: {
- path: "HEX",
- name: "HEX",
- symbol: "HEX",
- logoURI:
- "https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39/logo.png",
+ chainId: "dev",
+ createdAt: "2023-10-10T08:48:46+09:00",
+ name: "Gnoswap",
+ address: "g1sqaft388ruvsseu97r04w4rr4szxkh4nn6xpax",
+ path: "gno.land/r/gnos",
+ decimals: 4,
+ symbol: "GNOS",
+ logoURI: "https://s2.coinmarketcap.com/static/img/coins/64x64/5994.png",
+ priceId: "gno.land/r/gnos"
},
amount: "5000",
price: "$0.00",
diff --git a/packages/web/src/hooks/common/use-floating-tooltip.tsx b/packages/web/src/hooks/common/use-floating-tooltip.tsx
new file mode 100644
index 000000000..feba103a0
--- /dev/null
+++ b/packages/web/src/hooks/common/use-floating-tooltip.tsx
@@ -0,0 +1,112 @@
+import {
+ arrow,
+ getOverflowAncestors,
+ shift,
+ useFloating,
+} from "@floating-ui/react";
+import React, { useCallback, useEffect, useRef, useState } from "react";
+
+type FloatingPlacement = "end" | "start";
+type FloatingSide = "top" | "right" | "bottom" | "left";
+export type FloatingPosition =
+ | FloatingSide
+ | `${FloatingSide}-${FloatingPlacement}`;
+interface UseFloatingTooltip {
+ offset: number;
+ position: FloatingPosition;
+}
+
+export function useFloatingTooltip({
+ offset,
+ position,
+}: UseFloatingTooltip) {
+ const [opened, setOpened] = useState(false);
+ const boundaryRef = useRef();
+ const arrowRef = useRef(null);
+ const { x, y, elements, context, refs, strategy, update, placement } =
+ useFloating({
+ placement: position,
+ middleware: [
+ shift({
+ crossAxis: true,
+ padding: 5,
+ rootBoundary: "document",
+ }),
+ arrow({
+ element: arrowRef,
+ }),
+ ],
+ });
+
+ const horizontalOffset = placement.includes("right")
+ ? offset
+ : position.includes("left")
+ ? offset * -1
+ : 0;
+
+ const verticalOffset = placement.includes("bottom")
+ ? offset
+ : position.includes("top")
+ ? offset * -1
+ : 0;
+
+ const handleMouseMove = useCallback(
+ ({ clientX, clientY }: MouseEvent | React.MouseEvent) => {
+ refs.setPositionReference({
+ getBoundingClientRect() {
+ return {
+ width: 0,
+ height: 0,
+ x: clientX,
+ y: clientY,
+ left: clientX + horizontalOffset,
+ top: clientY + verticalOffset,
+ right: clientX,
+ bottom: clientY,
+ };
+ },
+ });
+ },
+ [elements.reference],
+ );
+
+ useEffect(() => {
+ if (refs.floating.current) {
+ const boundary = boundaryRef.current!;
+ boundary.addEventListener("mousemove", handleMouseMove);
+
+ const parents = getOverflowAncestors(refs.floating.current);
+ parents.forEach(parent => {
+ parent.addEventListener("scroll", update);
+ });
+
+ return () => {
+ boundary.removeEventListener("mousemove", handleMouseMove);
+ parents.forEach(parent => {
+ parent.removeEventListener("scroll", update);
+ });
+ };
+ }
+
+ return undefined;
+ }, [
+ elements.reference,
+ refs.floating.current,
+ update,
+ handleMouseMove,
+ opened,
+ ]);
+
+ return {
+ handleMouseMove,
+ x,
+ y,
+ arrowRef,
+ context,
+ strategy,
+ opened,
+ setOpened,
+ boundaryRef,
+ floating: refs.setFloating,
+ };
+}
diff --git a/packages/web/src/hooks/common/use-merged-ref.ts b/packages/web/src/hooks/common/use-merged-ref.ts
new file mode 100644
index 000000000..efc444260
--- /dev/null
+++ b/packages/web/src/hooks/common/use-merged-ref.ts
@@ -0,0 +1,21 @@
+import React, { useCallback, Ref } from "react";
+
+type PossibleRef = Ref | undefined;
+
+export function assignRef(ref: PossibleRef, value: T) {
+ if (typeof ref === "function") {
+ ref(value);
+ } else if (typeof ref === "object" && ref !== null && "current" in ref) {
+ (ref as React.MutableRefObject).current = value;
+ }
+}
+
+export function mergeRefs(...refs: PossibleRef[]) {
+ return (node: T | null) => {
+ refs.forEach(ref => assignRef(ref, node));
+ };
+}
+
+export function useMergedRef(...refs: PossibleRef[]) {
+ return useCallback(mergeRefs(...refs), refs);
+}
diff --git a/packages/web/src/hooks/common/use-slippage.ts b/packages/web/src/hooks/common/use-slippage.ts
new file mode 100644
index 000000000..0e9f865f2
--- /dev/null
+++ b/packages/web/src/hooks/common/use-slippage.ts
@@ -0,0 +1,22 @@
+import { useAtom } from "jotai";
+import { CommonState } from "@states/index";
+import { useCallback } from "react";
+import { DEFAULT_SLIPPAGE } from "@constants/option.constant";
+
+export const useSlippage = () => {
+ const [slippage, setSlippage] = useAtom(CommonState.slippage);
+
+ const changeSlippage = useCallback(
+ (slippage: number) => {
+ const changedSlippage = Math.min(100, Math.max(0, slippage));
+ setSlippage(changedSlippage);
+ },
+ [setSlippage],
+ );
+
+ const resetSlippage = useCallback(() => {
+ setSlippage(DEFAULT_SLIPPAGE);
+ }, [setSlippage]);
+
+ return { slippage, changeSlippage, resetSlippage };
+};
diff --git a/packages/web/src/hooks/pool/use-pool-data.tsx b/packages/web/src/hooks/pool/use-pool-data.tsx
index af662cb63..d0ed15278 100644
--- a/packages/web/src/hooks/pool/use-pool-data.tsx
+++ b/packages/web/src/hooks/pool/use-pool-data.tsx
@@ -1,5 +1,8 @@
+import { PoolPosition } from "@containers/earn-my-position-container/EarnMyPositionContainer";
import { useGnoswapContext } from "@hooks/common/use-gnoswap-context";
import { CardListPoolInfo } from "@models/common/card-list-item-info";
+import { PoolCardInfo } from "@models/pool/info/pool-card-info";
+import { PoolMapper } from "@models/pool/mapper/pool-mapper";
import { PoolState } from "@states/index";
import { useAtom } from "jotai";
import { useMemo } from "react";
@@ -7,6 +10,12 @@ import { useMemo } from "react";
export const usePoolData = () => {
const { poolRepository } = useGnoswapContext();
const [pools, setPools] = useAtom(PoolState.pools);
+ const [isFetchedPools, setIsFetchedPools] = useAtom(PoolState.isFetchedPools);
+ const [isFetchedPositions, setIsFetchedPositions] = useAtom(PoolState.isFetchedPositions);
+
+ const poolListInfos = useMemo(() => {
+ return pools.map(PoolMapper.toListInfo);
+ }, [pools]);
const higestAPRs: CardListPoolInfo[] = useMemo(() => {
const sortedTokens = pools.sort((p1, p2) => {
@@ -15,17 +24,38 @@ export const usePoolData = () => {
return sortedTokens.map(pool => ({
pool,
upDown: "none",
- content: `${pool.topBin.annualizedFeeGrowth}%`
+ content: `${pool.topBin.annualizedFeeGrowth || 0}%`
}));
}, [pools]);
+ const myPositions: PoolPosition[] = useMemo(() => {
+ return [];
+ }, []);
+ async function updatePositions() {
+ setIsFetchedPositions(true);
+ }
+
+ const incentivizedPools: PoolCardInfo[] = useMemo(() => {
+ return pools
+ .map(PoolMapper.toCardInfo)
+ .filter(info => info.incentiveType === "Incentivized");
+ }, [pools]);
+
async function updatePools() {
const response = await poolRepository.getPools();
setPools(response.pools);
+ setIsFetchedPools(true);
}
return {
+ isFetchedPools,
higestAPRs,
+ isFetchedPositions,
+ myPositions,
+ pools,
+ poolListInfos,
+ incentivizedPools,
updatePools,
+ updatePositions,
};
};
\ No newline at end of file
diff --git a/packages/web/src/hooks/pool/use-pool.tsx b/packages/web/src/hooks/pool/use-pool.tsx
new file mode 100644
index 000000000..671e8e52e
--- /dev/null
+++ b/packages/web/src/hooks/pool/use-pool.tsx
@@ -0,0 +1,55 @@
+import { SwapFeeTierType } from "@constants/option.constant";
+import { AddLiquidityPriceRage } from "@containers/earn-add-liquidity-container/EarnAddLiquidityContainer";
+import { useGnoswapContext } from "@hooks/common/use-gnoswap-context";
+import { useWallet } from "@hooks/wallet/use-wallet";
+import { TokenModel } from "@models/token/token-model";
+import { useCallback } from "react";
+
+export const usePool = () => {
+ const { account } = useWallet();
+ const { poolRepository } = useGnoswapContext();
+
+ const createPool = useCallback(async ({
+ tokenA,
+ tokenB,
+ tokenAAmount,
+ tokenBAmount,
+ swapFeeTier,
+ startPrice,
+ priceRange,
+ slippage,
+ }: {
+ tokenA: TokenModel;
+ tokenB: TokenModel;
+ tokenAAmount: string;
+ tokenBAmount: string;
+ swapFeeTier: SwapFeeTierType;
+ startPrice: string;
+ priceRange: AddLiquidityPriceRage;
+ slippage: number;
+ }) => {
+ if (!account) {
+ return null;
+ }
+ const hash = await poolRepository.createPool({
+ tokenA,
+ tokenB,
+ tokenAAmount,
+ tokenBAmount,
+ feeTier: swapFeeTier,
+ startPrice,
+ minTick: priceRange.range.minTick,
+ maxTick: priceRange.range.maxTick,
+ slippage,
+ caller: account.address
+ }).catch(e => {
+ console.error(e);
+ return null;
+ });
+ return hash;
+ }, [account, poolRepository]);
+
+ return {
+ createPool
+ };
+};
\ No newline at end of file
diff --git a/packages/web/src/hooks/swap/use-swap.tsx b/packages/web/src/hooks/swap/use-swap.tsx
index 5aa1cd9df..253abfb9a 100644
--- a/packages/web/src/hooks/swap/use-swap.tsx
+++ b/packages/web/src/hooks/swap/use-swap.tsx
@@ -1,30 +1,124 @@
+import { SwapDirectionType } from "@common/values";
+import { SwapFeeTierInfoMap } from "@constants/option.constant";
import { useGnoswapContext } from "@hooks/common/use-gnoswap-context";
import { useWallet } from "@hooks/wallet/use-wallet";
-import { useCallback } from "react";
+import { TokenModel } from "@models/token/token-model";
+import BigNumber from "bignumber.js";
+import { useCallback, useMemo } from "react";
-export const useSwap = () => {
+interface UseSwapProps {
+ tokenA: TokenModel | null;
+ tokenB: TokenModel | null;
+ direction: SwapDirectionType;
+ slippage: number;
+}
+
+export const useSwap = ({
+ tokenA,
+ tokenB,
+ direction,
+}: UseSwapProps) => {
const { account } = useWallet();
const { swapRepository } = useGnoswapContext();
- const swap = useCallback(async () => {
+ const selectedTokenPair = tokenA !== null && tokenB !== null;
+
+ /**
+ * TODO: Once a contract can handle GRC20 tokens dynamically, it will need to be reconsidered.
+ */
+ const zeroForOne = useMemo(() => {
+ return tokenA?.symbol.toLowerCase() === "foo";
+ }, [tokenA?.symbol]);
+
+ const amountDirection = useMemo(() => {
+ const isDirection = direction === "EXACT_IN";
+ if (isDirection) {
+ return 1;
+ }
+ return -1;
+ }, [direction]);
+
+ /**
+ * TODO: Once a contract can handle GRC20 tokens dynamically, it will need to be reconsidered.
+ */
+ const getAmountResult = useCallback((inputAmount: number, tokenAAmount: number, tokenBAmount: number) => {
+ const isExactIn = inputAmount >= 0;
+ if (isExactIn === zeroForOne) {
+ return BigNumber(tokenBAmount).abs().toString();
+ }
+ return BigNumber(tokenAAmount).abs().toString();
+ }, [zeroForOne]);
+
+ const findSwapPool = useCallback(async (amount: string) => {
+ if (!selectedTokenPair) {
+ return null;
+ }
+ const amountSpecified = BigNumber(amount).multipliedBy(amountDirection).toNumber();
+
+ return swapRepository.findSwapPool({
+ tokenA: zeroForOne ? tokenA : tokenB,
+ tokenB: zeroForOne ? tokenB : tokenA,
+ zeroForOne,
+ amountSpecified,
+ }).catch(() => null);
+ }, [selectedTokenPair, amountDirection, swapRepository, zeroForOne, tokenA, tokenB]);
+
+ const getExpectedSwap = useCallback(async (amount: string) => {
+ if (!selectedTokenPair) {
+ return null;
+ }
+ const swapPool = await findSwapPool(amount);
+ if (!swapPool) {
+ return null;
+ }
+ const fee = SwapFeeTierInfoMap[swapPool.feeTier].fee;
+
+ const amountSpecified = direction === "EXACT_OUT" ?
+ BigNumber(amount || 0).multipliedBy(-1).toNumber() :
+ BigNumber(amount || 0).toNumber();
+
+ return swapRepository.getExpectedSwapResult({
+ tokenA: zeroForOne ? tokenA : tokenB,
+ tokenB: zeroForOne ? tokenB : tokenA,
+ fee,
+ receiver: "",
+ zeroForOne,
+ amountSpecified,
+ }).then((data) =>
+ getAmountResult(amountSpecified, data.tokenAAmount, data.tokenBAmount))
+ .catch(() => null);
+ }, [selectedTokenPair, findSwapPool, direction, swapRepository, zeroForOne, tokenA, tokenB, getAmountResult]);
+
+ const swap = useCallback(async (tokenAAmount: string, tokenBAmount: string) => {
if (!account) {
return false;
}
+ if (!selectedTokenPair) {
+ return false;
+ }
+ const amountSpecified = direction === "EXACT_IN" ?
+ BigNumber(tokenAAmount).multipliedBy(amountDirection).toNumber() :
+ BigNumber(tokenBAmount).multipliedBy(amountDirection).toNumber();
+
+ const swapPool = await findSwapPool(`${amountSpecified}`);
+ if (!swapPool) {
+ return false;
+ }
+ const fee = SwapFeeTierInfoMap[swapPool.feeTier].fee;
const response = await swapRepository.swap({
- tokenA: "foo",
- tokenB: "bar",
- fee: 500,
+ tokenA: zeroForOne ? tokenA : tokenB,
+ tokenB: zeroForOne ? tokenB : tokenA,
+ fee,
receiver: account.address,
- zeroForOne: true,
- amountSpecified: 200000,
- sqrtPriceLimitX96: 4295128740,
+ zeroForOne,
+ amountSpecified,
})
- .then(() => true)
.catch(() => false);
return response;
- }, [account, swapRepository]);
+ }, [account, amountDirection, direction, findSwapPool, selectedTokenPair, swapRepository, tokenA, tokenB, zeroForOne]);
return {
- swap
+ swap,
+ getExpectedSwap
};
};
\ No newline at end of file
diff --git a/packages/web/src/hooks/token/use-earn-add-liquidity-confirm-modal.tsx b/packages/web/src/hooks/token/use-earn-add-liquidity-confirm-modal.tsx
new file mode 100644
index 000000000..fab6ba33b
--- /dev/null
+++ b/packages/web/src/hooks/token/use-earn-add-liquidity-confirm-modal.tsx
@@ -0,0 +1,139 @@
+import EarnAddConfirm from "@components/earn-add/earn-add-confirm/EarnAddConfirm";
+import { SwapFeeTierInfoMap, SwapFeeTierType } from "@constants/option.constant";
+import { AddLiquidityPriceRage } from "@containers/earn-add-liquidity-container/EarnAddLiquidityContainer";
+import useNavigate from "@hooks/common/use-navigate";
+import { usePool } from "@hooks/pool/use-pool";
+import { TokenModel } from "@models/token/token-model";
+import { CommonState } from "@states/index";
+import { useAtom } from "jotai";
+import { useCallback, useMemo } from "react";
+import { TokenAmountInputModel } from "./use-token-amount-input";
+import { getCurrentPriceByRaw } from "@utils/swap-utils";
+
+export interface EarnAddLiquidityConfirmModalProps {
+ tokenA: TokenModel | null;
+ tokenB: TokenModel | null;
+ tokenAAmountInput: TokenAmountInputModel;
+ tokenBAmountInput: TokenAmountInputModel;
+ currentPrice: string;
+ priceRange: AddLiquidityPriceRage | null;
+ slippage: number;
+ swapFeeTier: SwapFeeTierType | null;
+}
+export interface SelectTokenModalModel {
+ openModal: () => void;
+}
+
+export const useEarnAddLiquidityConfirmModal = ({
+ tokenA,
+ tokenB,
+ tokenAAmountInput,
+ tokenBAmountInput,
+ priceRange,
+ currentPrice,
+ slippage,
+ swapFeeTier,
+}: EarnAddLiquidityConfirmModalProps): SelectTokenModalModel => {
+ const [, setOpenedModal] = useAtom(CommonState.openedModal);
+ const [, setModalContent] = useAtom(CommonState.modalContent);
+ const { createPool } = usePool();
+ const navigator = useNavigate();
+
+ const amountInfo = useMemo(() => {
+ if (!tokenA || !tokenB || !swapFeeTier) {
+ return null;
+ }
+ return {
+ tokenA: {
+ info: tokenA,
+ amount: tokenAAmountInput.amount,
+ usdPrice: tokenAAmountInput.usdValue,
+ },
+ tokenB: {
+ info: tokenB,
+ amount: tokenBAmountInput.amount,
+ usdPrice: tokenBAmountInput.usdValue,
+ },
+ feeRate: SwapFeeTierInfoMap[swapFeeTier].rateStr
+ };
+ }, [swapFeeTier, tokenA, tokenAAmountInput, tokenBAmountInput, tokenB]);
+
+
+ const priceRangeInfo = useMemo(() => {
+ if (!priceRange) {
+ return null;
+ }
+ return {
+ currentPrice: getCurrentPriceByRaw(currentPrice).toFixed(),
+ minPrice: `${priceRange.range.minTick}`,
+ minPriceLable: priceRange.range.minPrice,
+ maxPrice: `${priceRange.range.maxTick}`,
+ maxPriceLable: priceRange.range.maxPrice,
+ feeBoost: "-",
+ estimatedAPR: "N/A",
+ };
+ }, [currentPrice, priceRange]);
+
+ const feeInfo = useMemo(() => {
+ return {
+ token: {
+ path: "native",
+ address: "",
+ priceId: "GNOLAND",
+ chainId: "dev",
+ name: "Gno.land",
+ symbol: "GNOT",
+ decimals: 6,
+ logoURI: "https://raw.githubusercontent.com/onbloc/gno-token-resource/main/gno-native/images/gnot.svg",
+ createdAt: ""
+ },
+ fee: "0.000001"
+ };
+ }, []);
+
+ const close = useCallback(() => {
+ setOpenedModal(false);
+ setModalContent(null);
+ }, [setModalContent, setOpenedModal]);
+
+ const moveEarn = useCallback(() => {
+ close();
+ navigator.push("/earn");
+ }, [close, navigator]);
+
+ const confirm = useCallback(() => {
+ if (!tokenA || !tokenB || !priceRange || !swapFeeTier) {
+ return;
+ }
+ createPool({
+ tokenA,
+ tokenB,
+ tokenAAmount: tokenAAmountInput.amount,
+ tokenBAmount: tokenBAmountInput.amount,
+ priceRange,
+ slippage,
+ startPrice: currentPrice,
+ swapFeeTier,
+ }).then(result => result && moveEarn());
+ }, [createPool, currentPrice, moveEarn, priceRange, slippage, swapFeeTier, tokenA, tokenAAmountInput.amount, tokenB, tokenBAmountInput.amount]);
+
+ const openModal = useCallback(() => {
+ if (!amountInfo || !priceRangeInfo) {
+ return;
+ }
+ setOpenedModal(true);
+ setModalContent(
+
+ );
+ }, [amountInfo, close, confirm, feeInfo, priceRangeInfo, setModalContent, setOpenedModal]);
+
+ return {
+ openModal
+ };
+};
\ No newline at end of file
diff --git a/packages/web/src/hooks/token/use-select-token-modal.tsx b/packages/web/src/hooks/token/use-select-token-modal.tsx
index 94b1e4306..696bdb944 100644
--- a/packages/web/src/hooks/token/use-select-token-modal.tsx
+++ b/packages/web/src/hooks/token/use-select-token-modal.tsx
@@ -1,11 +1,11 @@
import SelectTokenContainer from "@containers/select-token-container/SelectTokenContainer";
-import { TokenInfo } from "@models/token/token-info";
+import { TokenModel } from "@models/token/token-model";
import { CommonState } from "@states/index";
import { useAtom } from "jotai";
import { useCallback } from "react";
export interface SelectTokenModalProps {
- changeToken?: (token: TokenInfo) => void;
+ changeToken?: (token: TokenModel) => void;
}
export interface SelectTokenModalModel {
openModal: () => void;
diff --git a/packages/web/src/hooks/token/use-token-amount-input.tsx b/packages/web/src/hooks/token/use-token-amount-input.tsx
index 52e3abb32..23fe2226b 100644
--- a/packages/web/src/hooks/token/use-token-amount-input.tsx
+++ b/packages/web/src/hooks/token/use-token-amount-input.tsx
@@ -1,20 +1,30 @@
-import { TokenInfo } from "@models/token/token-info";
+import { TokenModel } from "@models/token/token-model";
import BigNumber from "bignumber.js";
-import { useCallback, useMemo, useState } from "react";
+import { useCallback, useEffect, useMemo, useState } from "react";
+import { useTokenData } from "./use-token-data";
export interface TokenAmountInputModel {
- token: TokenInfo | undefined;
+ token: TokenModel | null;
amount: string;
balance: string;
usdValue: string;
changeAmount: (amount: string) => void;
- changeBalance: (balance: number) => void;
}
-export const useTokenAmountInput = (token: TokenInfo | undefined): TokenAmountInputModel => {
+export const useTokenAmountInput = (token: TokenModel | null): TokenAmountInputModel => {
const [amount, setAmount] = useState("0");
const [balance, setBalance] = useState("0");
const [usd, setUSD] = useState();
+ const { balances, tokenPrices } = useTokenData();
+
+ useEffect(() => {
+ if (token && balances[token.path]) {
+ const balance = balances[token.path];
+ setBalance(BigNumber(balance ?? 0).toFormat());
+ } else {
+ setBalance("0");
+ }
+ }, [balances, token]);
const usdValue = useMemo(() => {
if (!usd) {
@@ -24,21 +34,24 @@ export const useTokenAmountInput = (token: TokenInfo | undefined): TokenAmountIn
}, [usd]);
const changeAmount = useCallback((value: string) => {
- const amount = value;
- setAmount(amount);
- setUSD(BigNumber(amount).toNumber());
- }, []);
+ if (!token) {
+ return;
+ }
- const changeBalance = useCallback((balance: number) => {
- setBalance(BigNumber(balance).toFormat());
- }, []);
+ const amount = BigNumber(value);
+ setAmount(amount.toString());
+
+ if (tokenPrices[token.priceId]) {
+ const usd = BigNumber(tokenPrices[token.priceId].usd).multipliedBy(amount).toNumber();
+ setUSD(usd);
+ }
+ }, [token, tokenPrices]);
return {
token,
amount,
balance,
usdValue,
- changeBalance,
changeAmount,
};
};
\ No newline at end of file
diff --git a/packages/web/src/hooks/token/use-token-data.tsx b/packages/web/src/hooks/token/use-token-data.tsx
index 9c0c2d465..062e968ee 100644
--- a/packages/web/src/hooks/token/use-token-data.tsx
+++ b/packages/web/src/hooks/token/use-token-data.tsx
@@ -2,11 +2,12 @@ import { useGnoswapContext } from "@hooks/common/use-gnoswap-context";
import { useWallet } from "@hooks/wallet/use-wallet";
import { CardListTokenInfo } from "@models/common/card-list-item-info";
import { TokenModel } from "@models/token/token-model";
+import { TokenPriceModel } from "@models/token/token-price-model";
import { TokenState } from "@states/index";
import { evaluateExpressionToNumber } from "@utils/rpc-utils";
import BigNumber from "bignumber.js";
import { useAtom } from "jotai";
-import { useMemo } from "react";
+import { useEffect, useMemo } from "react";
export const useTokenData = () => {
const { account } = useWallet();
@@ -15,10 +16,16 @@ export const useTokenData = () => {
const [tokenPrices, setTokenPrices] = useAtom(TokenState.tokenPrices);
const [balances, setBalances] = useAtom(TokenState.balances);
+ useEffect(() => {
+ if (rpcProvider && account) {
+ updateBalances();
+ }
+ }, [rpcProvider, account]);
+
const trendingTokens: CardListTokenInfo[] = useMemo(() => {
const sortedTokens = tokens.sort((t1, t2) => {
if (tokenPrices[t1.priceId] && tokenPrices[t2.priceId]) {
- return tokenPrices[t2.priceId].volume - tokenPrices[t1.priceId].volume;
+ return BigNumber(tokenPrices[t2.priceId].volume).toNumber() - BigNumber(tokenPrices[t1.priceId].volume).toNumber();
}
if (tokenPrices[t2.priceId]) {
return 1;
@@ -31,8 +38,8 @@ export const useTokenData = () => {
return sortedTokens.map(token => (
tokenPrices[token.priceId] ? {
token,
- upDown: tokenPrices[token.priceId].changedBefore1D > 0 ? "up" : "down",
- content: `${BigNumber(tokenPrices[token.priceId].changedBefore1D).toFixed()}%`
+ upDown: BigNumber(tokenPrices[token.priceId].change1d).isPositive() ? "up" : "down",
+ content: `${BigNumber(tokenPrices[token.priceId].change1d).toFixed()}%`
} : {
token,
upDown: "none",
@@ -60,12 +67,16 @@ export const useTokenData = () => {
async function updateTokens() {
const response = await tokenRepository.getTokens();
- setTokens(response.tokens);
+ setTokens(response?.tokens || []);
}
async function updateTokenPrices() {
const response = await tokenRepository.getTokenPrices();
- setTokenPrices(response.prices);
+ const priceMap = response.prices.reduce>((prev, current) => {
+ prev[current.path] = current;
+ return prev;
+ }, {});
+ setTokenPrices(priceMap);
}
async function updateBalances() {
@@ -85,7 +96,7 @@ export const useTokenData = () => {
const balances: Record = {};
fetchResults.forEach((result, index) => {
if (index < tokens.length) {
- balances[tokens[index].priceId] = result;
+ balances[tokens[index].path] = result;
}
});
setBalances(balances);
diff --git a/packages/web/src/hooks/wallet/use-wallet.ts b/packages/web/src/hooks/wallet/use-wallet.ts
index c6e5b987c..9f141bf47 100644
--- a/packages/web/src/hooks/wallet/use-wallet.ts
+++ b/packages/web/src/hooks/wallet/use-wallet.ts
@@ -1,14 +1,16 @@
import { WalletClient } from "@common/clients/wallet-client";
import { AdenaClient } from "@common/clients/wallet-client/adena";
import { useGnoswapContext } from "@hooks/common/use-gnoswap-context";
-import { WalletState } from "@states/index";
+import { CommonState, WalletState } from "@states/index";
import { useAtom } from "jotai";
import { useCallback, useEffect, useMemo } from "react";
+import NetworkData from "@resources/chains.json";
export const useWallet = () => {
const { accountRepository } = useGnoswapContext();
const [walletClient, setWalletClient] = useAtom(WalletState.client);
const [walletAccount, setWalletAccount] = useAtom(WalletState.account);
+ const [, setNetwork] = useAtom(CommonState.network);
const connected = useMemo(() => {
return walletAccount !== null && walletAccount.address.length > 0;
@@ -23,11 +25,21 @@ export const useWallet = () => {
useEffect(() => {
if (walletClient) {
- initSession();
updateWalletEvents(walletClient);
}
}, [walletClient]);
+ useEffect(() => {
+ if (walletAccount) {
+ const network = NetworkData.find(
+ network => network.chainId === walletAccount.chainId,
+ );
+ if (network) {
+ setNetwork(network);
+ }
+ }
+ }, [setNetwork, walletAccount]);
+
function initSession() {
const connectedBySession = accountRepository.isConnectedWalletBySession();
if (connectedBySession) {
@@ -36,14 +48,21 @@ export const useWallet = () => {
}
const connectAdenaClient = useCallback(() => {
- const adena = new AdenaClient();
- adena.initAdena();
+ const adena = AdenaClient.createAdenaClient();
+ if (adena !== null) {
+ adena.initAdena();
+ }
setWalletClient(adena);
- connectAccount();
}, [setWalletClient]);
- async function connectAccount() {
- const established = await accountRepository.addEstablishedSite();
+ const connectAccount = useCallback(async () => {
+ const established = await accountRepository
+ .addEstablishedSite()
+ .catch(() => null);
+
+ if (established === null) {
+ return;
+ }
if (established.code === 0 || established.code === 4001) {
const account = await accountRepository.getAccount();
@@ -52,7 +71,7 @@ export const useWallet = () => {
} else {
accountRepository.setConnectedWallet(false);
}
- }
+ }, [accountRepository, setWalletAccount]);
const disconnectWallet = useCallback(() => {
setWalletAccount(null);
@@ -66,11 +85,13 @@ export const useWallet = () => {
try {
walletClient.addEventChangedAccount(connectAdenaClient);
walletClient.addEventChangedNetwork(connectAdenaClient);
- } catch {
- setWalletClient(new AdenaClient());
- }
+ } catch {}
}
+ useEffect(() => {
+ if (walletClient) connectAccount();
+ }, [connectAccount, walletClient]);
+
return {
wallet,
account: walletAccount,
diff --git a/packages/web/src/layouts/governance-layout/GovernanceLayout.styles.ts b/packages/web/src/layouts/governance-layout/GovernanceLayout.styles.ts
new file mode 100644
index 000000000..65fe1167e
--- /dev/null
+++ b/packages/web/src/layouts/governance-layout/GovernanceLayout.styles.ts
@@ -0,0 +1,138 @@
+import mixins from "@styles/mixins";
+import styled from "@emotion/styled";
+import { fonts } from "@constants/font.constant";
+import { ContainerWidth, media } from "@styles/media";
+
+export const GovernanceLayoutWrapper = styled.div`
+ ${mixins.flexbox("column", "center", "flex-start")};
+ width: 100%;
+ background-color: ${({ theme }) => theme.color.background01};
+ color: ${({ theme }) => theme.color.text10};
+ .governance-section {
+ ${mixins.flexbox("column", "center", "flex-start")};
+ max-width: ${ContainerWidth.WEB_SECTION_CONTAINER};
+ width: 100%;
+ padding: 100px 0px;
+ gap: 36px;
+ @media (max-width: 1180px) {
+ max-width: ${ContainerWidth.TABLET_CONTAINER};
+ padding: 60px 0px;
+ gap: 24px;
+ }
+ ${media.mobile} {
+ max-width: ${ContainerWidth.MOBILE_CONTAINER};
+ width: 90%;
+ padding: 24px 0px 48px 0px;
+ gap: 12px;
+ }
+ }
+
+ .title-container {
+ position: relative;
+ max-width: ${ContainerWidth.WEB_CONTAINER};
+ width: 100%;
+ padding: 0px 40px 0px 40px;
+ ${media.tablet} {
+ gap: 16px;
+ }
+ ${media.mobile} {
+ padding: 0px 0px 0px 0px;
+ gap: 12px;
+ }
+ }
+
+ .title {
+ display: inline-block;
+ ${fonts.h3};
+ color: ${({ theme }) => theme.color.text02};
+ @media (max-width: 1180px) {
+ ${fonts.h4};
+ }
+ ${media.mobile} {
+ ${fonts.h5};
+ }
+ }
+ .sub-title-layout {
+ position: absolute;
+ ${mixins.flexbox("row", "center", "center", false)};
+ ${fonts.body11};
+ gap: 4px;
+ margin-left: 20px;
+ bottom: 7px;
+ top: 19px;
+ cursor: pointer;
+ p {
+ display: inline-block;
+ }
+ color: ${({ theme }) => theme.color.text04};
+ svg * {
+ fill: ${({ theme }) => theme.color.icon03};
+ }
+ @media (max-width: 1180px) {
+ margin-left: 12px;
+ bottom: 6px;
+ top: 15px;
+ }
+ ${media.mobile} {
+ margin-left: 10px;
+ bottom: 2px;
+ top: 9px;
+ }
+ }
+
+ .link-icon {
+ width: 16px;
+ height: 16px;
+ * {
+ fill: ${({ theme }) => theme.color.text07};
+ }
+ }
+
+ .summary-container {
+ ${mixins.flexbox("column", "flex-start", "flex-start")};
+ max-width: ${ContainerWidth.WEB_CONTAINER};
+ width: 100%;
+ padding: 0px 40px;
+ gap: 22px;
+ @media (max-width: 1180px) {
+ gap: 16px;
+ }
+ ${media.mobile} {
+ padding: 12px 0px 24px 0px;
+ }
+ }
+`;
+
+export const LinkButton = styled.div`
+ ${mixins.flexbox("row", "center", "center")};
+ width: 100%;
+ ${fonts.body11};
+ gap: 4px;
+ color: ${({ theme }) => theme.color.text04};
+ ${media.mobile} {
+ ${fonts.p3};
+ }
+ a {
+ ${mixins.flexbox("row", "center", "center")};
+ color: ${({ theme }) => theme.color.text07};
+ &:hover {
+ color: ${({ theme }) => theme.color.text08};
+ svg {
+ * {
+ fill: ${({ theme }) => theme.color.icon14};
+ }
+ }
+ }
+ }
+ svg {
+ width: 16px;
+ height: 16px;
+ * {
+ fill: ${({ theme }) => theme.color.text07};
+ }
+ }
+ ${media.mobile} {
+ margin-bottom: 8px;
+ flex-wrap: wrap;
+ }
+`;
diff --git a/packages/web/src/layouts/governance-layout/GovernanceLayout.tsx b/packages/web/src/layouts/governance-layout/GovernanceLayout.tsx
new file mode 100644
index 000000000..cb3f79286
--- /dev/null
+++ b/packages/web/src/layouts/governance-layout/GovernanceLayout.tsx
@@ -0,0 +1,55 @@
+import IconFile from "@components/common/icons/IconFile";
+import React, { useState } from "react";
+import { GovernanceLayoutWrapper, LinkButton } from "./GovernanceLayout.styles";
+import Link from "next/link";
+import IconStrokeArrowRight from "@components/common/icons/IconStrokeArrowRight";
+import LearnMoreModal from "@components/governance/learn-more-modal/LearnMoreModal";
+
+interface GovernanceLayoutProps {
+ header: React.ReactNode;
+ summary: React.ReactNode;
+ footer: React.ReactNode;
+ list: React.ReactNode;
+}
+
+const GovernanceLayout: React.FC = ({
+ header,
+ summary,
+ list,
+ footer,
+}) => {
+ const [isShowLearnMoreModal, setIsShowLearnMoreModal] = useState(false);
+ return (
+
+ {header}
+
+
+
Governance
+
setIsShowLearnMoreModal(true)}
+ >
+
Learn More
+
+
+
+
+ {summary}
+
+ Stake GNOS/GNOT Positions to earn xGNOS
+
+ Click here
+
+
+ {list}
+
+
+ {footer}
+ {isShowLearnMoreModal && (
+
+ )}
+
+ );
+};
+
+export default GovernanceLayout;
diff --git a/packages/web/src/models/pool/info/pool-card-info.ts b/packages/web/src/models/pool/info/pool-card-info.ts
new file mode 100644
index 000000000..daf1fe6c2
--- /dev/null
+++ b/packages/web/src/models/pool/info/pool-card-info.ts
@@ -0,0 +1,32 @@
+import { SwapFeeTierType } from "@constants/option.constant";
+import { POOL_TYPE } from "@containers/pool-list-container/PoolListContainer";
+import { TokenModel } from "@models/token/token-model";
+import { PoolRewardInfo } from "./pool-reward-info";
+
+export interface PoolCardInfo {
+ poolId: string;
+
+ tokenA: TokenModel;
+
+ tokenB: TokenModel;
+
+ feeTier: SwapFeeTierType;
+
+ liquidity: string;
+
+ apr: string;
+
+ volume24h: string;
+
+ fees24h: string;
+
+ rewards: PoolRewardInfo[];
+
+ incentiveType: POOL_TYPE;
+
+ tickInfo: {
+ ticks: string[];
+
+ currentTick: number;
+ };
+}
diff --git a/packages/web/src/models/pool/info/pool-list-info.ts b/packages/web/src/models/pool/info/pool-list-info.ts
new file mode 100644
index 000000000..c18fd99ba
--- /dev/null
+++ b/packages/web/src/models/pool/info/pool-list-info.ts
@@ -0,0 +1,32 @@
+import { SwapFeeTierType } from "@constants/option.constant";
+import { POOL_TYPE } from "@containers/pool-list-container/PoolListContainer";
+import { TokenModel } from "@models/token/token-model";
+import { PoolRewardInfo } from "./pool-reward-info";
+
+export interface PoolListInfo {
+ poolId: string;
+
+ tokenA: TokenModel;
+
+ tokenB: TokenModel;
+
+ feeTier: SwapFeeTierType;
+
+ liquidity: string;
+
+ apr: string;
+
+ volume24h: string;
+
+ fees24h: string;
+
+ rewards: PoolRewardInfo[];
+
+ incentiveType: POOL_TYPE;
+
+ tickInfo: {
+ ticks: string[];
+
+ currentTick: number;
+ };
+}
diff --git a/packages/web/src/models/pool/info/pool-reward-info.ts b/packages/web/src/models/pool/info/pool-reward-info.ts
new file mode 100644
index 000000000..a4e5fa57e
--- /dev/null
+++ b/packages/web/src/models/pool/info/pool-reward-info.ts
@@ -0,0 +1,6 @@
+import { TokenModel } from "@models/token/token-model";
+
+export interface PoolRewardInfo {
+ token: TokenModel;
+ amount: number;
+}
diff --git a/packages/web/src/models/pool/info/pool-select-item-info.ts b/packages/web/src/models/pool/info/pool-select-item-info.ts
index f726065ce..e362aa0c0 100644
--- a/packages/web/src/models/pool/info/pool-select-item-info.ts
+++ b/packages/web/src/models/pool/info/pool-select-item-info.ts
@@ -1,11 +1,9 @@
-import { PoolModel } from "@models/pool/pool-model";
import { TokenModel } from "@models/token/token-model";
-import BigNumber from "bignumber.js";
export interface PoolSelectItemInfo {
poolId: string;
- feeRate: number;
+ feeRate: string;
liquidityAmount: string;
@@ -13,13 +11,3 @@ export interface PoolSelectItemInfo {
tokenB: TokenModel;
}
-
-export function toPoolSelectItemInfo(pool: PoolModel): PoolSelectItemInfo {
- return {
- poolId: pool.id,
- liquidityAmount: BigNumber(pool.price).toFixed(),
- feeRate: pool.fee,
- tokenA: pool.tokenA,
- tokenB: pool.tokenB,
- };
-}
diff --git a/packages/web/src/models/pool/mapper/pool-mapper.ts b/packages/web/src/models/pool/mapper/pool-mapper.ts
new file mode 100644
index 000000000..a047c5491
--- /dev/null
+++ b/packages/web/src/models/pool/mapper/pool-mapper.ts
@@ -0,0 +1,123 @@
+import BigNumber from "bignumber.js";
+import { PoolListInfo } from "../info/pool-list-info";
+import { PoolModel } from "../pool-model";
+import { SwapFeeTierInfoMap } from "@constants/option.constant";
+import { PoolRewardInfo } from "../info/pool-reward-info";
+import { PoolCardInfo } from "../info/pool-card-info";
+import { PoolSelectItemInfo } from "../info/pool-select-item-info";
+
+export class PoolMapper {
+ public static toListInfo(poolModel: PoolModel): PoolListInfo {
+ const {
+ id,
+ price,
+ tokenA,
+ tokenB,
+ volume,
+ totalVolume,
+ fee,
+ feeVolume,
+ apr,
+ } = poolModel;
+
+ const feeTierInfo = Object.values(SwapFeeTierInfoMap).find(
+ info => `${info.fee}` === fee,
+ );
+
+ const defaultReward: PoolRewardInfo = {
+ token: {
+ chainId: "dev",
+ createdAt: "2023-10-12T06:56:12+09:00",
+ name: "Gnoswap",
+ address: "g1sqaft388ruvsseu97r04w4rr4szxkh4nn6xpax",
+ path: "gno.land/r/gnos",
+ decimals: 4,
+ symbol: "GNOS",
+ logoURI: "/gnos.svg",
+ priceId: "gno.land/r/gnos",
+ },
+ amount: 10,
+ };
+
+ return {
+ poolId: id,
+ tokenA,
+ tokenB,
+ feeTier: feeTierInfo?.type || "NONE",
+ liquidity: `$${BigNumber(totalVolume.amount).toFormat()}`,
+ apr: `${BigNumber(apr).toFormat(2)}%`,
+ volume24h: `$${BigNumber(volume.amount).toFormat()}`,
+ fees24h: `$${BigNumber(feeVolume).toFormat()}`,
+ rewards: [defaultReward],
+ incentiveType: "Incentivized",
+ tickInfo: {
+ currentTick: price,
+ ticks: [],
+ },
+ };
+ }
+
+ public static toPoolSelectItemInfo(pool: PoolModel): PoolSelectItemInfo {
+ const feeRate =
+ Object.values(SwapFeeTierInfoMap).find(info => `${info.fee}` === pool.fee)
+ ?.rateStr || "-";
+
+ return {
+ poolId: pool.id,
+ liquidityAmount: BigNumber(pool.price).toFixed(),
+ feeRate,
+ tokenA: pool.tokenA,
+ tokenB: pool.tokenB,
+ };
+ }
+
+ public static toCardInfo(poolModel: PoolModel): PoolCardInfo {
+ const {
+ id,
+ price,
+ tokenA,
+ tokenB,
+ volume,
+ totalVolume,
+ fee,
+ feeVolume,
+ apr,
+ } = poolModel;
+
+ const feeTierInfo = Object.values(SwapFeeTierInfoMap).find(
+ info => `${info.fee}` === fee,
+ );
+
+ const defaultReward: PoolRewardInfo = {
+ token: {
+ chainId: "dev",
+ createdAt: "2023-10-12T06:56:12+09:00",
+ name: "Gnoswap",
+ address: "g1sqaft388ruvsseu97r04w4rr4szxkh4nn6xpax",
+ path: "gno.land/r/gnos",
+ decimals: 4,
+ symbol: "GNOS",
+ logoURI: "/gnos.svg",
+ priceId: "gno.land/r/gnos",
+ },
+ amount: 10,
+ };
+
+ return {
+ poolId: id,
+ tokenA,
+ tokenB,
+ feeTier: feeTierInfo?.type || "NONE",
+ liquidity: `$${BigNumber(totalVolume.amount).toFormat()}`,
+ apr: `${BigNumber(apr).toFormat(2)}%`,
+ volume24h: `$${BigNumber(volume.amount).toFormat()}`,
+ fees24h: `$${BigNumber(feeVolume).toFormat()}`,
+ rewards: [defaultReward],
+ incentiveType: "Incentivized",
+ tickInfo: {
+ currentTick: price,
+ ticks: [],
+ },
+ };
+ }
+}
diff --git a/packages/web/src/models/pool/pool-model.ts b/packages/web/src/models/pool/pool-model.ts
index 7c8acda2b..7c6515361 100644
--- a/packages/web/src/models/pool/pool-model.ts
+++ b/packages/web/src/models/pool/pool-model.ts
@@ -35,7 +35,7 @@ export interface PoolModel {
totalVolume: AmountModel;
- fee: number;
+ fee: string;
feeVolume: number;
diff --git a/packages/web/src/models/swap/swap-confirm-model.ts b/packages/web/src/models/swap/info/swap-confirm-model.ts
similarity index 80%
rename from packages/web/src/models/swap/swap-confirm-model.ts
rename to packages/web/src/models/swap/info/swap-confirm-model.ts
index ef9b3ca2f..a4b84d90e 100644
--- a/packages/web/src/models/swap/swap-confirm-model.ts
+++ b/packages/web/src/models/swap/info/swap-confirm-model.ts
@@ -1,4 +1,4 @@
-import { ExactTypeOption } from "@common/values/data-constant";
+import { SwapDirectionType } from "@common/values/data-constant";
import BigNumber from "bignumber.js";
import { AmountModel } from "@models/common/amount-model";
import { TokenInfo } from "@models/token/token-info";
@@ -8,7 +8,7 @@ export interface SwapConfirmModel {
tokenB: TokenInfo;
tokenAAmount: AmountModel;
tokenBAmount: AmountModel;
- type: ExactTypeOption;
+ type: SwapDirectionType;
slippage: number;
gasFee: BigNumber;
}
diff --git a/packages/web/src/models/swap/swap-fee-tier-info.ts b/packages/web/src/models/swap/swap-fee-tier-info.ts
new file mode 100644
index 000000000..6a75f30cb
--- /dev/null
+++ b/packages/web/src/models/swap/swap-fee-tier-info.ts
@@ -0,0 +1,6 @@
+import { SwapFeeTierType } from "@constants/option.constant";
+
+export interface SwapFeeTierInfo {
+ swapFeeTier: SwapFeeTierType;
+ selectedRange: number;
+}
diff --git a/packages/web/src/models/swap/swap-result-info.ts b/packages/web/src/models/swap/swap-result-info.ts
new file mode 100644
index 000000000..6f33564e3
--- /dev/null
+++ b/packages/web/src/models/swap/swap-result-info.ts
@@ -0,0 +1,4 @@
+export interface SwapResultInfo {
+ success: boolean;
+ hash?: string;
+}
diff --git a/packages/web/src/models/swap/swap-route-info.ts b/packages/web/src/models/swap/swap-route-info.ts
new file mode 100644
index 000000000..2dcc064a2
--- /dev/null
+++ b/packages/web/src/models/swap/swap-route-info.ts
@@ -0,0 +1,13 @@
+import { PoolModel } from "@models/pool/pool-model";
+import { TokenModel } from "@models/token/token-model";
+import { AmountModel } from "@models/common/amount-model";
+
+export interface SwapRouteInfo {
+ version: string;
+ from: TokenModel;
+ to: TokenModel;
+ pools: PoolModel[];
+ weight: number;
+ gasFee: AmountModel;
+ gasFeeUSD: number;
+}
diff --git a/packages/web/src/models/swap/swap-summary-info.ts b/packages/web/src/models/swap/swap-summary-info.ts
new file mode 100644
index 000000000..28e87f886
--- /dev/null
+++ b/packages/web/src/models/swap/swap-summary-info.ts
@@ -0,0 +1,43 @@
+import { SwapDirectionType } from "@common/values";
+import { AmountModel } from "@models/common/amount-model";
+import { TokenModel } from "@models/token/token-model";
+import { toNumberFormat } from "@utils/number-utils";
+
+export interface SwapSummaryInfo {
+ tokenA: TokenModel;
+
+ tokenB: TokenModel;
+
+ swapDirection: SwapDirectionType;
+
+ swapRate: number;
+
+ swapRateUSD: number;
+
+ priceImpact: number;
+
+ guaranteedAmount: AmountModel; // Apply slippage
+
+ gasFee: AmountModel;
+
+ gasFeeUSD: number;
+}
+
+export function swapDirectionToGuaranteedType(
+ swapDirection: SwapDirectionType,
+) {
+ if (swapDirection === "EXACT_IN") {
+ return "Min. Received";
+ }
+ return "Max. Sent";
+}
+
+export function getMinReceivedBy(swapSummaryInfo: SwapSummaryInfo) {
+ const { swapDirection, guaranteedAmount } = swapSummaryInfo;
+ if (swapDirection === "EXACT_OUT") {
+ return "-";
+ }
+ return `${toNumberFormat(guaranteedAmount.amount)} ${
+ guaranteedAmount.currency
+ }`;
+}
diff --git a/packages/web/src/models/swap/swap-token-info.ts b/packages/web/src/models/swap/swap-token-info.ts
new file mode 100644
index 000000000..55618a53b
--- /dev/null
+++ b/packages/web/src/models/swap/swap-token-info.ts
@@ -0,0 +1,28 @@
+import { SwapDirectionType } from "@common/values";
+import { TokenModel } from "@models/token/token-model";
+
+export interface SwapTokenInfo {
+ tokenA: TokenModel | null;
+
+ tokenAAmount: string;
+
+ tokenABalance: string;
+
+ tokenAUSD: number;
+
+ tokenAUSDStr: string;
+
+ tokenB: TokenModel | null;
+
+ tokenBAmount: string;
+
+ tokenBBalance: string;
+
+ tokenBUSD: number;
+
+ tokenBUSDStr: string;
+
+ direction: SwapDirectionType;
+
+ slippage: number;
+}
diff --git a/packages/web/src/models/swap/swap-token-model.ts b/packages/web/src/models/swap/swap-token-model.ts
deleted file mode 100644
index b080200fe..000000000
--- a/packages/web/src/models/swap/swap-token-model.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { TokenInfo } from "@models/token/token-info";
-
-export interface SwapTokenModel {
- token: TokenInfo;
- amount: string;
- price: string;
- balance: string;
-}
diff --git a/packages/web/src/models/token/token-price-model.ts b/packages/web/src/models/token/token-price-model.ts
index 6f75c5c3b..d2b35a90e 100644
--- a/packages/web/src/models/token/token-price-model.ts
+++ b/packages/web/src/models/token/token-price-model.ts
@@ -1,9 +1,11 @@
export interface TokenPriceModel {
- usd: number;
- changedBefore1D: number;
- changedBefore7D: number;
- changedBefore30D: number;
- marketCap: number;
- liquidity: number;
- volume: number;
+ path: string;
+ usd: string;
+ change1h: string;
+ change1d: string;
+ change7d: string;
+ change30d: string;
+ marketCap: string;
+ liquidity: string;
+ volume: string;
}
diff --git a/packages/web/src/models/token/token-swap-model.ts b/packages/web/src/models/token/token-swap-model.ts
index 36b70ddae..8f9714c55 100644
--- a/packages/web/src/models/token/token-swap-model.ts
+++ b/packages/web/src/models/token/token-swap-model.ts
@@ -1,8 +1,8 @@
-import { ExactTypeOption } from "@common/values/data-constant";
+import { SwapDirectionType } from "@common/values/data-constant";
import { TokenInfo } from "./token-info";
export interface TokenSwapModel {
tokenA: TokenInfo | null;
tokenB: TokenInfo | null;
- type: ExactTypeOption;
+ type: SwapDirectionType;
}
diff --git a/packages/web/src/pages/governance.tsx b/packages/web/src/pages/governance.tsx
new file mode 100644
index 000000000..c3444a532
--- /dev/null
+++ b/packages/web/src/pages/governance.tsx
@@ -0,0 +1,16 @@
+import HeaderContainer from "@containers/header-container/HeaderContainer";
+import Footer from "@components/common/footer/Footer";
+import GovernanceLayout from "@layouts/governance-layout/GovernanceLayout";
+import GovernanceContainer from "@containers/governance-container/GovernanceContainer";
+import ProposalListContainer from "@containers/proposal-list-container/ProposalListContainer";
+
+export default function Dashboard() {
+ return (
+ }
+ summary={}
+ list={}
+ footer={}
+ />
+ );
+}
diff --git a/packages/web/src/providers/gnoswap-service-provider/GnoswapServiceProvider.tsx b/packages/web/src/providers/gnoswap-service-provider/GnoswapServiceProvider.tsx
index d5d948f5e..bfff38f5d 100644
--- a/packages/web/src/providers/gnoswap-service-provider/GnoswapServiceProvider.tsx
+++ b/packages/web/src/providers/gnoswap-service-provider/GnoswapServiceProvider.tsx
@@ -5,13 +5,15 @@ import { LiquidityRepository, LiquidityRepositoryMock } from "@repositories/liqu
import { PoolRepository } from "@repositories/pool";
import { PoolRepositoryImpl } from "@repositories/pool/pool-repository-impl";
import { StakingRepository, StakingRepositoryMock } from "@repositories/staking";
-import { SwapRepository, SwapRepositoryMock } from "@repositories/swap";
+import { SwapRepository } from "@repositories/swap";
import { TokenRepository } from "@repositories/token";
import { TokenRepositoryImpl } from "@repositories/token/token-repository-impl";
import { createContext, useEffect, useMemo, useState } from "react";
import { useAtom } from "jotai";
import { CommonState, WalletState } from "@states/index";
import { GnoProvider, GnoWSProvider } from "@gnolang/gno-js-client";
+import { SwapRepositoryImpl } from "@repositories/swap/swap-repository-impl";
+import ChainNetworkInfos from "@resources/chains.json";
interface GnoswapContextProps {
rpcProvider: GnoProvider | null;
@@ -32,9 +34,9 @@ const GnoswapServiceProvider: React.FC = ({
}) => {
const [networkClient] = useState(new AxiosClient(API_URL));
- const [localStorageClient] = useState(WebStorageClient.createLocalStorageClient());
+ const [localStorageClient, setLocalStorageClient] = useState(WebStorageClient.createLocalStorageClient());
- const [sessionStorageClient] = useState(WebStorageClient.createSessionStorageClient());
+ const [sessionStorageClient, setSessionStorageClient] = useState(WebStorageClient.createSessionStorageClient());
const [walletClient] = useAtom(WalletState.client);
@@ -43,8 +45,19 @@ const GnoswapServiceProvider: React.FC = ({
const [rpcProvider, setRPCProvider] = useState(null);
useEffect(() => {
- const provider = new GnoWSProvider(network.wsUrl, 5 * 1000);
- provider.waitForOpenConnection().then(() => setRPCProvider(provider));
+ if (window) {
+ setLocalStorageClient(WebStorageClient.createLocalStorageClient());
+ setSessionStorageClient(WebStorageClient.createSessionStorageClient());
+ }
+ }, []);
+
+ useEffect(() => {
+ const defaultChainId = process.env.NEXT_PUBLIC_DEFAULT_CHAIN_ID || "";
+ const currentNetwork = network || ChainNetworkInfos.find(info => info.chainId === defaultChainId);
+ if (currentNetwork) {
+ const provider = new GnoWSProvider(currentNetwork.wsUrl, 5 * 1000);
+ provider.waitForOpenConnection().then(() => setRPCProvider(provider));
+ }
}, [network]);
const accountRepository = useMemo(() => {
@@ -56,16 +69,16 @@ const GnoswapServiceProvider: React.FC = ({
}, []);
const poolRepository = useMemo(() => {
- return new PoolRepositoryImpl(networkClient);
- }, [networkClient]);
+ return new PoolRepositoryImpl(networkClient, walletClient);
+ }, [networkClient, walletClient]);
const stakingRepository = useMemo(() => {
return new StakingRepositoryMock();
}, []);
const swapRepository = useMemo(() => {
- return new SwapRepositoryMock(walletClient);
- }, []);
+ return new SwapRepositoryImpl(walletClient, rpcProvider, localStorageClient);
+ }, [localStorageClient, rpcProvider, walletClient]);
const tokenRepository = useMemo(() => {
return new TokenRepositoryImpl(networkClient, localStorageClient);
diff --git a/packages/web/src/repositories/account/account-repository-impl.ts b/packages/web/src/repositories/account/account-repository-impl.ts
index 0113726f8..b0bf9b903 100644
--- a/packages/web/src/repositories/account/account-repository-impl.ts
+++ b/packages/web/src/repositories/account/account-repository-impl.ts
@@ -12,15 +12,16 @@ import { SendTransactionRequestParam } from "@common/clients/wallet-client/proto
import { AccountMapper } from "@models/account/mapper/account-mapper";
import { NetworkClient } from "@common/clients/network-client";
import { AccountBalanceModel } from "@models/account/account-balance-model";
+import { CommonError } from "@common/errors";
export class AccountRepositoryImpl implements AccountRepository {
- private walletClient: WalletClient;
+ private walletClient: WalletClient | null;
private networkClient: NetworkClient;
private localStorageClient: StorageClient;
private sessionStorageClient: StorageClient