diff --git a/package.json b/package.json index 9dc79a9d..c38008e2 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "next": "11.0.2-canary.27", "next-i18next": "^8.7.0", "node-fetch": "^2.6.1", - "picklefinance-core": "^0.5.13", + "picklefinance-core": "^0.5.46", "rc-slider": "^10.0.0-alpha.6", "react": "^16.13.1", "react-apexcharts": "^1.3.7", @@ -96,6 +96,7 @@ "rooks": "^5.8.0", "rxjs": "^6.6.3", "sass": "^1.26.11", + "sharp": "^0.31.1", "styled-components": "^5.2.0", "swr": "^1.0.1", "unstated-next": "^1.1.0", @@ -122,6 +123,7 @@ "@types/recharts": "^1.8.19", "@types/recharts-scale": "^1.0.0", "@types/sass": "^1.16.0", + "@types/sharp": "^0.31.0", "@types/styled-components": "^5.1.3", "@types/tailwindcss": "^3.0.4", "@typescript-eslint/eslint-plugin": "^4.1.1", @@ -139,7 +141,8 @@ "tailwindcss": "^3.0.15", "typechain": "^8.0.0", "typescript": "^4.3.5", - "typesync": "^0.8.0" + "typesync": "^0.8.0", + "uuidv4": "^6.2.13" }, "husky": { "hooks": { diff --git a/pages/stats.tsx b/pages/stats.tsx index 77a614ba..9cccb266 100644 --- a/pages/stats.tsx +++ b/pages/stats.tsx @@ -56,7 +56,7 @@ const Stats: PickleFinancePage = () => { ); }; -const Loading: FC<{ ready: readyState; page?: "platform" | "chain" | "jar" }> = ({ +const Loading: FC<{ ready: ReadyState; page?: "platform" | "chain" | "jar" }> = ({ ready, page, }) => { @@ -93,7 +93,7 @@ const PageTitle: FC = () => { ); }; -export interface readyState { +export interface ReadyState { platform: boolean; chain: boolean; jar: boolean; diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 7eab1ce0..acee9b9d 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -542,6 +542,7 @@ "statsAndDocs": "Stats & Docs", "strategyAddress": "Strategy Address", "success": "Success!", + "tag": "Tags", "token": "Token", "tokens": "{{ amount }} Tokens", "tokensDeposited": "You have <1>{{ amount }} pTokens", diff --git a/public/protocols/uwu.png b/public/protocols/uwu.png new file mode 100644 index 00000000..d9b7de99 Binary files /dev/null and b/public/protocols/uwu.png differ diff --git a/public/tokens/tusd.png b/public/tokens/tusd.png new file mode 100644 index 00000000..aff51387 Binary files /dev/null and b/public/tokens/tusd.png differ diff --git a/v2/components/PickleDillBalanceCard.tsx b/v2/components/PickleDillBalanceCard.tsx index cdba6f45..c68dba92 100644 --- a/v2/components/PickleDillBalanceCard.tsx +++ b/v2/components/PickleDillBalanceCard.tsx @@ -32,8 +32,6 @@ const DillBalanceCard: FC = () => { } if (userModel && userModel.dill && userModel.dill.balance) { - // TODO this specific logic of dividing by 1e18 but then getting 3 decimals seems common. - // Might want to extract to a utility dill = (BigNumber.from(userModel.dill.balance).div(1e10).div(1e5).toNumber() / 1e3).toString(); if (userModel.dill.pickleLocked) lockedPickles = ( diff --git a/v2/features/farms/FarmTableRowBodyTransactionControls.tsx b/v2/features/farms/FarmTableRowBodyTransactionControls.tsx index 1b44cce0..f2c24140 100644 --- a/v2/features/farms/FarmTableRowBodyTransactionControls.tsx +++ b/v2/features/farms/FarmTableRowBodyTransactionControls.tsx @@ -1,12 +1,13 @@ import { FC } from "react"; import { useTranslation } from "next-i18next"; import { BigNumber, ethers } from "ethers"; +import { JarDetails } from "picklefinance-core/lib/model/PickleModelJson"; import { useAppSelector } from "v2/store"; import { AssetWithData, CoreSelectors } from "v2/store/core"; import { UserSelectors } from "v2/store/user"; import { jarDecimals } from "v2/utils/user"; -import { isAcceptingDeposits, jarSupportsStaking } from "v2/store/core.helpers"; +import { isAcceptingDeposits, isJar, jarSupportsStaking } from "v2/store/core.helpers"; import LoadingIndicator from "v2/components/LoadingIndicator"; import ApprovalFlow from "./flows/approval/ApprovalFlow"; import DepositFlow from "./flows/deposit/DepositFlow"; @@ -82,11 +83,16 @@ const FarmsTableRowBodyTransactionControls: FC = ({ asset }) => {

- {t("v2.farms.depositedToken", { token: asset.depositToken.name })} + p{t("v2.farms.depositedToken", { token: asset.depositToken.name })}

- {jarTokens} +

{jarTokens}

+ {!!(isJar(asset) && jarTokens) && ( +

+ ({asset.depositTokensInJar.tokens} {asset.depositToken.name}) +

+ )}
{!!(asset.depositToken.nativePath || hasMainnetZap) && ( @@ -124,7 +130,7 @@ const FarmsTableRowBodyTransactionControls: FC = ({ asset }) => {

- {t("v2.farms.stakedToken", { token: asset.depositToken.name })} + p{t("v2.farms.stakedToken", { token: asset.depositToken.name })}

diff --git a/v2/features/farms/FarmsTableBody.tsx b/v2/features/farms/FarmsTableBody.tsx index 4a9e2179..797ab6d6 100644 --- a/v2/features/farms/FarmsTableBody.tsx +++ b/v2/features/farms/FarmsTableBody.tsx @@ -2,7 +2,7 @@ import React, { FC } from "react"; import { useTranslation } from "next-i18next"; import FarmsTableRow from "./FarmsTableRow"; -import { AssetWithData, CoreSelectors, JarWithData } from "v2/store/core"; +import { AssetWithData, CoreSelectors } from "v2/store/core"; import { UserSelectors } from "v2/store/user"; import { Sort } from "v2/store/controls"; import { UserTokenData } from "picklefinance-core/lib/client/UserModel"; diff --git a/v2/features/farms/Pagination.tsx b/v2/features/farms/Pagination.tsx index b3560354..967501f3 100644 --- a/v2/features/farms/Pagination.tsx +++ b/v2/features/farms/Pagination.tsx @@ -1,4 +1,4 @@ -import { FC, MouseEventHandler, ReactElement } from "react"; +import { FC, MouseEventHandler } from "react"; import { useSelector } from "react-redux"; import { useTranslation } from "next-i18next"; import { ArrowRightIcon, ArrowLeftIcon } from "@heroicons/react/solid"; diff --git a/v2/features/stats/ChainStats.tsx b/v2/features/stats/ChainStats.tsx index f8f9002a..4a7dce23 100644 --- a/v2/features/stats/ChainStats.tsx +++ b/v2/features/stats/ChainStats.tsx @@ -11,13 +11,13 @@ import { } from "v2/features/stats/chain/BigMoverUtils"; import { PickleModelJson } from "picklefinance-core"; import { ChainSelectData } from "./ChainSelect"; -import { readyState } from "pages/stats"; +import { ReadyState } from "pages/stats"; const ChainStats: FC<{ core: PickleModelJson.PickleModelJson | undefined; chain: ChainSelectData; setJar: SetFunction; - ready: readyState; + ready: ReadyState; setReady: SetFunction; page: "platform" | "chain" | "jar" | undefined; }> = ({ core, chain, setJar, ready, setReady, page }) => { @@ -30,7 +30,7 @@ const ChainStats: FC<{ if (Object.keys(chain).length > 0) getChainData(chain.value) .then((data) => setChainData(data)) - .then(() => setReady((prev: readyState) => ({ ...prev, chain: true }))); + .then(() => setReady((prev: ReadyState) => ({ ...prev, chain: true }))); }; getData(); }, [chain]); @@ -44,7 +44,7 @@ const ChainStats: FC<{ useEffect(() => { if (tokenPctChangeData.length > 0 && tvlChange.length > 0) - setReady((prev: readyState) => ({ ...prev, chain: true })); + setReady((prev: ReadyState) => ({ ...prev, chain: true })); }, [tokenPctChangeData, tvlChange]); tokenPctChangeData.sort((a, b) => (a.tokenPriceChange || 0) - (b.tokenPriceChange || 0)); diff --git a/v2/features/stats/JarStats.tsx b/v2/features/stats/JarStats.tsx index e7809508..a6b8d462 100644 --- a/v2/features/stats/JarStats.tsx +++ b/v2/features/stats/JarStats.tsx @@ -9,51 +9,114 @@ import DocContainer from "v2/features/stats/jar/DocContainer"; import RevTableContainer from "v2/features/stats/jar/RevTableContainer"; import FarmsTable from "v2/features/farms/FarmsTable"; import { JarSelectData } from "./JarSelect"; -import { readyState } from "pages/stats"; +import { ReadyState } from "pages/stats"; +import { JarDefinition } from "picklefinance-core/lib/model/PickleModelJson"; +import { useTranslation } from "next-i18next"; +import TxHistoryContainer from "./jar/userHistory/TxHistoryContainer"; +import { UserJarHistoryPnlGenerator } from "picklefinance-core"; +import { + PnlTransactionWrapper, + UserJarHistory, +} from "picklefinance-core/lib/client/pnl/UserHistoryInterfaces"; +import RelatedTokens from "./jar/RelatedTokens"; +import { useAccount } from "v2/hooks"; const JarStats: FC<{ core: PickleModelJson.PickleModelJson | undefined; jar: JarSelectData; - ready: readyState; + ready: ReadyState; setReady: SetFunction; page: "platform" | "chain" | "jar" | undefined; }> = ({ core, jar, ready, setReady, page }) => { + const { t } = useTranslation("common"); + const account = useAccount(); + //const account = "0xfeedc450742ac0d9bb38341d9939449e3270f76f"; let assets = useSelector(CoreSelectors.makeAssetsSelector({ filtered: false, paginated: false })); const [jarData, setJarData] = useState({} as JarChartData); + const [userHistory, setUserHistory] = useState(); + const [userPnl, setUserPnl] = useState(); let asset: AssetWithData | undefined = {} as AssetWithData; + let assetJar: JarDefinition | undefined = {} as JarDefinition; + if (jar && jar.value) asset = assets.find((a) => a.details.apiKey.toLowerCase() === jar.value.toLowerCase()); + if (asset) assetJar = core?.assets.jars.find((j) => j.contract == asset?.contract); + + const addrs = Object.fromEntries( + Object.entries({ + User: account ? account.toLowerCase() : "account not found", + Jar: assetJar ? assetJar.contract.toLowerCase() : "jar not found", + Farm: assetJar && assetJar.farm ? assetJar.farm.farmAddress.toLowerCase() : "farm not found", + Null: "0x0000000000000000000000000000000000000000", + }).map(([key, value]) => [value, key]), + ); + + const chain = core?.chains.filter((c) => c.network === String(assetJar?.chain))[0]; + addrs["chain_native"] = chain + ? `${chain.gasToken[0].toUpperCase()}${chain.gasTokenSymbol.slice(1)}` + : "chain native"; + // fetch jar documentation useEffect(() => { const getData = async (): Promise => { if (Object.keys(jar).length > 0) getJarData(jar.value) .then((data) => setJarData(data)) - .then(() => setReady((prev: readyState) => ({ ...prev, jar: true }))); + .then(() => setReady((prev: ReadyState) => ({ ...prev, jar: true }))); }; getData(); - }, [jar]); + }, [jar]); // eslint-disable-line + + // fetch all user txn history if there is any + useEffect(() => { + const getUserHistory = async (account: string | null | undefined): Promise => { + account && + (await fetch(`https://api.pickle.finance/prod/protocol/userhistory/${account}`) + .then((resp) => resp.json()) + .then((jsonResp) => { + if (!JSON.stringify(jsonResp).toLowerCase().includes("invalid wallet")) + setUserHistory(jsonResp); + else console.info(jsonResp); + })); + }; + getUserHistory(account); + }, [account]); + + // generate pnl report for one jar from user history if/when it loads + useEffect(() => { + if (account && userHistory && userHistory[jar.value]) { + let pnl = new UserJarHistoryPnlGenerator(account, userHistory[jar.value]).generatePnL(); + DEBUG_OUT(pnl); + setUserPnl(pnl); + } + }, [userHistory]); // eslint-disable-line if (asset && page === "jar" && ready[page]) return ( <> -
- {asset && asset.depositTokensInJar && ( - - )} +
+ {asset.depositTokensInJar && }
+ {userPnl && userPnl.length > 0 && core && assetJar && ( + + )} {jarData && jarData.documentation && } - {jarData && - jarData.revenueExpenses && - jarData.revenueExpenses.recentHarvests.length > 0 && ( - +
+ {jarData && jarData.documentation && ( + )} + {jarData && + jarData.revenueExpenses && + jarData.revenueExpenses.recentHarvests.length > 0 && ( + + )} +
); return null; @@ -68,4 +131,9 @@ const getJarData = async (jarKey: string): Promise => { return data; }; +const DEBUG_OUT = (msg: any) => { + if (GLOBAL_DEBUG) console.log(msg); +}; +const GLOBAL_DEBUG = true; + export default JarStats; diff --git a/v2/features/stats/PlatformStats.tsx b/v2/features/stats/PlatformStats.tsx index 71179f7a..4ba36661 100644 --- a/v2/features/stats/PlatformStats.tsx +++ b/v2/features/stats/PlatformStats.tsx @@ -3,13 +3,13 @@ import { FC, useEffect, useState } from "react"; import { PlatformData, SetFunction } from "v2/types"; import ChainTableContainer from "./platform/ChainTableContainer"; import ChartContainer from "./shared/ChartContainer"; -import { readyState } from "pages/stats"; +import { ReadyState } from "pages/stats"; import PlatformHeader from "./platform/PlatformHeader"; const PlatformStats: FC<{ setChain: SetFunction; core: PickleModelJson.PickleModelJson | undefined; - ready: readyState; + ready: ReadyState; setReady: SetFunction; page: "platform" | "chain" | "jar" | undefined; }> = ({ setChain, core, ready, setReady, page }) => { @@ -18,7 +18,7 @@ const PlatformStats: FC<{ const getData = async (): Promise => { getPlatformData() .then((platformData) => setDataSeries(platformData)) - .then(() => setReady((prev: readyState) => ({ ...prev, platform: true }))); + .then(() => setReady((prev: ReadyState) => ({ ...prev, platform: true }))); }; getData(); }, []); diff --git a/v2/features/stats/jar/DocContainer.tsx b/v2/features/stats/jar/DocContainer.tsx index fa3099eb..6c74f97a 100644 --- a/v2/features/stats/jar/DocContainer.tsx +++ b/v2/features/stats/jar/DocContainer.tsx @@ -6,7 +6,7 @@ import Image from "next/image"; import { classNames } from "v2/utils"; const DocContainer: FC<{ docs: AssetDocumentationResult }> = ({ docs }) => { - const { description, obtain, social, risks, componentTokens } = docs; + const { description, obtain, social, risks } = docs; const { t } = useTranslation("common"); return ( <> @@ -16,36 +16,10 @@ const DocContainer: FC<{ docs: AssetDocumentationResult }> = ({ docs }) => {
- ); }; -const TokenText: FC<{ text: string }> = ({ text }) => { - let [isMore, setIsMore] = useState(false); - const { t } = useTranslation("common"); - - if (text.length > 600) - return ( - <> - - {text.slice(0, 400).concat(isMore ? "" : "... ")} - - {isMore ? ( - - {text.slice(300)} - - ) : null} - - - ); - else return

{text}

; -}; - const Description: FC<{ description: string; t: TFunction }> = ({ description, t }) => (

@@ -98,72 +72,4 @@ const Risks: FC<{ risks: string[]; t: TFunction }> = ({ risks, t }) => (

); -const RelatedTokens: FC<{ componentTokens: { [key: string]: string }; t: TFunction }> = ({ - componentTokens, - t, -}) => { - const [chosenToken, setChosenToken] = useState( - Object.keys(componentTokens).length > 0 ? Object.keys(componentTokens)[0] : "", - ); - return ( - <> -

- {t("v2.farms.docs.relatedTokens")} -

-
- - - - {Object.keys(componentTokens).map((token) => ( - - ))} - - -
setChosenToken(token)} - > -
-
- {token} -
-

- {token.toUpperCase()} -

-
-
-
- {componentTokens[chosenToken] && ( -
-

- {chosenToken.toUpperCase()} -

-
- {} -
-
- )} - - ); -}; - export default DocContainer; diff --git a/v2/features/stats/jar/RelatedTokens.tsx b/v2/features/stats/jar/RelatedTokens.tsx new file mode 100644 index 00000000..6ced3720 --- /dev/null +++ b/v2/features/stats/jar/RelatedTokens.tsx @@ -0,0 +1,98 @@ +import { TFunction, useTranslation } from "next-i18next"; +import Image from "next/image"; +import { FC, useState } from "react"; +import { classNames } from "v2/utils"; + +const RelatedTokens: FC<{ componentTokens: { [key: string]: string }; t: TFunction }> = ({ + componentTokens, + t, +}) => { + const [chosenToken, setChosenToken] = useState( + Object.keys(componentTokens).length > 0 ? Object.keys(componentTokens)[0] : "", + ); + return ( + <> +

+ {t("v2.farms.docs.relatedTokens")} +

+
+ + + + {Object.keys(componentTokens).map((token) => ( + + ))} + + +
setChosenToken(token)} + > +
+
+ {token} +
+

+ {token.toUpperCase()} +

+
+
+
+ {componentTokens[chosenToken] && ( +
+

+ {chosenToken.toUpperCase()} +

+
+ {} +
+
+ )} + + ); +}; + +const TokenText: FC<{ text: string }> = ({ text }) => { + let [isMore, setIsMore] = useState(false); + const { t } = useTranslation("common"); + + if (text.length > 600) + return ( + <> + {isMore ? ( + {text} + ) : ( + + {text.slice(0, 400).concat("...")} + + )} + + + ); + else return

{text}

; +}; + +export default RelatedTokens; diff --git a/v2/features/stats/jar/userHistory/CurrentSummary.tsx b/v2/features/stats/jar/userHistory/CurrentSummary.tsx new file mode 100644 index 00000000..6d16ab74 --- /dev/null +++ b/v2/features/stats/jar/userHistory/CurrentSummary.tsx @@ -0,0 +1,99 @@ +import { BigNumber, ethers } from "ethers"; +import { + Lot, + PnlTransactionWrapper, + StakingRewards, +} from "picklefinance-core/lib/client/pnl/UserHistoryInterfaces"; +import { JarDefinition } from "picklefinance-core/lib/model/PickleModelJson"; +import { FC } from "react"; +import { formatDollarsAddDecimalsForSmallNumbers, weiToVisibleString } from "v2/utils"; + +const CurrentSummary: FC<{ lastTxn: PnlTransactionWrapper; jar: JarDefinition }> = ({ + lastTxn, + jar, +}) => { + console.log(JSON.stringify(lastTxn, null, 2)); + const currentCost = lastTxn.pnlRollingDataWithLots.costOfOpenPositions; + const decimals = jar.details.decimals || jar.depositToken.decimals || 18; + const jarTokens = weiToVisibleString(lastTxn.pnlRollingDataWithLots.rollingWeiCount.toString(), decimals); + const currentUSDValue = (jar.details.ratio || 1) * + (lastTxn.pnlRollingDataWithLots.rollingWeiCount.gt(0) && jar.depositToken.price + ? weiMulPriceInDollars(lastTxn.pnlRollingDataWithLots.rollingWeiCount, jar.depositToken.price, decimals) + : 0); + console.log(JSON.stringify([currentCost, decimals, jarTokens, currentUSDValue], null, 2)); + console.log(JSON.stringify([lastTxn.pnlRollingDataWithLots.rollingWeiCount.toString(), jar.depositToken.price, decimals], null, 2)); + + // console.log("rolling data: " + JSON.stringify(lastTxn.pnlRollingDataWithLots)); + const rewardsSummedByToken = sumRewardsByToken(lastTxn.pnlRollingDataWithLots.rollingRewards); + // console.log("rewardsSummedByToken: " + JSON.stringify(rewardsSummedByToken)); + const totalRewards = Object.values(rewardsSummedByToken).reduce((acc, curr) => acc + curr, 0); + const safeJarName = jar.depositToken?.name || " this jar"; + return ( +
+

Your Summary for {safeJarName}

+
+ + + + + + +
+
+ ); +}; + +const SummaryCard: FC<{ value: any; label: string }> = ({ value, label }) => ( +
+

{value}

+

{label}

+
+); + +const sumRewardsByToken = (rewards: StakingRewards) => { + const rewardsSummedByToken: { [tokenAddr: string]: number } = {}; + Object.keys(rewards).forEach((token) => { + let tokenRewards = 0; + rewards[token].forEach((r) => (tokenRewards += r.value)); + rewardsSummedByToken[token] = tokenRewards; + }); + return rewardsSummedByToken; +}; + +const sumPnl = (lots: Lot[]) => { + let realizedProfit = 0; + for( let i = 0; i < lots.length; i++ ) { + if( lots[i].status === 'closed') { + realizedProfit += lots[i].saleProceedsUSD - lots[i].totalCostUsd; + } else { + const fractionOpen = lots[i].weiRemaining.mul(1000).div(lots[i].wei).toNumber() / 1000; + const fractionClosed = 1 - fractionOpen; + const costFraction = lots[i].totalCostUsd * fractionClosed; + realizedProfit += lots[i].saleProceedsUSD - costFraction; + } + } + return formatDollarsAddDecimalsForSmallNumbers(realizedProfit, 2); +}; + +const weiMulPriceInDollars = (numWei: BigNumber, tokenPrice: number, decimals: number) => { + try { + // console.log("1: " + wei + ", " + decimals + ", " + tokenPrice); + const log = Math.log(tokenPrice) / Math.log(10); + const precisionAdjust = log > 4 ? 0 : 5 - Math.floor(log); + const precisionAsNumber = Math.pow(10, precisionAdjust); + const tokenPriceWithPrecision = (tokenPrice * precisionAsNumber).toFixed(); + // console.log("2: " + tokenPrice + ", " + log + ", " + precisionAdjust + ", " + precisionAsNumber + ", " + tokenPriceWithPrecision + ", " + precisionAsNumber) + const resultBN = numWei.mul(tokenPriceWithPrecision).div(precisionAsNumber); + const asUSD = parseFloat(ethers.utils.formatUnits(resultBN, decimals)); + return asUSD; + } catch (err) { + return 0; + } +} +export default CurrentSummary; diff --git a/v2/features/stats/jar/userHistory/Pagination.tsx b/v2/features/stats/jar/userHistory/Pagination.tsx new file mode 100644 index 00000000..99af0217 --- /dev/null +++ b/v2/features/stats/jar/userHistory/Pagination.tsx @@ -0,0 +1,80 @@ +import { FC, MouseEventHandler } from "react"; +import { useTranslation } from "next-i18next"; +import { ArrowRightIcon, ArrowLeftIcon } from "@heroicons/react/solid"; + +import { classNames } from "v2/utils"; +import { SetFunction } from "v2/types"; + +enum PageDirection { + Forward = 1, + Back = -1, +} + +interface PaginationArrowProps { + currentPage: number; + pageCount: number; + onClick: MouseEventHandler; + type: "left" | "right"; +} + +const PaginationArrow: FC = ({ currentPage, pageCount, onClick, type }) => { + const Icon = type === "left" ? ArrowLeftIcon : ArrowRightIcon; + + return ( + + ); +}; + +const Pagination: FC<{ currentPage: number; setCurrentPage: SetFunction; pageCount: number }> = ({ + currentPage, + setCurrentPage, + pageCount, +}) => { + const { t } = useTranslation("common"); + const handlePageClick = (direction: PageDirection, setCurrentPage: SetFunction) => { + const newPage = currentPage + direction; + + if (newPage < 0 || newPage >= pageCount) return; + + setCurrentPage(newPage); + }; + + return ( + <> +
+ handlePageClick(PageDirection.Back, setCurrentPage)} + /> + + {t("v2.farms.pagination", { current: currentPage + 1, total: pageCount })} + + handlePageClick(PageDirection.Forward, setCurrentPage)} + /> +
+ + ); +}; + +export default Pagination; diff --git a/v2/features/stats/jar/userHistory/SortToggle.tsx b/v2/features/stats/jar/userHistory/SortToggle.tsx new file mode 100644 index 00000000..e5ad964f --- /dev/null +++ b/v2/features/stats/jar/userHistory/SortToggle.tsx @@ -0,0 +1,51 @@ +import { RadioGroup } from "@headlessui/react"; +import { useTranslation } from "next-i18next"; +import { FC } from "react"; +import { classNames } from "v2/utils"; + +const SortToggle: FC = ({ txSort, setTxSort }) => { + const { t } = useTranslation("common"); + + return ( + +
+ + classNames( + checked ? "bg-accent" : "bg-background-light hover:bg-background-lightest", + "border-y border-l border-accent font-title rounded-l-full cursor-pointer text-foreground py-3 px-6 flex items-center justify-center text-sm font-medium", + ) + } + > + +

{t("Old")}

+
+
+ + + classNames( + checked ? "bg-accent" : "bg-background-light hover:bg-background-lightest", + "border-y border-r border-accent font-title rounded-r-full cursor-pointer text-foreground py-3 px-6 flex items-center justify-center text-sm font-medium", + ) + } + > + +

{t("New")}

+
+
+
+
+ ); +}; + +interface ToggleProps { + txSort: string; + setTxSort: React.Dispatch>; +} + +export default SortToggle; diff --git a/v2/features/stats/jar/userHistory/TxHistoryContainer.tsx b/v2/features/stats/jar/userHistory/TxHistoryContainer.tsx new file mode 100644 index 00000000..f9558e2f --- /dev/null +++ b/v2/features/stats/jar/userHistory/TxHistoryContainer.tsx @@ -0,0 +1,37 @@ +import { PickleModelJson } from "picklefinance-core"; +import { PnlTransactionWrapper } from "picklefinance-core/lib/client/pnl/UserHistoryInterfaces"; +import { JarDefinition } from "picklefinance-core/lib/model/PickleModelJson"; +import { FC, useState } from "react"; +import { classNames } from "v2/utils"; +import CurrentSummary from "./CurrentSummary"; +import SortToggle from "./SortToggle"; +import TxHistoryTable from "./TxHistoryTable"; + +const TxHistoryContainer: FC<{ + userPnl: PnlTransactionWrapper[]; + core: PickleModelJson.PickleModelJson; + addrs: { [key: string]: string }; + jar: JarDefinition; + className?: string; +}> = ({ userPnl, core, addrs, jar, className }) => { + const [txSort, setTxSort] = useState<"old" | "new">("old"); + const lastTxn = userPnl.sort( + (a, b) => b.userTransaction.timestamp - a.userTransaction.timestamp, + )[0]; + return ( +
+ +
+

+ {"User History"} +

+
+ +
+
+ {core && } +
+ ); +}; + +export default TxHistoryContainer; diff --git a/v2/features/stats/jar/userHistory/TxHistoryTable.tsx b/v2/features/stats/jar/userHistory/TxHistoryTable.tsx new file mode 100644 index 00000000..5029f25c --- /dev/null +++ b/v2/features/stats/jar/userHistory/TxHistoryTable.tsx @@ -0,0 +1,66 @@ +import { PickleModelJson } from "picklefinance-core"; +import { PnlTransactionWrapper } from "picklefinance-core/lib/client/pnl/UserHistoryInterfaces"; +import { FC, useEffect, useState } from "react"; +import { classNames } from "v2/utils"; +import Pagination from "./Pagination"; +import TxTableBody from "./TxTableBody"; + +const TxHistoryTable: FC<{ + userPnl: PnlTransactionWrapper[]; + core: PickleModelJson.PickleModelJson; + addrs: { [key: string]: string }; + txSort: "old" | "new"; + className?: string; +}> = ({ userPnl, core, addrs, txSort, className }) => { + const [currentPage, setCurrentPage] = useState(0); + const sortedPnl = + txSort === "old" + ? userPnl.sort((a, b) => a.userTransaction.timestamp - b.userTransaction.timestamp) + : userPnl.sort((a, b) => b.userTransaction.timestamp - a.userTransaction.timestamp); + const displayPnl = + currentPage * 6 + 6 < sortedPnl.length + ? sortedPnl.slice(currentPage * 6, currentPage * 6 + 6) + : sortedPnl.slice(currentPage * 6); + return ( +
+
+ + + + + + + + + + + + + + + + + +
+
+ +
+
+
+ ); +}; + +const TxTableHeaderCell: FC<{ label: string }> = ({ label }) => ( + + {label} + +); + +export default TxHistoryTable; diff --git a/v2/features/stats/jar/userHistory/TxTableBody.tsx b/v2/features/stats/jar/userHistory/TxTableBody.tsx new file mode 100644 index 00000000..a4254109 --- /dev/null +++ b/v2/features/stats/jar/userHistory/TxTableBody.tsx @@ -0,0 +1,113 @@ +import { Disclosure, Transition } from "@headlessui/react"; +import { PickleModelJson } from "picklefinance-core"; +import { RawChain } from "picklefinance-core/lib/chain/Chains"; +import { FC, Fragment } from "react"; +import { classNames } from "v2/utils"; +import TxTableRowBody from "./TxTableRowBody"; +import TxTableRowHeader from "./TxTableRowHeader"; +import TxTableSpacerRow from "./TxTableSpacerRow"; +import { v4 as uuid } from "uuid"; +import { PnlTransactionWrapper } from "picklefinance-core/lib/client/pnl/UserHistoryInterfaces"; + +const TxTableBody: FC<{ + userPnl: PnlTransactionWrapper[]; + allPnlTx: PnlTransactionWrapper[]; + core: PickleModelJson.PickleModelJson; + addrs: { [key: string]: string }; + txSort: "old" | "new"; +}> = ({ userPnl, allPnlTx, core, addrs, txSort }) => { + return ( + <> + {txSort === "old" + ? userPnl + .sort((a, b) => a.userTransaction.timestamp - b.userTransaction.timestamp) + .map((tx) => ( + + )) + : userPnl + .sort((a, b) => b.userTransaction.timestamp - a.userTransaction.timestamp) + .map((tx) => ( + + ))} + + ); +}; + +const TxTableRow: FC<{ + tx: PnlTransactionWrapper; + userPnl: PnlTransactionWrapper[]; + allPnlTx: PnlTransactionWrapper[]; + txSort: "old" | "new"; + core: PickleModelJson.PickleModelJson; + addrs: { [key: string]: string }; +}> = ({ tx, userPnl, allPnlTx, txSort, core, addrs }) => { + const chain: RawChain | undefined = core.chains.filter( + (c) => c.chainId === tx.userTransaction.chain_id, + )[0]; + return ( + <> + + {({ open }) => ( + <> + + {core && ( + + )} + + + + + {core && chain && ( + + )} + + + + )} + + + + ); +}; + +export default TxTableBody; diff --git a/v2/features/stats/jar/userHistory/TxTableRowBody.tsx b/v2/features/stats/jar/userHistory/TxTableRowBody.tsx new file mode 100644 index 00000000..de98603f --- /dev/null +++ b/v2/features/stats/jar/userHistory/TxTableRowBody.tsx @@ -0,0 +1,210 @@ +import { PickleModelJson } from "picklefinance-core"; +import { RawChain } from "picklefinance-core/lib/chain/Chains"; +import { FC } from "react"; +import { formatNumber } from "v2/utils"; +import Link from "v2/components/Link"; +import { BigNumber } from "ethers"; +import { UserTransfer } from "picklefinance-core/lib/client/pnl/UserHistoryInterfaces"; + +const TxTableRowBody: FC<{ + transfers: UserTransfer[]; + core: PickleModelJson.PickleModelJson; + chain: RawChain; + addrs: { [key: string]: string }; +}> = ({ transfers, core, chain, addrs }) => ( + +
+
+ {transfers.map((t) => ( + + ))} +
+
+ +); + +const TransferDescription: FC<{ + transfer: UserTransfer; + core: PickleModelJson.PickleModelJson; + chain: RawChain; + tmpAddrs: { [addr: string]: string }; +}> = ({ transfer, core, chain, tmpAddrs }) => { + const { + fromAddr, + toAddr, + burned, + minted, + nTokens, + nGwei, + useGwei, + value, + tokenName, + } = transferData(transfer, core, tmpAddrs); + const useFromLink = fromAddr.includes("...") && chain !== undefined; + const useToLink = toAddr.includes("...") && chain !== undefined; + const nTokensString = useGwei + ? `${nGwei} Gwei of ${tokenName} tokens` + : `${nTokens}${" " + tokenName} tokens`; + return ( + <> + {burned && ( +

{`${nTokensString} ${value} were burned.`}

+ )} + {minted && ( + <> + {useToLink ? ( + +

+ {`${nTokensString} ${value} were minted and sent to`} +

+ +
+ ) : ( +

+ {`${nTokensString} ${value} were minted and sent to ${toAddr}`} +

+ )} + + )} + {!minted && !burned && ( + <> + {useFromLink && useToLink && ( +
+ +

+ {`sent ${nTokensString} ${value} to `} +

+ +
+ )} + {useFromLink && !useToLink && ( +
+ +

+ {`sent ${nTokensString} ${value} to ${toAddr}`} +

+
+ )} + {!useFromLink && useToLink && ( +
+

+ {`${fromAddr} sent ${nTokensString} ${value} to`} +

+ +
+ )} + {!useFromLink && !useToLink && ( +

+ {`${fromAddr} sent ${nTokensString} ${value} to ${toAddr}`} +

+ )} + + )} + + ); +}; + +const AddrLink: FC<{ chain: RawChain; addr: string; linkAddr: string; className?: string }> = ({ + chain, + addr, + linkAddr, + className, +}) => ( + + {addr} + +); + +const transferData = ( + transfer: UserTransfer, + core: PickleModelJson.PickleModelJson, + addrs: { [addr: string]: string }, +) => { + const tmp = core && coreToAddrMap(core); + addrs = tmp && { ...tmp, ...addrs }; + const fromAddr = + addrs[transfer.fromAddress.toLowerCase()] || + transfer.fromAddress.slice(0, 5) + "..." + transfer.fromAddress.slice(-3); + const toAddr = + addrs[transfer.toAddress.toLowerCase()] || + transfer.toAddress.slice(0, 5) + "..." + transfer.toAddress.slice(-3); + const burned = toAddr === "Null"; + const minted = fromAddr === "Null"; + const nTokens = + transfer.price && transfer.value + ? transfer.value / +transfer.price < 1 + ? formatNumber(transfer.value / +transfer.price, 6) + : formatNumber(transfer.value / +transfer.price, 2) + : "unknown"; + const nGwei = formatNumber(BigNumber.from(transfer.amount).div(1e9).toNumber()); + const useGwei = nTokens === "unknown"; + + const value = transfer.value ? "(" + formatNumber(transfer.value, 2) + " USD)" : ""; + const tokenName = addrs[transfer.tokenAddress.toLowerCase()] || ""; + return { + addrs: addrs, + fromAddr: fromAddr, + toAddr: toAddr, + burned: burned, + minted: minted, + nTokens: nTokens, + nGwei: nGwei, + useGwei: useGwei, + value: value, + tokenName: tokenName, + }; +}; + +const coreToAddrMap = (core: PickleModelJson.PickleModelJson) => { + let addrs: { [addr: string]: string } = {}; + core.assets.jars.forEach((a) => { + const depositTokenAddr = a.depositToken.addr.toLowerCase(); + const depositTokenName = a.depositToken.name; + const pTokenAddr = a.contract.toLowerCase(); + const pTokenName = "p" + depositTokenName; + addrs[depositTokenAddr] = depositTokenName; + addrs[pTokenAddr] = pTokenName; + }); + core.tokens.forEach((t) => { + const tokenAddr = t.contractAddr.toLowerCase(); + const tokenName = t.name ? t.name : t.id; + addrs[tokenAddr] = tokenName; + }); + return addrs; +}; + +export default TxTableRowBody; diff --git a/v2/features/stats/jar/userHistory/TxTableRowHeader.tsx b/v2/features/stats/jar/userHistory/TxTableRowHeader.tsx new file mode 100644 index 00000000..cfcddc21 --- /dev/null +++ b/v2/features/stats/jar/userHistory/TxTableRowHeader.tsx @@ -0,0 +1,237 @@ +import { ChevronDownIcon } from "@heroicons/react/solid"; +import { PickleModelJson } from "picklefinance-core"; +import { FC, HTMLAttributes } from "react"; +import { classNames, formatDate, formatDollars, weiToVisibleString } from "v2/utils"; +import Link from "v2/components/Link"; +import { + PnlTransactionWrapper, + StakingRewards, + UserTransfer, + UserTx, +} from "picklefinance-core/lib/client/pnl/UserHistoryInterfaces"; +import { t } from "xstate"; +import { BigNumber } from "ethers"; + +const TxTableRowHeader: FC<{ + tx: PnlTransactionWrapper; + userPnl: PnlTransactionWrapper[]; + allPnlTx: PnlTransactionWrapper[]; + txSort: "old" | "new"; + core: PickleModelJson.PickleModelJson; + open: boolean; +}> = ({ tx, userPnl, allPnlTx, txSort, core, open }) => { + const chain = core.chains.find((c) => c.chainId == tx.userTransaction.chain_id); + const txLinkUrl = `${chain?.explorer}/tx/${tx.userTransaction.hash}`; + return ( + <> + +

+ {formatDate(new Date(tx.userTransaction.timestamp * 1000))} +

+
+ +
+
+

+ {txType[tx.userTransaction.transaction_type]} +

+
+
+
+ + + {tx.userTransaction.hash.slice(0, 5) + "..." + tx.userTransaction.hash.slice(-3)} + + + +
+
+

+ {userTxToPtokenCount(tx.userTransaction)} +

+
+
+
+ +
+
+

+ {rollingBalances(userPnl, tx)} +

+
+
+
+ +
+
+

+ {userTxToValue(tx.userTransaction)} +

+
+
+
+ +
+
+

+ {Object.keys(tx.transactionRewards).length > 0 + ? rewardsArrToValue(tx.transactionRewards) + : "--"} +

+
+
+
+ +

+ {txSort === "old" ? txToPnl(tx, allPnlTx.slice(-1)[0]) : txToPnl(tx, allPnlTx[0])} +

+
+ +
+
+
+ + ); +}; + +const RowCell: FC> = ({ children, className }) => ( + + {children} + +); + +const rewardsArrToValue = (rewards: StakingRewards) => { + let totalRewardValue = 0; + Object.keys(rewards).forEach((token) => { + rewards[token].forEach((r) => (totalRewardValue += r.value)); + }); + if (totalRewardValue < 0.01) return "< $0.00"; + return formatDollars(totalRewardValue, 2); +}; + +const txToPnl = (tx: PnlTransactionWrapper, lastTx: PnlTransactionWrapper) => { + if (!["ZAPIN", "DEPOSIT"].includes(tx.userTransaction.transaction_type)) return "N/A"; + const index = tx.pnlRollingDataWithLots.lots.length - 1; + const thisTxInFuture = lastTx.pnlRollingDataWithLots.lots[index]; + const fractionOpen = thisTxInFuture.weiRemaining.mul(1000).div(thisTxInFuture.wei).toNumber() / 1000; + const fractionClosed = 1 - fractionOpen; + if( fractionClosed === 0 ) { + return "OPEN"; + } + const pnl = thisTxInFuture.saleProceedsUSD - (fractionClosed * thisTxInFuture.totalCostUsd); + const ret = formatDollars(pnl, 2); + if( fractionClosed > 0 && fractionClosed < 1) { + return ret + " (PARTIAL)"; + } + return ret; +}; + +const userTxToValue = (tx: UserTx) => { + let userTransfer: UserTransfer | undefined = findMainTransfer(tx); + if( !userTransfer ) { + return "$0.00"; + } + return formatDollars(userTransfer.value, 2); +}; + +const findAllMainTransfers = (tx: UserTx): UserTransfer | undefined => { + let test: UserTransfer | undefined = undefined; + if (tx.transaction_type === "DEPOSIT" || tx.transaction_type === "ZAPIN") + tx.transfers.forEach((t) => { + if (t.transfer_type === "DEPOSIT") test = t; + }); + if (tx.transaction_type === "WITHDRAW" || tx.transaction_type === "ZAPOUT") + tx.transfers.forEach((t) => { + if (t.transfer_type === "WITHDRAW") test = t; + }); + if (tx.transaction_type === "STAKE") + tx.transfers.forEach((t) => { + if (t.transfer_type === "STAKE") test = t; + }); + if (tx.transaction_type === "UNSTAKE") + tx.transfers.forEach((t) => { + if (t.transfer_type === "UNSTAKE") test = t; + }); + return test; +} + +const findMainTransfer = (tx: UserTx): UserTransfer | undefined => { + let test: UserTransfer | undefined = undefined; + if (tx.transaction_type === "DEPOSIT" || tx.transaction_type === "ZAPIN") + tx.transfers.forEach((t) => { + if (t.transfer_type === "DEPOSIT") test = t; + }); + if (tx.transaction_type === "WITHDRAW" || tx.transaction_type === "ZAPOUT") + tx.transfers.forEach((t) => { + if (t.transfer_type === "WITHDRAW") test = t; + }); + if (tx.transaction_type === "STAKE") + tx.transfers.forEach((t) => { + if (t.transfer_type === "STAKE") test = t; + }); + if (tx.transaction_type === "UNSTAKE") + tx.transfers.forEach((t) => { + if (t.transfer_type === "UNSTAKE") test = t; + }); + return test; +} +const userTxToPtokenCount = (tx: UserTx) => { + let userTransfer: UserTransfer | undefined = findMainTransfer(tx); + if( !userTransfer ) { + return "0"; + } + const value = userTransfer ? userTransfer.amount : "0"; + return weiToVisibleString(value, userTransfer.decimals); +} + + +const txType: { [key: string]: string } = { + DEPOSIT: "Deposit", + STAKE: "Stake", + UNSTAKE: "Unstake", + WITHDRAW: "Withdraw", + ZAPIN: "Zap In", + STAKE_REWARD: "Stake Reward", +}; + +const findDepositTokenDecimalCount = (userPnl: PnlTransactionWrapper[]) => { + for( let i = 0; i < userPnl.length; i++ ) { + if (userPnl[i].userTransaction.transaction_type === "DEPOSIT" || userPnl[i].userTransaction.transaction_type === "ZAPIN") { + const transfers = userPnl[i].userTransaction.transfers; + for( let j = 0; j < transfers.length; j++ ) { + if (transfers[j].transfer_type === "DEPOSIT") + return transfers[j].decimals; + } + } + }; + return 18; +} +const rollingBalances = (userPnl: PnlTransactionWrapper[], tx: PnlTransactionWrapper) => { + const weiBalance = tx.pnlRollingDataWithLots.rollingWeiCount.toString(); + let dec: number = findDepositTokenDecimalCount(userPnl); + + let locked: BigNumber = BigNumber.from(0); + let remaining: BigNumber = BigNumber.from(0); + for( let i = 0; i < tx.pnlRollingDataWithLots.lots.length; i++ ) { + locked = locked.add(tx.pnlRollingDataWithLots.lots[i].weiLocked); + remaining = remaining.add(tx.pnlRollingDataWithLots.lots[i].weiRemaining); + } + const unlocked = remaining.sub(locked); + const unlockedStr = weiToVisibleString(unlocked.toString(), dec); + const lockedStr = weiToVisibleString(locked.toString(), dec); + return unlockedStr + " / " + lockedStr; +} + +export default TxTableRowHeader; diff --git a/v2/features/stats/jar/userHistory/TxTableSpacerRow.tsx b/v2/features/stats/jar/userHistory/TxTableSpacerRow.tsx new file mode 100644 index 00000000..af94b067 --- /dev/null +++ b/v2/features/stats/jar/userHistory/TxTableSpacerRow.tsx @@ -0,0 +1,11 @@ +import { FC } from "react"; + +const TxTableSpacerRow: FC = () => { + return ( + + + + ); +}; + +export default TxTableSpacerRow; diff --git a/v2/features/stats/jar/userHistory/utils/PnL.ts b/v2/features/stats/jar/userHistory/utils/PnL.ts new file mode 100644 index 00000000..4850bb55 --- /dev/null +++ b/v2/features/stats/jar/userHistory/utils/PnL.ts @@ -0,0 +1,311 @@ +import { BigNumber } from "ethers"; +import { UserTx } from "picklefinance-core/lib/client/pnl/UserHistoryInterfaces"; + +export const generatePnL = (userJarHistory: UserTx[]) => { + const pnl: (PnlTxn | undefined)[] = userJarHistory.map((txn) => { + if (txn.transaction_type === "ZAPIN") { + return handleZap(txn); + } + if (txn.transaction_type === "DEPOSIT") { + return handleDeposit(txn); + } + if (txn.transaction_type === "WITHDRAW") { + return handleWithdraw(txn); + } + // TODO add stake and unstake here + }); + const pnlWithTotals: PnlTxnWithTotals[] = []; + pnl.forEach((txn, i) => { + if (i === 0 && txn) { + pnlWithTotals.push(firstPnlItem(txn)); + return; + } else { + if (!txn) { + if (pnlWithTotals.length > 0) { + pnlWithTotals.push({ + ...pnlWithTotals[i - 1], + action: "other", + nTokens: BigNumber.from(0), + pl: undefined, + }); + } + return; + } + let entry: PnlTxnWithTotals; + if (txn.action === "deposit") { + entry = getDepositPnlEntry(txn, pnlWithTotals, i); + pnlWithTotals.push(entry); + return; + } + if (txn.action === "withdraw") { + entry = getWithdrawPnlEntry(txn, pnlWithTotals, i); + pnlWithTotals.push(entry); + } + // TODO add stake and unstake locking of tokens + if (txn.action === "stake") { + // lock tokens from earliest lots + } + if (txn.action === "unstake") { + // unlock tokens from earliest lots + } + } + }); + return pnlWithTotals; +}; + +const handleZap = (txn: UserTx): PnlTxn => { + let value = 0; + let nTokens = BigNumber.from(0); + let tokenDecimals = 18; + let costBasis = 0; + txn.transfers.forEach((transfer) => { + if (transfer.transfer_type === "FROM_CALLER") value = +transfer.value.toFixed(2); + if (transfer.transfer_type === "PTRANSFER") { + nTokens = BigNumber.from(transfer.amount); + if (transfer.decimals) tokenDecimals = transfer.decimals; + costBasis = +transfer.price; + } + }); + DEBUG_OUT("handleZap VALUE: ", value); + DEBUG_OUT("handleZap N TOKENS: ", nTokens.toString()); + const ret: PnlTxn = { + action: "deposit", + value: value, + nTokens: nTokens, + tokenDecimals: tokenDecimals, + costBasis: costBasis, + hash: txn.hash, + timestamp: txn.timestamp, + }; + return ret; +}; + +const handleDeposit = (txn: UserTx) => { + let value = 0; + let nTokens = BigNumber.from(0); + let tokenDecimals = 18; + let costBasis = 0; + txn.transfers.forEach((transfer) => { + if (transfer.transfer_type === "DEPOSIT") nTokens = BigNumber.from(transfer.amount); + if (transfer.transfer_type === "WANT_DEPOSIT") { + value = +transfer.value.toFixed(2); + if (transfer.decimals) tokenDecimals = transfer.decimals; + costBasis = +transfer.price; + } + }); + const ret: PnlTxn = { + action: "deposit", + value: value, + nTokens: nTokens, + tokenDecimals: tokenDecimals, + costBasis: costBasis, + hash: txn.hash, + timestamp: txn.timestamp, + }; + + return ret; +}; + +const handleWithdraw = (txn: UserTx) => { + let value = 0; + let nTokens = BigNumber.from(0); + let tokenDecimals = 18; + let costBasis = 0; + txn.transfers.forEach((transfer) => { + if (transfer.transfer_type === "WITHDRAW") nTokens = BigNumber.from(transfer.amount); + if (transfer.transfer_type === "WANT_WITHDRAW") { + value = +transfer.value.toFixed(2); + if (transfer.decimals) tokenDecimals = transfer.decimals; + costBasis = +transfer.price; + } + }); + const ret: PnlTxn = { + action: "withdraw", + value: value, + nTokens: nTokens, + tokenDecimals: tokenDecimals, + costBasis: costBasis, + hash: txn.hash, + timestamp: txn.timestamp, + }; + return ret; +}; + +const getDepositPnlEntry = ( + txn: PnlTxn, + pnlWithTotals: PnlTxnWithTotals[], + i: number, +): PnlTxnWithTotals => { + const totalNTokens = pnlWithTotals[i - 1].totalNTokens.add(txn.nTokens); + const totalCost = pnlWithTotals[i - 1].totalCost + txn.value; + + const totalCostBasis = + BigNumber.from(Math.floor(totalCost * 1e9)) + .div(totalNTokens) + .toNumber() / 1e9; + + let lots = [...pnlWithTotals[i - 1].lots]; + lots.push({ + nTokens: txn?.nTokens, + nTokensRemaining: txn?.nTokens, + nTokensLocked: BigNumber.from(0), + tokenDecimals: txn.tokenDecimals, + costBasis: txn.costBasis, + cost: txn.value, + status: "open", + timestamp: txn.timestamp, + }); + return { + ...txn, + totalNTokens: totalNTokens, + totalCostBasis: totalCostBasis, + totalCost: totalCost, + lots: lots, + }; +}; + +const getWithdrawPnlEntry = ( + txn: PnlTxn, + pnlWithTotals: PnlTxnWithTotals[], + i: number, +): PnlTxnWithTotals => { + let totalNTokens = pnlWithTotals[i - 1].totalNTokens.sub(txn.nTokens); + let totalCostBasis = pnlWithTotals[i - 1].totalNTokens.sub(txn.nTokens).eq(0) + ? BigNumber.from(0) + : pnlWithTotals[i - 1].totalNTokens + .mul(pnlWithTotals[i - 1].totalCostBasis) + .sub(txn.nTokens.mul(txn.costBasis)) + .div(pnlWithTotals[i - 1].totalNTokens.sub(txn.nTokens)); + let lots = [...pnlWithTotals[i - 1].lots]; + let nt = txn.nTokens; + let cost = 0; + lots = lots.map((l) => { + if (nt.eq(0) || l.status === "closed") return l; + if (l.nTokensRemaining.sub(nt).gt(0)) { + let ret = { ...l, nTokensRemaining: l.nTokensRemaining.sub(nt) } as Lot; + cost += l.cost; + nt = BigNumber.from(0); + return ret; + } else { + let ret = { ...l, nTokensRemaining: BigNumber.from(0), status: "closed" } as Lot; + nt = nt.sub(l.nTokensRemaining); + cost += l.cost; + return ret; + } + }); + const totalCost = totalNTokens.mul(totalCostBasis).toNumber(); + return { + ...txn, + totalNTokens: totalNTokens, + totalCostBasis: totalCostBasis.toNumber(), + totalCost: totalCost, + pl: txn.value - cost, + lots: lots, + }; +}; + +// TODO handleStake and handleUnstake +// const handleStake = (txn: UserTx) => { +// return; +// }; +// const handleUnstake = (txn: UserTx) => { +// return; +// }; + +const firstPnlItem = (txn: PnlTxn): PnlTxnWithTotals => { + return { + ...txn, + totalNTokens: txn?.nTokens, + totalCostBasis: txn?.costBasis, + totalCost: txn.value, + lots: [ + { + nTokens: txn?.nTokens, + nTokensRemaining: txn?.nTokens, + nTokensLocked: BigNumber.from(0), + tokenDecimals: txn.tokenDecimals, + costBasis: txn.costBasis, + cost: txn.value, + status: "open", + timestamp: txn.timestamp, + }, + ], + }; +}; + +const getCost = (nTokens: BigNumber, costBasis: number, tokenDecimals: number) => { + let cost = 0; + if (costBasis === 0) return 0; + cost = + nTokens + .mul(costBasis) + .div(1e6) + .div(Math.pow(10, tokenDecimals - 9)) + .toNumber() / 1e3; + return cost; +}; +export interface PnlTxn { + action: "deposit" | "withdraw" | "stake" | "unstake" | "other"; + value: number; + nTokens: BigNumber; + tokenDecimals: number; + costBasis: number; + hash: string; + timestamp: number; +} +export interface PnlTxnWithTotals extends PnlTxn { + totalNTokens: BigNumber; + totalCostBasis: number; + totalCost: number; + pl?: number; + // totalTradingPnL?: number; + // totalRewardsUSD?: number; + lots: Lot[]; +} +interface Lot { + nTokens: BigNumber; + nTokensRemaining: BigNumber; + nTokensLocked: BigNumber; + tokenDecimals: number; + costBasis: number; + cost: number; + status: "open" | "locked" | "closed"; + timestamp: number; +} + +const xTimesTenToTheN = (x: BigNumber | number, n: number) => { + if (typeof x === "number") x = BigNumber.from(Math.floor(x * 1e3)); + while (n > 0) { + if (n < 9) { + x = x.mul(Math.pow(10, n)); + n = 0; + } else { + x = x.mul(Math.pow(10, 9)); + n -= 9; + } + } + return x.div(1e3); +}; +const xDivTenToTheN = (x: BigNumber | number, n: number) => { + if (typeof x === "number") x = BigNumber.from(Math.floor(x * 1e3)); + while (n > 0) { + if (x.lte(1e9)) + return { + x: x, + underflowDigits: n, + }; + if (n < 9) { + x = x.div(Math.pow(10, n)); + n = 0; + } else { + x = x.div(Math.pow(10, 9)); + n -= 9; + } + } + return { x: x.div(1e3) }; +}; + +const DEBUG_OUT = (label: string, msg: any) => { + if (GLOBAL_DEBUG) console.log(label + "\n", msg); +}; +const GLOBAL_DEBUG = true; diff --git a/v2/store/controls.ts b/v2/store/controls.ts index fe6e4554..9db8571f 100644 --- a/v2/store/controls.ts +++ b/v2/store/controls.ts @@ -7,6 +7,7 @@ export enum FilterType { Token = "token", Protocol = "protocol", Network = "network", + Tag = "tag", } export type Sort = { diff --git a/v2/store/core.ts b/v2/store/core.ts index 4e74326a..6d705d84 100644 --- a/v2/store/core.ts +++ b/v2/store/core.ts @@ -25,6 +25,7 @@ import { } from "v2/utils/user"; import { getUniV3Tokens } from "v2/utils/univ3"; import { allAssets, enabledPredicate } from "./core.helpers"; +import { toTitleCase } from "v2/utils/format"; const apiHost = process.env.apiHost; @@ -142,7 +143,24 @@ const filtersFromCoreData = (data: PickleModelJson): Filter[] => { color: brandColor(token), })); - return [...networkFilters, ...protocolFilters, ...tokenFilters]; + const tagFilters = [ + ...new Set( + data.assets.jars + .filter((jar) => jar.tags) + .flatMap((jars) => { + const { tags } = jars; + return tags?.map((tag) => tag); + }), + ), + ].map((tag) => ({ + type: FilterType.Tag, + value: tag || "", + label: toTitleCase(tag || ""), + imageSrc: tag === "stablecoins" ? `/tokens/usdc.png` : `/gray.png`, + color: brandColor("usdc"), + })); + + return [...networkFilters, ...protocolFilters, ...tokenFilters, ...tagFilters]; }; /** @@ -179,6 +197,11 @@ const selectFilteredAssets = createSelector( return jar.chain === filter.value; case FilterType.Protocol: return jar.protocol === filter.value; + case FilterType.Tag: + const { tags } = jar; + if (!tags) return false; + return tags.includes(filter.value); + case FilterType.Token: const { components } = jar.depositToken; diff --git a/v2/utils/format.ts b/v2/utils/format.ts index 6dba7a8e..a6a121e8 100644 --- a/v2/utils/format.ts +++ b/v2/utils/format.ts @@ -32,6 +32,13 @@ export const formatDate = (value: Date): string => { return formatter.format(value); }; +export const formatDollarsAddDecimalsForSmallNumbers = (value: number, precision = 0): string => { + if( value === 0 ) + return formatDollars(value, 0); + if( value < 10 ) + return formatDollars(value, 2); + return formatDollars(value, 0); +} export const formatDollars = (value: number, precision = 0): string => { const formatter = new Intl.NumberFormat("en-US", { style: "currency", @@ -107,3 +114,25 @@ export const toTitleCase = (str: string | String) => { }) .join(" "); }; + +export const weiToVisibleString = (wei: string, decimals: number): string => { + if( wei === "0") { + return "0"; + } + const digits = wei.length; + let order = digits - decimals; + if( order > 6 ) { + const digitOne = wei.substring(0,1); + const digitRest = wei.substring(1, 4); + return digitOne + "." + digitRest + "E" + order; + } else if( order < -3) { + const digitOne = wei.substring(0,1); + const digitRest = wei.substring(1, 4); + return digitOne + "." + digitRest + "E" + order; + } else { + const wholes = wei.substring(0,order); + const decimals = wei.substring(order); + return wholes + "." + decimals.substring(0,3); + } + return wei; +}; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 5c35308a..e6f31701 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1647,6 +1647,13 @@ dependencies: "@types/node" "*" +"@types/sharp@^0.31.0": + version "0.31.1" + resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.31.1.tgz#db768461455dbcf9ff11d69277fd70564483c4df" + integrity sha512-5nWwamN9ZFHXaYEincMSuza8nNfOof8nmO+mcI+Agx1uMUk4/pQnNIcix+9rLPXzKrm1pS34+6WRDbDV0Jn7ag== + dependencies: + "@types/node" "*" + "@types/styled-components@^5.1.3": version "5.1.26" resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.26.tgz#5627e6812ee96d755028a98dae61d28e57c233af" @@ -1663,6 +1670,11 @@ dependencies: tailwindcss "*" +"@types/uuid@8.3.4": + version "8.3.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== + "@types/ws@^7.4.4": version "7.4.7" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" @@ -2651,7 +2663,7 @@ bindings@^1.3.0: dependencies: file-uri-to-path "1.0.0" -bl@^4.1.0: +bl@^4.0.3, bl@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== @@ -3036,6 +3048,11 @@ chokidar@3.5.1: optionalDependencies: fsevents "~2.3.2" +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + chroma-js@^2.1.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chroma-js/-/chroma-js-2.4.2.tgz#dffc214ed0c11fa8eefca2c36651d8e57cbfb2b0" @@ -3160,7 +3177,7 @@ color-name@^1.0.0, color-name@^1.1.4, color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@^1.6.0: +color-string@^1.6.0, color-string@^1.9.0: version "1.9.1" resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== @@ -3176,6 +3193,14 @@ color@^3.1.2, color@^3.2.1: color-convert "^1.9.3" color-string "^1.6.0" +color@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" + integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== + dependencies: + color-convert "^2.0.1" + color-string "^1.9.0" + colorette@^1.2.2: version "1.4.0" resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40" @@ -3815,7 +3840,14 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og== -deep-extend@~0.6.0: +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + +deep-extend@^0.6.0, deep-extend@~0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== @@ -3883,6 +3915,11 @@ detect-indent@^6.0.0: resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6" integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== +detect-libc@^2.0.0, detect-libc@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" + integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== + detect-node@^2.0.4, detect-node@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" @@ -4067,6 +4104,13 @@ encoding@0.1.13: dependencies: iconv-lite "^0.6.2" +end-of-stream@^1.1.0, end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + enquirer@^2.3.5: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" @@ -4673,6 +4717,11 @@ execa@^6.1.0: signal-exit "^3.0.7" strip-final-newline "^3.0.0" +expand-template@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== + extract-files@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-9.0.0.tgz#8a7744f2437f81f5ed3250ed9f1550de902fe54a" @@ -4830,6 +4879,11 @@ fraction.js@^4.2.0: resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA== +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + fs-extra@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" @@ -4927,6 +4981,11 @@ get-symbol-description@^1.0.0: call-bind "^1.0.2" get-intrinsic "^1.1.1" +github-from-package@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" + integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== + glob-parent@^5.1.2, glob-parent@~5.1.0, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -5334,6 +5393,11 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== +ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + interface-ipld-format@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/interface-ipld-format/-/interface-ipld-format-1.0.1.tgz#bee39c70c584a033e186ff057a2be89f215963e3" @@ -6250,6 +6314,11 @@ mimic-fn@^4.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + min-indent@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" @@ -6286,6 +6355,16 @@ minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== +minimist@^1.2.3: + version "1.2.7" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" + integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== + +mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + mkdirp@^0.5.5: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" @@ -6386,6 +6465,11 @@ nanoid@^3.1.23, nanoid@^3.3.4: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== +napi-build-utils@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" + integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== + native-url@0.3.4: version "0.3.4" resolved "https://registry.yarnpkg.com/native-url/-/native-url-0.3.4.tgz#29c943172aed86c63cee62c8c04db7f5756661f8" @@ -6475,11 +6559,23 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" +node-abi@^3.3.0: + version "3.31.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.31.0.tgz#dfb2ea3d01188eb80859f69bb4a4354090c1b355" + integrity sha512-eSKV6s+APenqVh8ubJyiu/YhZgxQpGP66ntzUb3lY1xB9ukSRaGnx0AIxI+IM+1+IVYC1oWobgG5L3Lt9ARykQ== + dependencies: + semver "^7.3.5" + node-addon-api@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== +node-addon-api@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762" + integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== + node-fetch@2, node-fetch@2.6.7, node-fetch@^2.6.1, node-fetch@^2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" @@ -6681,7 +6777,7 @@ oblivious-set@1.0.0: resolved "https://registry.yarnpkg.com/oblivious-set/-/oblivious-set-1.0.0.tgz#c8316f2c2fb6ff7b11b6158db3234c49f733c566" integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw== -once@^1.3.0: +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== @@ -6888,10 +6984,10 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== -picklefinance-core@^0.5.13: - version "0.5.22" - resolved "https://registry.yarnpkg.com/picklefinance-core/-/picklefinance-core-0.5.22.tgz#ab0f303405f1a94fad403a8cfa9077b3f4cea663" - integrity sha512-HGWnr/Yhmhxqf9a4jtP4DcfPbqeJCJct+HmTXDA6zk/t6qsPmUr+nw39Ycf0+Wi/MDwr9tjQGLq47owYo/6ujA== +picklefinance-core@^0.5.46: + version "0.5.46" + resolved "https://registry.yarnpkg.com/picklefinance-core/-/picklefinance-core-0.5.46.tgz#09245de8cdd3828c394dce2ff8ace2f97de23b43" + integrity sha512-V9S8oHIVyqxOIMRfShUlzVfKcLLUP4V4PDxTK1EVVboFf0Wb419bMO3rHqXpFOZAQHYe26LHxmzs238lBii9WA== dependencies: "@thanpolas/univ3prices" "^3.0.2" "@types/coingecko-api" "^1.0.0" @@ -7045,6 +7141,24 @@ preact@^10.5.9: resolved "https://registry.yarnpkg.com/preact/-/preact-10.11.0.tgz#26af45a0613f4e17a197cc39d7a1ea23e09b2532" integrity sha512-Fk6+vB2kb6mSJfDgODq0YDhMfl0HNtK5+Uc9QqECO4nlyPAQwCI+BKyWO//idA7ikV7o+0Fm6LQmNuQi1wXI1w== +prebuild-install@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" + integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== + dependencies: + detect-libc "^2.0.0" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" + napi-build-utils "^1.0.1" + node-abi "^3.3.0" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^4.0.0" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -7120,6 +7234,14 @@ public-encrypt@^4.0.0: randombytes "^2.0.1" safe-buffer "^5.1.2" +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + punycode@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" @@ -7264,6 +7386,16 @@ rc-util@^5.18.1: react-is "^16.12.0" shallowequal "^1.1.0" +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + react-apexcharts@^1.3.7: version "1.4.0" resolved "https://registry.yarnpkg.com/react-apexcharts/-/react-apexcharts-1.4.0.tgz#e3619104b34750da67a2ca80289dc87085c2aa27" @@ -7817,6 +7949,13 @@ semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: dependencies: lru-cache "^6.0.0" +semver@^7.3.8: + version "7.3.8" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" + integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== + dependencies: + lru-cache "^6.0.0" + set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -7845,6 +7984,20 @@ shallowequal@^1.1.0: resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== +sharp@^0.31.1: + version "0.31.3" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.31.3.tgz#60227edc5c2be90e7378a210466c99aefcf32688" + integrity sha512-XcR4+FCLBFKw1bdB+GEhnUNXNXvnt0tDo4WsBsraKymuo/IAuPuCBVAL2wIkUw2r/dwFW5Q5+g66Kwl2dgDFVg== + dependencies: + color "^4.2.3" + detect-libc "^2.0.1" + node-addon-api "^5.0.0" + prebuild-install "^7.1.1" + semver "^7.3.8" + simple-get "^4.0.1" + tar-fs "^2.1.1" + tunnel-agent "^0.6.0" + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -7876,6 +8029,20 @@ signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^4.0.0, simple-get@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" + integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== + dependencies: + decompress-response "^6.0.0" + once "^1.3.1" + simple-concat "^1.0.0" + simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -8223,6 +8390,11 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + styled-components@^5.2.0: version "5.3.6" resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.3.6.tgz#27753c8c27c650bee9358e343fc927966bfd00d1" @@ -8358,6 +8530,27 @@ tailwindcss@*, tailwindcss@^3.0.15: quick-lru "^5.1.1" resolve "^1.22.1" +tar-fs@^2.0.0, tar-fs@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + +tar-stream@^2.1.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + text-encoding-utf-8@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz#585b62197b0ae437e3c7b5d0af27ac1021e10d13" @@ -8520,6 +8713,13 @@ tty-browserify@0.0.1: resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.1.tgz#3f05251ee17904dfd0677546670db9651682b811" integrity sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw== +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" + tween-functions@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/tween-functions/-/tween-functions-1.2.0.tgz#1ae3a50e7c60bb3def774eac707acbca73bbc3ff" @@ -8773,11 +8973,19 @@ util@^0.12.0, util@^0.12.4: safe-buffer "^5.1.2" which-typed-array "^1.1.2" -uuid@^8.3.2: +uuid@8.3.2, uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +uuidv4@^6.2.13: + version "6.2.13" + resolved "https://registry.yarnpkg.com/uuidv4/-/uuidv4-6.2.13.tgz#8f95ec5ef22d1f92c8e5d4c70b735d1c89572cb7" + integrity sha512-AXyzMjazYB3ovL3q051VLH06Ixj//Knx7QnUSi1T//Ie3io6CpsPu9nVMOx5MoLWh6xV0B9J0hIaxungxXUbPQ== + dependencies: + "@types/uuid" "8.3.4" + uuid "8.3.2" + v8-compile-cache@^2.0.3: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"