From c7dde00cb28ff34922243f44757dd2a8c97f1858 Mon Sep 17 00:00:00 2001 From: dsudit01 <79417718+dsudit01@users.noreply.github.com> Date: Thu, 12 Oct 2023 17:25:13 -0400 Subject: [PATCH] refactor ambient and canto dex lp modals to work with correct numbers, include ambient in normal table lp interface --- app/lp/components/ambientLP.module.scss | 0 app/lp/components/ambientLPModal.tsx | 368 ++++++++----- app/lp/components/cantoDexLPModal.tsx | 520 ++++++------------ app/lp/components/newAmbientLPModal.tsx | 448 --------------- app/lp/components/pairRow.tsx | 122 +++- .../{stakeModal.tsx => stakeLPModal.tsx} | 60 +- app/lp/page.tsx | 144 ++--- components/icon/icon.tsx | 2 + config/jsons/bridgeInTokens.json | 12 +- config/jsons/bridgeOutTokens.json | 4 +- config/transactions/txMap.ts | 2 +- hooks/pairs/ambient/config/ambientPairs.ts | 4 +- .../pairs/ambient/helpers/ambientPairData.ts | 27 +- .../pairs/ambient/interfaces/ambientPairs.ts | 2 + .../ambientTx.ts | 1 - hooks/pairs/ambient/useAmbientPairs.ts | 10 +- .../pairs/cantoDex/interfaces/pairsTxTypes.ts | 1 - hooks/pairs/cantoDex/transactions/pairsTx.ts | 52 +- hooks/pairs/cantoDex/useCantoDex.ts | 9 +- tsconfig.json | 2 +- utils/ambient/liquidity.utils.ts | 4 +- utils/evm/helpers.utils.ts | 24 +- utils/tokens/tokenMath.utils.ts | 19 + 23 files changed, 754 insertions(+), 1083 deletions(-) delete mode 100644 app/lp/components/ambientLP.module.scss delete mode 100644 app/lp/components/newAmbientLPModal.tsx rename app/lp/components/{stakeModal.tsx => stakeLPModal.tsx} (81%) rename hooks/pairs/ambient/{transactions.ts => transactions}/ambientTx.ts (99%) diff --git a/app/lp/components/ambientLP.module.scss b/app/lp/components/ambientLP.module.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/app/lp/components/ambientLPModal.tsx b/app/lp/components/ambientLPModal.tsx index 19eba7c6..174c6980 100644 --- a/app/lp/components/ambientLPModal.tsx +++ b/app/lp/components/ambientLPModal.tsx @@ -1,10 +1,21 @@ +"use client"; import Button from "@/components/button/button"; -import Container from "@/components/container/container"; import Input from "@/components/input/input"; import Spacer from "@/components/layout/spacer"; -import Text from "@/components/text"; +import { + convertToBigNumber, + displayAmount, + formatBalance, +} from "@/utils/tokenBalances.utils"; +import { useState } from "react"; +import Container from "@/components/container/container"; import { ValidationReturn } from "@/config/interfaces"; -import { DEFAULT_AMBIENT_TICKS } from "@/hooks/pairs/ambient/config/prices"; +import Icon from "@/components/icon/icon"; +import Text from "@/components/text"; +import styles from "./cantoDex.module.scss"; +import Amount from "@/components/amount/amount"; +import Tabs from "@/components/tabs/tabs"; +import { ModalItem } from "@/app/lending/components/modal/modal"; import { AmbientPair } from "@/hooks/pairs/ambient/interfaces/ambientPairs"; import { AmbientTransactionParams, @@ -14,87 +25,133 @@ import { convertFromQ64RootPrice, getPriceFromTick, } from "@/utils/ambient/ambientMath.utils"; +import { DEFAULT_AMBIENT_TICKS } from "@/hooks/pairs/ambient/config/prices"; import { baseTokenFromConcLiquidity, getConcBaseTokensFromQuoteTokens, getConcQuoteTokensFromBaseTokens, quoteTokenFromConcLiquidity, } from "@/utils/ambient/liquidity.utils"; -import { - convertToBigNumber, - displayAmount, - formatBalance, -} from "@/utils/tokenBalances.utils"; import { percentOfAmount } from "@/utils/tokens/tokenMath.utils"; -import { useState } from "react"; -import styles from "./ambientLP.module.css"; -interface TestAmbientModalProps { +interface AmbientModalProps { pair: AmbientPair; validateParams: ( params: Partial ) => ValidationReturn; sendTxFlow: (params: Partial) => void; } -interface AddParams { - lowerTick: number; - upperTick: number; - amount: string; - isAmountBase: boolean; - txType: AmbientTxType.ADD_CONC_LIQUIDITY; -} -interface RemoveParams { - lowerTick: number; - upperTick: number; - liquidity: string; - txType: AmbientTxType.REMOVE_CONC_LIQUIDITY; -} -export const TestAmbientModal = (props: TestAmbientModalProps) => { - const [modalType, setModalType] = useState<"add" | "remove" | "base">("base"); +export const AmbientModal = (props: AmbientModalProps) => { return ( - - {modalType !== "base" && ( - - )} - - {props.pair.symbol} - - - {modalType === "base" && ( - <> - - - - )} - {modalType === "add" && ( - - )} - {modalType === "remove" && ( - - )} + +
+ + + Liquidity + + +
+ +
+
+ + +
+ + {props.pair.symbol} + +
+ + + ), + }, + { + title: "Remove", + isDisabled: + props.pair.userDetails?.defaultRangePosition.liquidity === + "0", + content: ( + +
+
+ + +
+ + {props.pair.symbol} + +
+ +
+ ), + }, + ]} + /> +
+
); }; -interface TestAddProps { +interface AddConcParams { + lowerTick: number; + upperTick: number; + amount: string; + isAmountBase: boolean; + txType: AmbientTxType.ADD_CONC_LIQUIDITY; +} +interface AddModalProps { pair: AmbientPair; - sendTxFlow: (params: AddParams) => void; - validateParams: (params: AddParams) => ValidationReturn; + sendTxFlow: (params: AddConcParams) => void; + validateParams: (params: AddConcParams) => ValidationReturn; } -const TestAddAmbientLiquidity = ({ +const AddAmbientLiquidity = ({ pair, - sendTxFlow, validateParams, -}: TestAddProps) => { + sendTxFlow, +}: AddModalProps) => { + // values const defaultMinPrice = getPriceFromTick(DEFAULT_AMBIENT_TICKS.minTick); const defaultMaxPrice = getPriceFromTick(DEFAULT_AMBIENT_TICKS.maxTick); const currentPrice = convertFromQ64RootPrice(pair.q64PriceRoot); @@ -153,37 +210,17 @@ const TestAddAmbientLiquidity = ({ return ( -

- price:{" "} - {displayAmount( - currentPrice, - Math.abs(pair.base.decimals - pair.quote.decimals) - )} -

-

- min-price:{" "} - {displayAmount( - defaultMinPrice, - Math.abs(pair.base.decimals - pair.quote.decimals) - )} -

-

- max-price:{" "} - {displayAmount( - defaultMaxPrice, - Math.abs(pair.base.decimals - pair.quote.decimals) - )} -

- - + { setValue(e.target.value, true); }} - type="amount" - balance={pair.base.balance ?? "0"} - decimals={pair.base.decimals} + IconUrl={pair.base.logoURI} + title={pair.base.symbol} + max={pair.base.balance ?? "0"} + symbol={pair.base.symbol} error={ !paramCheck.isValid && Number(baseValue) !== 0 && @@ -191,16 +228,19 @@ const TestAddAmbientLiquidity = ({ } errorMessage={paramCheck.errorMessage} /> - - + + { setValue(e.target.value, false); }} - type="amount" - balance={pair.quote.balance ?? "0"} - decimals={pair.quote.decimals} + IconUrl={pair.quote.logoURI} + title={pair.quote.symbol} + max={pair.quote.balance ?? "0"} + symbol={pair.quote.symbol} error={ !paramCheck.isValid && Number(quoteValue) !== 0 && @@ -208,8 +248,36 @@ const TestAddAmbientLiquidity = ({ } errorMessage={paramCheck.errorMessage} /> - + + + + + + + + + +
); }; -interface TestRemoveProps { +interface RemoveConcParams { + lowerTick: number; + upperTick: number; + liquidity: string; + txType: AmbientTxType.REMOVE_CONC_LIQUIDITY; +} +interface RemoveProps { pair: AmbientPair; - sendTxFlow: (params: RemoveParams) => void; - validateParams: (params: RemoveParams) => ValidationReturn; + sendTxFlow: (params: RemoveConcParams) => void; + validateParams: (params: RemoveConcParams) => ValidationReturn; } -const TestRemoveAmbientLiquidity = ({ + +const RemoveAmbientLiquidity = ({ pair, - sendTxFlow, validateParams, -}: TestRemoveProps) => { + sendTxFlow, +}: RemoveProps) => { const [percentToRemove, setPercentToRemove] = useState(0); const liquidityToRemove = percentOfAmount( pair.userDetails?.defaultRangePosition?.liquidity ?? "0", @@ -252,8 +328,8 @@ const TestRemoveAmbientLiquidity = ({ ); return ( - - +
+ setPercentToRemove(Number(e.target.value))} @@ -261,41 +337,59 @@ const TestRemoveAmbientLiquidity = ({ min={0} max={100} label="percent to remove" + error={Number(percentToRemove) < 0 || Number(percentToRemove) > 100} + errorMessage="Percentage must be between 0 and 100%" /> - - - expected base tokens:{" "} - {displayAmount( - baseTokenFromConcLiquidity( - pair.q64PriceRoot, - liquidityToRemove.data.toString(), - pair.userDetails?.defaultRangePosition.lowerTick ?? 0, - pair.userDetails?.defaultRangePosition.upperTick ?? 0 - ), - pair.base.decimals, - { - symbol: pair.base.symbol, - } - )} - - - - expected quote tokens:{" "} - {displayAmount( - quoteTokenFromConcLiquidity( - pair.q64PriceRoot, - liquidityToRemove.data.toString(), - pair.userDetails?.defaultRangePosition.lowerTick ?? 0, - pair.userDetails?.defaultRangePosition.upperTick ?? 0 - ), - pair.quote.decimals, - { - symbol: pair.quote.symbol, - } - )} + + + + Expected Tokens - + + + + + + + + - +
); }; diff --git a/app/lp/components/cantoDexLPModal.tsx b/app/lp/components/cantoDexLPModal.tsx index 3c89e5df..e5e0e503 100644 --- a/app/lp/components/cantoDexLPModal.tsx +++ b/app/lp/components/cantoDexLPModal.tsx @@ -2,11 +2,7 @@ import Button from "@/components/button/button"; import Input from "@/components/input/input"; import Spacer from "@/components/layout/spacer"; -import { - convertToBigNumber, - displayAmount, - formatBalance, -} from "@/utils/tokenBalances.utils"; +import { convertToBigNumber, displayAmount } from "@/utils/tokenBalances.utils"; import { useEffect, useState } from "react"; import Container from "@/components/container/container"; import { quoteRemoveLiquidity } from "@/utils/evm/pairs.utils"; @@ -25,50 +21,26 @@ import Amount from "@/components/amount/amount"; import Tabs from "@/components/tabs/tabs"; import { ModalItem } from "@/app/lending/components/modal/modal"; import Toggle from "@/components/toggle"; -import { StakeLPModal } from "./stakeModal"; -interface AddParams { - value1: string; - value2: string; - willStake: boolean; - slippage: number; - deadline: string; -} -interface RemoveParams { - amountLP: string; - unstake: boolean; - slippage: number; - deadline: string; -} -interface TestEditProps { +import { StakeLPModal } from "./stakeLPModal"; +import { + addTokenBalances, + convertTokenAmountToNote, + divideBalances, +} from "@/utils/tokens/tokenMath.utils"; +import { formatPercent } from "@/utils/formatting.utils"; +import { areEqualAddresses } from "@/utils/address.utils"; + +interface ManageCantoDexLPProps { pair: CantoDexPairWithUserCTokenData; sendTxFlow: (params: Partial) => void; validateParams: ( params: Partial ) => ValidationReturn; } -export const TestEditModal = (props: TestEditProps) => { +export const CantoDexLPModal = (props: ManageCantoDexLPProps) => { const [modalType, setModalType] = useState<"liquidity" | "stake" | "base">( "base" ); - const createAddParams = (params: AddParams) => ({ - pair: props.pair, - slippage: params.slippage, - deadline: params.deadline, - txType: CantoDexTxTypes.ADD_LIQUIDITY, - amounts: { - amount1: params.value1, - amount2: params.value2, - }, - stake: params.willStake, - }); - const createRemoveParams = (params: RemoveParams) => ({ - pair: props.pair, - slippage: params.slippage, - deadline: params.deadline, - txType: CantoDexTxTypes.REMOVE_LIQUIDITY, - amountLP: params.amountLP, - unstake: true, - }); const Liquidity = () => (
{ }, { title: "Remove", - // isDisabled: - // props.pair.clmData?.userDetails?.balanceOfCToken === "0", + isDisabled: + Number( + addTokenBalances( + props.pair.clmData?.userDetails + ?.supplyBalanceInUnderlying ?? "0", + props.pair.clmData?.userDetails?.balanceOfUnderlying ?? "0" + ) + ) === 0, content: (
@@ -160,102 +138,24 @@ export const TestEditModal = (props: TestEditProps) => {
); - //
- // setModalType("base")} - // > - //
- // - //
- // - // Stake - // - //
- //
- // - //
- // - // - // {props.pair.symbol} - // - //
- // - // props.validateParams(createAddParams(params)) - // } - // sendTxFlow={(params) => - // props.sendTxFlow({ - // txType: CantoDexTxTypes.STAKE, - // amountLP: - // props.pair.clmData?.userDetails - // ?.balanceOfUnderlying ?? "0", - // }) - // } - // /> - // - // ), - // }, - // { - // title: "Unstake", - // isDisabled: - // props.pair.clmData?.userDetails?.balanceOfCToken === "0", - // content: ( - // - //
- // - // - // {props.pair.symbol} - // - //
- // - // props.validateParams(createRemoveParams(params)) - // } - // sendTxFlow={(params) => - // props.sendTxFlow(createRemoveParams(params)) - // } - // /> - //
- // ), - // }, - // ]} - // /> - //
- //
- // ); - const Base = () => - props.pair.clmData?.userDetails?.balanceOfUnderlying !== "0" && ( + const Base = () => { + // total LP will be staked + unstaked balance + const totalLP = addTokenBalances( + props.pair.clmData?.userDetails?.supplyBalanceInUnderlying ?? "0", + props.pair.clmData?.userDetails?.balanceOfUnderlying ?? "0" + ); + // position value will be total LP * price + const { data: positionValue } = convertTokenAmountToNote( + totalLP, + props.pair.clmData?.price ?? "0" + ); + // pool share determined by total value of LP and tvl + const poolShare = divideBalances( + positionValue?.toString() ?? "0", + props.pair.tvl + ); + + return (
@@ -266,87 +166,52 @@ export const TestEditModal = (props: TestEditProps) => { - + - + - {/* - - - */} + - - {/* {props.pair.clmData?.userDetails?.balanceOfCToken !== "0" && ( - - )} */} ); + }; const modals = { liquidity: Liquidity(), @@ -379,20 +244,56 @@ export const TestEditModal = (props: TestEditProps) => { ); }; -interface TestAddProps { +// Functions to create correct parameters for transactions (add/remove liquidity) +interface AddTxParams { pair: CantoDexPairWithUserCTokenData; - validateParams: (params: AddParams) => ValidationReturn; - sendTxFlow: (params: AddParams) => void; + value1: string; + value2: string; + willStake: boolean; + slippage: number; + deadline: string; +} +const createAddParams = (params: AddTxParams) => ({ + pair: params.pair, + slippage: params.slippage, + deadline: params.deadline, + txType: CantoDexTxTypes.ADD_LIQUIDITY, + amounts: { + amount1: params.value1, + amount2: params.value2, + }, + stake: params.willStake, +}); +interface RemoveTxParams { + pair: CantoDexPairWithUserCTokenData; + amountLP: string; + slippage: number; + deadline: string; +} +const createRemoveParams = (params: RemoveTxParams) => ({ + pair: params.pair, + slippage: params.slippage, + deadline: params.deadline, + txType: CantoDexTxTypes.REMOVE_LIQUIDITY, + amountLP: params.amountLP, + unstake: true, +}); + +// Add and Remove Modals +interface AddLiquidityProps { + pair: CantoDexPairWithUserCTokenData; + validateParams: (params: AddTxParams) => ValidationReturn; + sendTxFlow: (params: AddTxParams) => void; } const AddLiquidityModal = ({ pair, validateParams, sendTxFlow, -}: TestAddProps) => { +}: AddLiquidityProps) => { // values const [slippage, setSlippage] = useState(2); const [deadline, setDeadline] = useState("10"); - const [willStake, setWillStake] = useState(false); + const [willStake, setWillStake] = useState(true); const [valueToken1, setValueToken1] = useState(""); const [valueToken2, setValueToken2] = useState(""); @@ -424,6 +325,7 @@ const AddLiquidityModal = ({ // validation const paramCheck = validateParams({ + pair, value1: ( convertToBigNumber(valueToken1, pair.token1.decimals).data ?? "0" ).toString(), @@ -435,6 +337,18 @@ const AddLiquidityModal = ({ deadline, }); + // speical function to display correct symbol if wcanto + const tokenSymbol = (token: { + chainId: number; + address: string; + symbol: string; + }) => { + const wcantoAddress = getCantoCoreAddress(Number(token.chainId), "wcanto"); + return areEqualAddresses(token.address, wcantoAddress ?? "") + ? "CANTO" + : token.symbol; + }; + return ( @@ -445,9 +359,9 @@ const AddLiquidityModal = ({ setValue(e.target.value, true); }} IconUrl={pair.token1.logoURI} - title={pair.token1.symbol} + title={tokenSymbol(pair.token1)} max={pair.token1.balance ?? "0"} - symbol={pair.token1.symbol} + symbol={tokenSymbol(pair.token1)} error={ !paramCheck.isValid && Number(valueToken1) !== 0 && @@ -465,9 +379,9 @@ const AddLiquidityModal = ({ setValue(e.target.value, false); }} IconUrl={pair.token2.logoURI} - title={pair.token2.symbol} + title={tokenSymbol(pair.token2)} max={pair.token2.balance ?? "0"} - symbol={pair.token2.symbol} + symbol={tokenSymbol(pair.token2)} error={ !paramCheck.isValid && Number(valueToken2) !== 0 && @@ -476,30 +390,7 @@ const AddLiquidityModal = ({ errorMessage={paramCheck.errorMessage} /> - {/* - - */} - - {/* */} - {/* */} setSlippage(Number(e.target.value))} + error={Number(slippage) > 100 || Number(slippage) < 0} + errorMessage="Slippage must be between 0-100%" /> % @@ -543,6 +436,8 @@ const AddLiquidityModal = ({ placeholder={Number(deadline).toString()} value={Number(deadline).toString()} onChange={(e) => setDeadline(e.target.value)} + error={Number(deadline) <= 0} + errorMessage="Deadline must be greater than 0 mins" /> mins @@ -558,126 +453,16 @@ const AddLiquidityModal = ({ - - - ); -}; - -const StakeLPToken = ({ pair, validateParams, sendTxFlow }: TestAddProps) => { - // values - const [slippage, setSlippage] = useState(2); - const [deadline, setDeadline] = useState("9999999999999999999999999"); - const [willStake, setWillStake] = useState(false); - const [valueToken1, setValueToken1] = useState(""); - const [valueToken2, setValueToken2] = useState(""); - - // set values based on optimization - async function setValue(value: string, token1: boolean) { - let optimalAmount; - if (token1) { - setValueToken1(value); - optimalAmount = await getOptimalValueBFormatted({ - chainId: Number(pair.token1.chainId), - pair, - valueChanged: 1, - amount: value, - }); - } else { - setValueToken2(value); - optimalAmount = await getOptimalValueBFormatted({ - chainId: Number(pair.token1.chainId), - pair, - valueChanged: 2, - amount: value, - }); - } - if (optimalAmount.error) return; - token1 - ? setValueToken2(optimalAmount.data) - : setValueToken1(optimalAmount.data); - } - - // validation - const paramCheck = validateParams({ - value1: ( - convertToBigNumber(valueToken1, pair.token1.decimals).data ?? "0" - ).toString(), - value2: ( - convertToBigNumber(valueToken2, pair.token2.decimals).data ?? "0" - ).toString(), - willStake, - slippage, - deadline, - }); - - return ( - - - { - setValue(e.target.value, true); - }} - IconUrl={pair.token1.logoURI} - title={pair.token1.symbol} - max={pair.token1.balance ?? "0"} - symbol={pair.token1.symbol} - error={ - !paramCheck.isValid && - Number(valueToken1) !== 0 && - paramCheck.errorMessage?.startsWith(pair.token1.symbol) - } - errorMessage={paramCheck.errorMessage} - /> - - {/* - - - */} - - {/* */} - - - - Stake - - setWillStake(value)} value={willStake} /> - - */} - - - - - - - - - {/* - - Stake - - setWillStake(value)} value={willStake} /> - */} - - - - - - ); -}; - -interface RemoveProps { - pair: AmbientPair; - sendTxFlow: (params: RemoveParams) => void; - validateParams: (params: RemoveParams) => ValidationReturn; -} - -const RemoveAmbientLiquidity = ({ - pair, - validateParams, - sendTxFlow, -}: RemoveProps) => { - const [percentToRemove, setPercentToRemove] = useState(0); - const liquidityToRemove = percentOfAmount( - pair.userDetails?.defaultRangePosition?.liquidity ?? "0", - percentToRemove - ); - - return ( -
- - setPercentToRemove(Number(e.target.value))} - type="number" - min={0} - max={100} - label="percent to remove" - /> - - - - Expected Tokens - - - - - - - - - - -
- ); -}; diff --git a/app/lp/components/pairRow.tsx b/app/lp/components/pairRow.tsx index d3b4a779..ef0d4d1d 100644 --- a/app/lp/components/pairRow.tsx +++ b/app/lp/components/pairRow.tsx @@ -2,6 +2,7 @@ import Button from "@/components/button/button"; import Icon from "@/components/icon/icon"; import Spacer from "@/components/layout/spacer"; import Text from "@/components/text"; +import { AmbientPair } from "@/hooks/pairs/ambient/interfaces/ambientPairs"; import { CantoDexPairWithUserCTokenData } from "@/hooks/pairs/cantoDex/interfaces/pairs"; import { formatPercent } from "@/utils/formatting.utils"; import { displayAmount } from "@/utils/tokenBalances.utils"; @@ -9,10 +10,11 @@ import { addTokenBalances, convertTokenAmountToNote, divideBalances, + percentOfAmount, } from "@/utils/tokens/tokenMath.utils"; import Image from "next/image"; -export const UserPairRow = ({ +export const UserCantoDexPairRow = ({ pair, onManage, }: { @@ -45,9 +47,8 @@ export const UserPairRow = ({ {displayAmount(totalUserLPValue.toString(), 18, { precision: 2, })} - - , - - {displayAmount(totalUserLpTokens, pair.decimals)} - , - - {displayAmount( - pair.clmData?.userDetails?.supplyBalanceInUnderlying ?? "0", - pair.decimals - )} - , + // + // {displayAmount(totalUserLpTokens, pair.decimals)} + // , + // + // {displayAmount( + // pair.clmData?.userDetails?.supplyBalanceInUnderlying ?? "0", + // pair.decimals + // )} + // , {displayAmount(pair.clmData?.userDetails?.rewards ?? "0", 18)} , @@ -75,7 +76,7 @@ export const UserPairRow = ({ ]; }; -export const GeneralPairRow = ({ +export const GeneralCantoDexPairRow = ({ pair, onAddLiquidity, }: { @@ -93,8 +94,8 @@ export const GeneralPairRow = ({ {displayAmount(pair.tvl, 18, { precision: 2, })} - onAddLiquidity(pair.address)}>Add Liquidity
, ]; + +export const GeneralAmbientPairRow = ({ + pair, + onAddLiquidity, +}: { + pair: AmbientPair; + onAddLiquidity: (pairAddress: string) => void; +}) => [ +
+ logo + + {pair.symbol} +
, + {"0.00%"}, + + {displayAmount(pair.liquidity.tvl, 18, { + precision: 2, + })} + + , + + {pair.stable ? "Stable" : "Volatile"} + , +
+ +
, +]; + +export const UserAmbientPairRow = ({ + pair, + onManage, +}: { + pair: AmbientPair; + onManage: (pairAddress: string) => void; +}) => { + return [ +
+ logo + + {pair.symbol} +
, + {"0.00%"}, + + {formatPercent( + divideBalances( + pair.userDetails?.defaultRangePosition.liquidity ?? "0", + pair.liquidity.rootLiquidity + ) + )} + , + + {displayAmount( + percentOfAmount( + pair.liquidity.tvl, + Number( + divideBalances( + pair.userDetails?.defaultRangePosition.liquidity ?? "0", + pair.liquidity.rootLiquidity + ) + ) + ).data, + 18 + )} + + , + {"0"}, +
+ +
, + ]; +}; diff --git a/app/lp/components/stakeModal.tsx b/app/lp/components/stakeLPModal.tsx similarity index 81% rename from app/lp/components/stakeModal.tsx rename to app/lp/components/stakeLPModal.tsx index 98acc023..b4bb64d5 100644 --- a/app/lp/components/stakeModal.tsx +++ b/app/lp/components/stakeLPModal.tsx @@ -17,7 +17,10 @@ import { useState } from "react"; import { ValidationReturn } from "@/config/interfaces"; import Amount from "@/components/amount/amount"; import { CantoDexTxTypes } from "@/hooks/pairs/cantoDex/interfaces/pairsTxTypes"; -import { convertTokenAmountToNote } from "@/utils/tokens/tokenMath.utils"; +import { + addTokenBalances, + convertTokenAmountToNote, +} from "@/utils/tokens/tokenMath.utils"; import { ModalItem } from "@/app/lending/components/modal/modal"; interface Props { clpToken: CTokenWithUserData; @@ -35,28 +38,33 @@ interface Props { } export const StakeLPModal = (props: Props) => { - const Balances = ({ cToken }: { cToken: CTokenWithUserData }) => ( - - - - + // get total values to decide what can be done with tokens + const totalLP = addTokenBalances( + props.clpToken.userDetails?.supplyBalanceInUnderlying ?? "0", + props.clpToken.userDetails?.balanceOfUnderlying ?? "0" ); - const APRs = ({ cToken }: { cToken: CTokenWithUserData }) => ( - - + const CLMInfo = ({ cToken }: { cToken: CTokenWithUserData }) => ( + + + + + + + + ); @@ -106,18 +114,14 @@ export const StakeLPModal = (props: Props) => { setAmount(val.target.value); }} IconUrl={cLPToken.underlying.logoURI} - title={cLPToken.symbol} + title={cLPToken.underlying.symbol} max={maxAmount} - symbol={cLPToken.symbol} + symbol={cLPToken.underlying.symbol} error={!amountCheck.isValid && Number(amount) !== 0} errorMessage={amountCheck.errorMessage} /> - - - - - +
a.symbol.localeCompare(b.symbol) ); - const userPairs = cantoDexPairs.filter( + const userCantoDexPairs = cantoDexPairs.filter( (pair) => (pair.clmData?.userDetails?.balanceOfCToken !== "0" || pair.clmData?.userDetails?.balanceOfUnderlying !== "0") && @@ -61,7 +73,11 @@ export default function Page() { if (error) { console.log(error); } else { - txStore?.addNewFlow({ txFlow: flow, signer: signer }); + txStore?.addNewFlow({ + txFlow: flow, + signer: signer, + onSuccessCallback: () => selection.setPair(null), + }); } } function canPerformCantoDexTx( @@ -79,12 +95,19 @@ export default function Page() { if (error) { console.log(error); } else { - txStore?.addNewFlow({ txFlow: flow, signer: signer }); + txStore?.addNewFlow({ + txFlow: flow, + signer: signer, + onSuccessCallback: () => selection.setPair(null), + }); } } /** AMBIENT */ const { ambientPairs } = ambient; + const userAmbientPairs = ambientPairs.filter( + (pair) => Number(pair.userDetails?.defaultRangePosition.liquidity) !== 0 + ); //transactions function sendAmbientTxFlow(params: Partial) { @@ -97,7 +120,11 @@ export default function Page() { if (error) { console.log(error); } else { - txStore?.addNewFlow({ txFlow: flow, signer: signer }); + txStore?.addNewFlow({ + txFlow: flow, + signer: signer, + onSuccessCallback: () => selection.setPair(null), + }); } } function canPerformAmbientTx( @@ -119,7 +146,7 @@ export default function Page() {
setPair(null)}> {selectedPair && isCantoDexPair(selectedPair) && ( - - {userPairs.length > 0 && ( + {userCantoDexPairs.length + userAmbientPairs.length > 0 && ( ( - { - setPair(pairAddress); - }} - /> - ))} + columns={7} + processedData={[ + ...userCantoDexPairs.map((pair) => ( + { + setPair(pairAddress); + }} + /> + )), + ...userAmbientPairs.map((pair) => ( + { + setPair(pairAddress); + }} + /> + )), + ]} /> )} @@ -175,52 +213,24 @@ export default function Page() { title="All Pairs" headers={["Pair", "APR", "TVL", "Type", "action"]} columns={6} - processedData={sortedPairs.map((pair) => ( - { - setPair(pairAddress); - }} - /> - ))} - /> - -
[ - {pair.symbol}, - - {displayAmount( - baseTokenFromConcLiquidity( - pair.q64PriceRoot, - pair.userDetails?.defaultRangePosition.liquidity ?? "0", - pair.userDetails?.defaultRangePosition.lowerTick ?? 0, - pair.userDetails?.defaultRangePosition.upperTick ?? 0 - ), - pair.base.decimals - )} - , - - {displayAmount( - quoteTokenFromConcLiquidity( - pair.q64PriceRoot, - pair.userDetails?.defaultRangePosition.liquidity ?? "0", - pair.userDetails?.defaultRangePosition.lowerTick ?? 0, - pair.userDetails?.defaultRangePosition.upperTick ?? 0 - ), - pair.quote.decimals - )} - , -
- - , -
, - ])} + processedData={[ + ...sortedPairs.map((pair) => ( + { + setPair(pairAddress); + }} + /> + )), + ...ambientPairs.map((pair) => ( + setPair(pairAddress)} + /> + )), + ]} /> diff --git a/components/icon/icon.tsx b/components/icon/icon.tsx index e12f3509..0b157e8d 100644 --- a/components/icon/icon.tsx +++ b/components/icon/icon.tsx @@ -8,6 +8,7 @@ interface Props { color?: "primary" | "accent" | "dark"; className?: string; themed?: boolean; + style?: React.CSSProperties; } const Icon = (props: Props) => { @@ -23,6 +24,7 @@ const Icon = (props: Props) => { ? "invert(0)" : "invert(var(--dark-mode))" : "", + ...props.style, }} alt="icon" width={props.icon.size || 16} diff --git a/config/jsons/bridgeInTokens.json b/config/jsons/bridgeInTokens.json index a633df5f..0978fc33 100644 --- a/config/jsons/bridgeInTokens.json +++ b/config/jsons/bridgeInTokens.json @@ -79,7 +79,7 @@ "name": "cNote", "symbol": "cNOTE", "decimals": 18, - "icon": "/tokens/note.svg", + "icon": "https://raw.githubusercontent.com/Plex-Engineer/public-assets/main/icons/tokens/cNote.svg", "isOFT": true, "isOFTProxy": false, "bridgeMethods": [ @@ -488,7 +488,7 @@ "name": "cNote", "symbol": "cNOTE", "decimals": 18, - "icon": "/tokens/note.svg", + "icon": "https://raw.githubusercontent.com/Plex-Engineer/public-assets/main/icons/tokens/cNote.svg", "isOFT": true, "isOFTProxy": false, "bridgeMethods": [ @@ -518,7 +518,7 @@ "name": "cNote", "symbol": "cNOTE", "decimals": 18, - "icon": "/tokens/note.svg", + "icon": "https://raw.githubusercontent.com/Plex-Engineer/public-assets/main/icons/tokens/cNote.svg", "isOFT": true, "isOFTProxy": false, "bridgeMethods": [ @@ -548,7 +548,7 @@ "name": "cNote", "symbol": "cNOTE", "decimals": 18, - "icon": "/tokens/note.svg", + "icon": "https://raw.githubusercontent.com/Plex-Engineer/public-assets/main/icons/tokens/cNote.svg", "isOFT": true, "isOFTProxy": false, "bridgeMethods": [ @@ -578,7 +578,7 @@ "name": "cNote", "symbol": "cNOTE", "decimals": 18, - "icon": "/tokens/note.svg", + "icon": "https://raw.githubusercontent.com/Plex-Engineer/public-assets/main/icons/tokens/cNote.svg", "isOFT": true, "isOFTProxy": false, "bridgeMethods": [ @@ -608,7 +608,7 @@ "name": "cNote", "symbol": "cNOTE", "decimals": 18, - "icon": "/tokens/note.svg", + "icon": "https://raw.githubusercontent.com/Plex-Engineer/public-assets/main/icons/tokens/cNote.svg", "isOFT": true, "isOFTProxy": false, "bridgeMethods": [ diff --git a/config/jsons/bridgeOutTokens.json b/config/jsons/bridgeOutTokens.json index c18c1883..aa9e898e 100644 --- a/config/jsons/bridgeOutTokens.json +++ b/config/jsons/bridgeOutTokens.json @@ -36,7 +36,7 @@ "name": "cNote", "symbol": "cNOTE", "decimals": 18, - "icon": "/tokens/note.svg", + "icon": "https://raw.githubusercontent.com/Plex-Engineer/public-assets/main/icons/tokens/cNote.svg", "isOFT": true, "isOFTProxy": true, "oftUnderlyingAddress": "0xEe602429Ef7eCe0a13e4FfE8dBC16e101049504C", @@ -615,7 +615,7 @@ "name": "cNote", "symbol": "cNOTE", "decimals": 18, - "icon": "/tokens/note.svg", + "icon": "https://raw.githubusercontent.com/Plex-Engineer/public-assets/main/icons/tokens/cNote.svg", "isOFT": true, "isOFTProxy": true, "oftUnderlyingAddress": "0x04E52476d318CdF739C38BD41A922787D441900c", diff --git a/config/transactions/txMap.ts b/config/transactions/txMap.ts index 3f545f4c..996ab336 100644 --- a/config/transactions/txMap.ts +++ b/config/transactions/txMap.ts @@ -20,7 +20,7 @@ import { validateCTokenLendingRetryParams, } from "@/hooks/lending/transactions/lending"; import { AmbientTransactionParams } from "@/hooks/pairs/ambient/interfaces/ambientTxTypes"; -import { ambientLiquidityTx } from "@/hooks/pairs/ambient/transactions.ts/ambientTx"; +import { ambientLiquidityTx } from "@/hooks/pairs/ambient/transactions/ambientTx"; import { CantoDexTransactionParams, StakeLPParams, diff --git a/hooks/pairs/ambient/config/ambientPairs.ts b/hooks/pairs/ambient/config/ambientPairs.ts index fa56dcd3..b8b82d1e 100644 --- a/hooks/pairs/ambient/config/ambientPairs.ts +++ b/hooks/pairs/ambient/config/ambientPairs.ts @@ -8,7 +8,8 @@ const TESTNET_AMBIENT_PAIRS: BaseAmbientPair[] = [ address: "0x04E52476d318CdF739C38BD41A922787D441900c", chainId: 7701, decimals: 18, - logoURI: "/tokens/note.svg", + logoURI: + "https://raw.githubusercontent.com/Plex-Engineer/public-assets/main/icons/tokens/cNote.svg", name: "Collateral Note", symbol: "cNote", }, @@ -26,6 +27,7 @@ const TESTNET_AMBIENT_PAIRS: BaseAmbientPair[] = [ "0x04E52476d318CdF739C38BD41A922787D441900c-0xc51534568489f47949A828C8e3BF68463bdF3566", symbol: "cNoteUSDCLP", logoURI: "", + stable: true, }, ]; diff --git a/hooks/pairs/ambient/helpers/ambientPairData.ts b/hooks/pairs/ambient/helpers/ambientPairData.ts index c3f551eb..273f6c92 100644 --- a/hooks/pairs/ambient/helpers/ambientPairData.ts +++ b/hooks/pairs/ambient/helpers/ambientPairData.ts @@ -13,6 +13,11 @@ import { getQuoteLiquidity, } from "@/utils/ambient/liquidity.utils"; import { DEFAULT_AMBIENT_TICKS } from "../config/prices"; +import { + addTokenBalances, + convertTokenAmountToNote, +} from "@/utils/tokens/tokenMath.utils"; +import BigNumber from "bignumber.js"; export async function getGeneralAmbientPairData( chainId: number, @@ -80,6 +85,23 @@ export async function getGeneralAmbientPairData( const q64PriceRoot = ((curve.priceRoot_ ?? 0) as number).toString(); const concLiquidity = ((curve.concLiq_ ?? 0) as number).toString(); const rootLiquidity = (chunkedData[index][2].result ?? 0).toString(); + const baseLiquidity = getBaseLiquidity(q64PriceRoot, rootLiquidity); + const quoteLiquidity = getQuoteLiquidity(q64PriceRoot, rootLiquidity); + + // get tvl + const baseTokensInNote = convertTokenAmountToNote( + baseLiquidity, + new BigNumber(10).pow(36 - pair.base.decimals).toString() + ); + const quoteTokensInNote = convertTokenAmountToNote( + quoteLiquidity, + new BigNumber(10).pow(36 - pair.quote.decimals).toString() + ); + const tvl = addTokenBalances( + baseTokensInNote.data.toString(), + quoteTokensInNote.data.toString() + ); + const userPosition = chunkedData[index][3].result ? { userDetails: { @@ -97,9 +119,10 @@ export async function getGeneralAmbientPairData( q64PriceRoot, currentTick: chunkedData[index][1].result ?? 0, liquidity: { + tvl, rootLiquidity, - base: getBaseLiquidity(q64PriceRoot, rootLiquidity), - quote: getQuoteLiquidity(q64PriceRoot, rootLiquidity), + base: baseLiquidity, + quote: quoteLiquidity, }, concLiquidity, ...userPosition, diff --git a/hooks/pairs/ambient/interfaces/ambientPairs.ts b/hooks/pairs/ambient/interfaces/ambientPairs.ts index 148c3b14..b72b2521 100644 --- a/hooks/pairs/ambient/interfaces/ambientPairs.ts +++ b/hooks/pairs/ambient/interfaces/ambientPairs.ts @@ -5,6 +5,7 @@ export interface BaseAmbientPair { base: AmbientPairToken; quote: AmbientPairToken; poolIdx: number; + stable: boolean; } export interface AmbientPair extends BaseAmbientPair { @@ -12,6 +13,7 @@ export interface AmbientPair extends BaseAmbientPair { currentTick: number; // current tick of curve concLiquidity: string; // concentrated liquidity liquidity: { + tvl: string; base: string; quote: string; rootLiquidity: string; diff --git a/hooks/pairs/ambient/transactions.ts/ambientTx.ts b/hooks/pairs/ambient/transactions/ambientTx.ts similarity index 99% rename from hooks/pairs/ambient/transactions.ts/ambientTx.ts rename to hooks/pairs/ambient/transactions/ambientTx.ts index b62fdf00..109d0e4f 100644 --- a/hooks/pairs/ambient/transactions.ts/ambientTx.ts +++ b/hooks/pairs/ambient/transactions/ambientTx.ts @@ -44,7 +44,6 @@ export async function ambientLiquidityTx( if (!crocDexAddress) { return NEW_ERROR("Ambient liquidity tx:: Invalid chain id"); } - switch (params.txType) { case AmbientTxType.ADD_CONC_LIQUIDITY: return await addConcLiquidityFlow({ diff --git a/hooks/pairs/ambient/useAmbientPairs.ts b/hooks/pairs/ambient/useAmbientPairs.ts index 3bb34a75..e7701f2b 100644 --- a/hooks/pairs/ambient/useAmbientPairs.ts +++ b/hooks/pairs/ambient/useAmbientPairs.ts @@ -55,7 +55,7 @@ export default function useAmbientPairs( }, { onSuccess: (response) => { - console.log(response); + // console.log(response); }, onError: (error) => { console.log(error); @@ -125,20 +125,22 @@ export default function useAmbientPairs( const baseCheck = validateInputTokenAmount( baseAmount, base.balance ?? "0", - base.address, + base.symbol, base.decimals ); const quoteCheck = validateInputTokenAmount( quoteAmount, quote.balance ?? "0", - quote.address, + quote.symbol, quote.decimals ); const prefixError = !baseCheck.isValid ? base.symbol : quote.symbol; return { isValid: baseCheck.isValid && quoteCheck.isValid, errorMessage: - prefixError + (baseCheck.errorMessage || quoteCheck.errorMessage), + prefixError + + " " + + (baseCheck.errorMessage || quoteCheck.errorMessage), }; } case AmbientTxType.REMOVE_CONC_LIQUIDITY: { diff --git a/hooks/pairs/cantoDex/interfaces/pairsTxTypes.ts b/hooks/pairs/cantoDex/interfaces/pairsTxTypes.ts index a4388cd3..1ac984de 100644 --- a/hooks/pairs/cantoDex/interfaces/pairsTxTypes.ts +++ b/hooks/pairs/cantoDex/interfaces/pairsTxTypes.ts @@ -20,7 +20,6 @@ export type CantoDexTransactionParams = { | { txType: CantoDexTxTypes.REMOVE_LIQUIDITY; amountLP: string; - unstake: boolean; slippage: number; deadline: string; } diff --git a/hooks/pairs/cantoDex/transactions/pairsTx.ts b/hooks/pairs/cantoDex/transactions/pairsTx.ts index d85ac576..4ec78b6a 100644 --- a/hooks/pairs/cantoDex/transactions/pairsTx.ts +++ b/hooks/pairs/cantoDex/transactions/pairsTx.ts @@ -14,7 +14,11 @@ import { createApprovalTxs, getTokenBalance } from "@/utils/evm/erc20.utils"; import { TX_DESCRIPTIONS } from "@/config/consts/txDescriptions"; import { getCantoCoreAddress } from "@/config/consts/addresses"; import { areEqualAddresses } from "@/utils/address.utils"; -import { percentOfAmount } from "@/utils/tokens/tokenMath.utils"; +import { + greaterThanOrEqualTo, + percentOfAmount, + subtractTokenBalances, +} from "@/utils/tokens/tokenMath.utils"; import { quoteRemoveLiquidity } from "@/utils/evm/pairs.utils"; import { TransactionFlowType } from "@/config/transactions/txMap"; import { @@ -23,6 +27,7 @@ import { StakeLPParams, } from "../interfaces/pairsTxTypes"; import { displayAmount } from "@/utils/tokenBalances.utils"; +import { getEVMTimestamp } from "@/utils/evm/helpers.utils"; export async function cantoDexLPTx( params: CantoDexTransactionParams @@ -123,6 +128,15 @@ async function addLiquidityFlow( ); } + // add deadline to block timestamp + const { data: timestamp, error: timestampError } = await getEVMTimestamp( + params.chainId + ); + if (timestampError) { + return NEW_ERROR("addLiquidityFlow: " + errMsg(timestampError)); + } + const timeoutDeadline = timestamp + Math.floor(Number(params.deadline)) * 60; + /** add add liquidity tx to list */ txList.push( _addLiquidityTx( @@ -138,7 +152,7 @@ async function addLiquidityFlow( params.amounts.amount2, amount1Min.data, amount2Min.data, - params.deadline, + timeoutDeadline.toString(), TX_DESCRIPTIONS.ADD_LIQUIDITY( params.pair, displayAmount(params.amounts.amount1, params.pair.token1.decimals), @@ -181,21 +195,38 @@ async function removeLiquidityFlow( return NEW_ERROR("removeLiquidityFlow: incorrect tx type passed"); } // check for user details - if (!params.pair.clmData) { + if (!params.pair.clmData || !params.pair.clmData.userDetails) { return NEW_ERROR("removeLiquidityFlow: pair does not have user details"); } /** create tx list */ const txList: Transaction[] = []; /** Unstake */ - if (params.unstake) { + + // check if the amount is greater than LP balance and unstake enough for tx + const unstakeAmount = subtractTokenBalances( + params.amountLP, + params.pair.clmData.userDetails.balanceOfUnderlying + ); + if (greaterThanOrEqualTo(unstakeAmount, "0").data) { + // check that the user has enough LP to unstake + if ( + greaterThanOrEqualTo( + unstakeAmount, + params.pair.clmData.userDetails.supplyBalanceInUnderlying + ).data + ) { + return NEW_ERROR( + "removeLiquidityFlow: user does not have enough LP to unstake" + ); + } // remove LP from clm const { data: withdrawTx, error: withdrawError } = await cTokenLendingTx({ chainId: params.chainId, ethAccount: params.ethAccount, txType: CTokenLendingTxTypes.WITHDRAW, cToken: params.pair.clmData, - amount: params.amountLP, + amount: unstakeAmount, }); if (withdrawError) { return NEW_ERROR("removeLiquidityFlow: " + errMsg(withdrawError)); @@ -261,6 +292,15 @@ async function removeLiquidityFlow( ); } + // add deadline to block timestamp + const { data: timestamp, error: timestampError } = await getEVMTimestamp( + params.chainId + ); + if (timestampError) { + return NEW_ERROR("removeLiquidityFlow: " + errMsg(timestampError)); + } + const timeoutDeadline = timestamp + Math.floor(Number(params.deadline)) * 60; + /** add remove liquidity tx to list */ txList.push( _removeLiquidityTx( @@ -275,7 +315,7 @@ async function removeLiquidityFlow( params.amountLP, amount1Min.data, amount2Min.data, - params.deadline, + timeoutDeadline.toString(), TX_DESCRIPTIONS.REMOVE_LIQUIDITY( params.pair, displayAmount(params.amountLP, params.pair.decimals) diff --git a/hooks/pairs/cantoDex/useCantoDex.ts b/hooks/pairs/cantoDex/useCantoDex.ts index 07911033..6e3d9abf 100644 --- a/hooks/pairs/cantoDex/useCantoDex.ts +++ b/hooks/pairs/cantoDex/useCantoDex.ts @@ -22,6 +22,7 @@ import { areEqualAddresses } from "@/utils/address.utils"; import { validateInputTokenAmount } from "@/utils/validation.utils"; import { createNewCantoDexTxFLow } from "./helpers/createPairsFlow"; import { createNewClaimCLMRewardsFlow } from "@/hooks/lending/helpers/createLendingFlow"; +import { addTokenBalances } from "@/utils/tokens/tokenMath.utils"; export default function useCantoDex( params: CantoDexHookInputParams, @@ -142,6 +143,7 @@ export default function useCantoDex( isValid: token1Check.isValid && token2Check.isValid, errorMessage: prefixError + + " " + (token1Check.errorMessage || token2Check.errorMessage), }; } @@ -149,9 +151,10 @@ export default function useCantoDex( // if unstaking first, check supplyBalance, otherwise check balanceOfUnderlying return validateInputTokenAmount( txParams.amountLP, - txParams.unstake - ? userDetails.supplyBalanceInUnderlying - : userDetails.balanceOfUnderlying, + addTokenBalances( + userDetails.supplyBalanceInUnderlying, + userDetails.balanceOfUnderlying + ), pair.symbol, pair.decimals ); diff --git a/tsconfig.json b/tsconfig.json index 87843abb..46022b8b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,6 +23,6 @@ "@/*": ["./*"] }, }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "__tests__/utils/cosmos/transactions/txMessages.test.ts"], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "__tests__/utils/cosmos/transactions/txMessages.test.ts", "hooks/pairs/ambient/transactions"], "exclude": ["node_modules"] } diff --git a/utils/ambient/liquidity.utils.ts b/utils/ambient/liquidity.utils.ts index ab3254f7..cbff606b 100644 --- a/utils/ambient/liquidity.utils.ts +++ b/utils/ambient/liquidity.utils.ts @@ -32,7 +32,7 @@ export function getBaseLiquidity( // don't square price, want square root const baseLiquidity = priceScaled.times(rootLiquidity); // return as string - return baseLiquidity.toString(); + return baseLiquidity.integerValue().toString(); } /** @@ -59,7 +59,7 @@ export function getQuoteLiquidity( // don't square price, want square root const quoteLiquidity = rootLiquidityBN.dividedBy(priceScaled); // return as string - return quoteLiquidity.toString(); + return quoteLiquidity.integerValue().toString(); } /** diff --git a/utils/evm/helpers.utils.ts b/utils/evm/helpers.utils.ts index 9afed44a..ed78f282 100644 --- a/utils/evm/helpers.utils.ts +++ b/utils/evm/helpers.utils.ts @@ -1,4 +1,10 @@ -import { NEW_ERROR, NO_ERROR, ReturnWithError } from "@/config/interfaces"; +import { + NEW_ERROR, + NO_ERROR, + PromiseWithError, + ReturnWithError, + errMsg, +} from "@/config/interfaces"; import * as NETWORKS from "@/config/networks"; import Web3 from "web3"; @@ -24,3 +30,19 @@ export function getRpcUrlFromChainId(chainId: number): ReturnWithError { export function getProviderWithoutSigner(rpcUrl: string): Web3 { return new Web3(rpcUrl); } + +/** + * @notice gets current block timestamp of chainId + * @param {number} chainId chainId to get current block timestamp for + * @returns {PromiseWithError} current block timestamp or error + */ +export async function getEVMTimestamp( + chainId: number +): PromiseWithError { + const { data: rpcUrl, error: rpcError } = getRpcUrlFromChainId(chainId); + if (rpcError) return NEW_ERROR("getEVMTimestamp::" + errMsg(rpcError)); + const provider = getProviderWithoutSigner(rpcUrl); + const blockNumber = await provider.eth.getBlockNumber(); + const block = await provider.eth.getBlock(blockNumber); + return NO_ERROR(Number(block.timestamp)); +} diff --git a/utils/tokens/tokenMath.utils.ts b/utils/tokens/tokenMath.utils.ts index d088b651..c9adaee7 100644 --- a/utils/tokens/tokenMath.utils.ts +++ b/utils/tokens/tokenMath.utils.ts @@ -90,6 +90,25 @@ export function addTokenBalances(amount1: string, amount2: string): string { return amount1BN.data.plus(amount2BN.data).toString(); } +/** + * @notice subtracts two token balances + * @dev must be from the same token to keep decimals + * @param {string} amount1 first amount to subtract from + * @param {string} amount2 second amount to subtract + * @returns {string} difference of the two amounts + */ +export function subtractTokenBalances( + amount1: string, + amount2: string +): string { + const [amount1BN, amount2BN] = [ + convertToBigNumber(amount1), + convertToBigNumber(amount2), + ]; + if (amount1BN.error || amount2BN.error) return "0"; + return amount1BN.data.minus(amount2BN.data).toString(); +} + /** * @notice divides token balances * @dev must be from the same token to keep decimals