Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions components/v1/MarketMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { chain as Chains } from "wagmi";

// Markets Metadata
export type Metadata = {
export type MetadataToken = {
logo: string;
vaultLogo: string;
vaultAddress: string;
Expand All @@ -22,8 +22,18 @@ export type Metadata = {
uniswapSwapURL: string;
isETH: boolean;
};
export type MarketMetadata = Record<string, Metadata>;
export type MarketMetadata = Record<string, MetadataToken>;
export type MarketMetadataRecord = Record<number, MarketMetadata>;
type MapperNameToChainId = Record<string, number>;
type MapperTokenToTokenId = Record<string, string>;

export const MapperNameToChainId: MapperNameToChainId = {
arbitrum: Chains.arbitrumOne.id,
};
export const MapperTokenToTokenId: MapperTokenToTokenId = {
ethrise: "0x46D06cf8052eA6FdbF71736AF33eD23686eA1452",
};

export const Metadata: MarketMetadataRecord = {
/* Kovan chain is no longer supported */
// [Chains.kovan.id]: {
Expand Down Expand Up @@ -51,6 +61,7 @@ export const Metadata: MarketMetadataRecord = {
// isETH: true,
// },
// },

[Chains.arbitrumOne.id]: {
["0x46D06cf8052eA6FdbF71736AF33eD23686eA1452"]: {
title: "ETHRISE",
Expand Down
6 changes: 3 additions & 3 deletions components/v1/WarningHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ const WarningHeader: FunctionComponent<WarningHeaderProps> = ({}) => {
<>
<div className={`relative ${!router.pathname.includes("binance") || !showBSCWarning ? "hidden" : "block"} z-10`}>
<div className="flex w-max min-w-full flex-row gap-8 border-b border-gray-light-4 bg-[#FFF9ED]/40 px-4 py-3 backdrop-blur-[102px] dark:border-gray-dark-4 dark:bg-gray-dark-1/40">
{[...Array(10)].map(() => {
{[...Array(10)].map((val, index) => {
return (
<>
<div key={`${val} ${index}`}>
<div className="flex flex-row items-center gap-1.5 border-r border-amber-light-11/10 pr-8 dark:border-amber-dark-11/10">
<p className="text-xs tracking-[-0.03em] text-amber-light-11 dark:text-amber-dark-11">Use at your own risk</p>
{getBscLogo("normal")}
Expand All @@ -46,7 +46,7 @@ const WarningHeader: FunctionComponent<WarningHeaderProps> = ({}) => {
<p className="text-xs tracking-[-0.03em] text-gray-light-11 dark:text-gray-dark-11">Binance Smart Chain</p>
{getBscLogo("gray")}
</div>
</>
</div>
);
})}
</div>
Expand Down
10 changes: 6 additions & 4 deletions components/v1/swr/useLeveragedTokenNAV.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ethers } from "ethers";
import useSWR from "swr";
import useSWR, { SWRConfiguration } from "swr";
import { VaultABI } from "../../../abis/VaultABI";
import { SWRCacheNamespace } from "./namespace";

Expand All @@ -21,14 +21,16 @@ const LeveragedTokenNAVFetcher = async (args: LeveragedTokenNAVFetcherArgs): Pro
};

// Get the latest NAV of the leveraged token
export function useLeveragedTokenNAV(req: LeveragedTokenNAVRequest) {
const { data, error } = useSWR<ethers.BigNumber, Error>(
export function useLeveragedTokenNAV(req: LeveragedTokenNAVRequest, options?: SWRConfiguration) {
const { data, error, mutate } = useSWR<ethers.BigNumber, Error>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we need mutate ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because i want to re-fetch data when error appears

{ token: req.token, vault: req.vault, provider: req.provider, namespace: SWRCacheNamespace.VaultGetLeveragedTokenNAV },
LeveragedTokenNAVFetcher
LeveragedTokenNAVFetcher,
options
);
return {
data: data,
isLoading: !data && !error,
error: error,
mutate,
};
}
10 changes: 6 additions & 4 deletions components/v1/swr/useTokenBalance.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ethers } from "ethers";
import useSWR from "swr";
import useSWR, { SWRConfiguration } from "swr";
import { erc20ABI } from "wagmi";
import { SWRCacheNamespace } from "./namespace";

Expand All @@ -25,14 +25,16 @@ const TokenBalanceFetcher = async (args: TokenBalanceFetcherArgs): Promise<ether
};

// Fetch current balance of given acccount for specified token
export function useTokenBalance(req: TokenBalanceRequest) {
const { data, error } = useSWR<ethers.BigNumber, Error>(
export function useTokenBalance(req: TokenBalanceRequest, options?: SWRConfiguration) {
const { data, error, mutate } = useSWR<ethers.BigNumber, Error>(
{ account: req.account, token: req.token, provider: req.provider, namespace: SWRCacheNamespace.GetBalance },
TokenBalanceFetcher
TokenBalanceFetcher,
options
);
return {
data: data,
isLoading: !data && !error,
error: error,
mutate,
};
}
10 changes: 6 additions & 4 deletions components/v1/swr/useVaultExchangeRate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ethers } from "ethers";
import useSWR from "swr";
import useSWR, { SWRConfiguration } from "swr";
import { VaultABI } from "../../../abis/VaultABI";
import { SWRCacheNamespace } from "./namespace";

Expand All @@ -20,14 +20,16 @@ const VaultExchangeRateFetcher = async (args: VaultExchangeRateFetcherArgs): Pro
};

// Get the latest rv/deposit token exchange rate
export function useVaultExchangeRate(req: VaultExchangeRateRequest) {
const { data, error } = useSWR<ethers.BigNumber, Error>(
export function useVaultExchangeRate(req: VaultExchangeRateRequest, options?: SWRConfiguration) {
const { data, error, mutate } = useSWR<ethers.BigNumber, Error>(
{ vault: req.vault, provider: req.provider, namespace: SWRCacheNamespace.VaultGetExchangeRate },
VaultExchangeRateFetcher
VaultExchangeRateFetcher,
options
);
return {
data: data,
isLoading: !data && !error,
error: error,
mutate,
};
}
65 changes: 65 additions & 0 deletions modules/tokenPage/TokenContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import ButtonConnectWalletMobile from "../../components/v1/Buttons/ConnectWalletMobile";
import Footer from "../../uikit/layout/Footer";
import BackgroundGradient from "../ethrisePage/component/BackgroundGradient";
import LeveragedTokenBackingCard from "../ethrisePage/component/LeveragedTokenBackingCard";
import LeveragedTokenInfoCard from "../ethrisePage/component/LeveragedTokenInfoCard";
import TabsContentGrid from "../ethrisePage/component/TabsContentGrid";
import VaultInfoCard from "../ethrisePage/component/VaultInfoCard";
import { Root as TabsRoot } from "@radix-ui/react-tabs";
import PriceInfoCard from "./component/PriceInfoCard";
import { useTokenStore } from "./store/tokenStore";
import TabsList from "../ethrisePage/component/TabsList";
import MyAssetsCardContainer from "./component/MyAssetCard";

const MainContent = () => {
const { state } = useTokenStore();
if (state.status === "loaded") {
const { tokenId: tokenAddress } = state;
return (
<TabsRoot defaultValue="leverage" className="px-4 outline-0 sm:mx-auto">
<TabsList />
{/* Leverage Tab */}
<TabsContentGrid value="leverage">
{/* Left Column */}
<PriceInfoCard />
{/* Right Column */}
<div className="flex max-w-[540px] flex-col space-y-6">
<MyAssetsCardContainer />
<LeveragedTokenInfoCard address={tokenAddress} />
<LeveragedTokenBackingCard address={tokenAddress} />
</div>
</TabsContentGrid>
{/* Lend Tab */}
<TabsContentGrid value="lend">
{/* Left Column */}
<PriceInfoCard isVault />

{/* RightColumn */}
<div className="max-w-[540px] flex-col space-y-6">
<MyAssetsCardContainer isVault={true} />
<VaultInfoCard address={tokenAddress} />
</div>
</TabsContentGrid>
</TabsRoot>
);
} else return <div>loading...</div>; // TODO: loading view ??
};

function TokenContainer() {
return (
<>
<div className="mb-20 mt-[76px] flex flex-grow flex-col sm:z-10 sm:mb-0 sm:mt-[120px]">
<MainContent />
</div>
<div className="hidden sm:mt-20 sm:inline-block">
<Footer />
</div>
<BackgroundGradient />
<div className="sm:hidden">
<ButtonConnectWalletMobile />
</div>
</>
);
}

export { TokenContainer };
68 changes: 68 additions & 0 deletions modules/tokenPage/component/AssetItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import Button from "../../../uikit/buttonV2/Button";
import { tokenBalanceFormatter } from "../../../utils/formatters";
import { useAssetStore } from "../../tokenPage/store/assetStore";

type AssetsItemProps = {
variant: AssetItemsVariant;
};

const mapper = {
token: {
title: "Token Balance",
image: "/markets/tokenBalanceIcon.svg",
},
value: {
title: "Value (USDC)",
image: "/markets/valueIcon.svg",
},
return: {
title: "Return",
image: "/markets/returnIcon.svg",
},
returnDollar: {
title: "Return (USDC)",
image: "/markets/returnDollarIcon.svg",
},
};

type AssetItemsVariant = keyof typeof mapper;

const AssetsItem = ({ variant }: AssetsItemProps) => {
const { state } = useAssetStore();

const RenderData = () => {
switch (state.status) {
case "loading":
return <p className="h-[16px] w-[100px] animate-pulse rounded-[8px] bg-gray-light-3 dark:bg-gray-dark-3"></p>;
case "loaded":
let renderedValue;
switch (variant) {
case "value":
renderedValue = tokenBalanceFormatter.format(state.nav * state.balance);
break;
case "token":
renderedValue = tokenBalanceFormatter.format(state.balance);
break;
default:
renderedValue = "-";
}
return <p className="font-ibm text-sm font-semibold leading-4 tracking-[-.02em] text-gray-light-12 dark:text-gray-dark-12">{renderedValue}</p>;
case "error":
return null;
}
};

return (
<div className="flex flex-row">
<div className="mr-3 h-8 w-8 rounded-full bg-gray-light-4 text-center leading-9 dark:bg-gray-800 ">
<img className="mx-auto mt-2" width={16} height={16} src={mapper[variant].image} alt={mapper[variant].image} />
</div>
<div>
<p className="mb-1 text-sm leading-4 text-gray-light-10 dark:text-gray-dark-10">{mapper[variant].title}</p>
<RenderData />
</div>
</div>
);
};

export { AssetsItem };
102 changes: 102 additions & 0 deletions modules/tokenPage/component/MyAssetCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { FunctionComponent, useEffect } from "react";
import { useLeveragedTokenNAV } from "../../../components/v1/swr/useLeveragedTokenNAV";
import { useTokenBalance } from "../../../components/v1/swr/useTokenBalance";
import { useVaultExchangeRate } from "../../../components/v1/swr/useVaultExchangeRate";
import { useWalletContext } from "../../../components/v1/Wallet";
import ButtonTertiary from "../../../uikit/button/ButtonTertiary";
import Button from "../../../uikit/buttonV2/Button";
import InformationCard from "../../../uikit/card/InformationCard";
import { addTokenToMetamask, tokenType } from "../../../utils/addTokenToMetamask";
import { useAssetStore } from "../store/assetStore";
import { LoadedData, useTokenStore } from "../store/tokenStore";
import { AssetsItem } from "./AssetItem";

type MyAssetsCardProps = {
isVault?: boolean;
};

const MyAssetsCard = ({ state, isVault }: { state: LoadedData; isVault: boolean }) => {
const { chainId, token: metadata, tokenId } = state;
const { setValue, setError, state: assetState, setLoading } = useAssetStore();

const { account, provider } = useWalletContext();
const navResponse = useLeveragedTokenNAV({ token: tokenId, vault: metadata.vaultAddress, provider: provider }, { isPaused: () => !tokenId || !metadata || !provider });
const latestVaultExchangeRateResponse = useVaultExchangeRate({ vault: metadata.vaultAddress, provider: provider }, { isPaused: () => !metadata || !provider });
const balanceResponse = useTokenBalance({ account: account, token: isVault ? metadata.vaultAddress : tokenId, provider: provider }, { isPaused: () => !tokenId || !metadata || !account });

const refetchData = () => {
setLoading();
navResponse.mutate();
latestVaultExchangeRateResponse.mutate();
balanceResponse.mutate();
};

useEffect(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we have multiples useEffect() ?

Copy link
Contributor Author

@asaadam asaadam Jul 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because it's specific usage.
this side effect will only run when dependencies changes value
because i think if you combine them , it may create unexpected re-render and side effect

if (!account) {
setError("No account");
return;
}
}, [account]);

useEffect(() => {
if (navResponse.data && latestVaultExchangeRateResponse.data && balanceResponse.data) {
setValue(navResponse.data, latestVaultExchangeRateResponse.data, balanceResponse.data, metadata);
}
}, [navResponse.data, latestVaultExchangeRateResponse.data, balanceResponse.data]);

useEffect(() => {
if (navResponse.error || latestVaultExchangeRateResponse.error) {
setError("Something went wrong");
}
}, [navResponse.error, latestVaultExchangeRateResponse.error]);

if (assetState.status === "error" && assetState.error === "No account") {
return null;
}
if (assetState.status === "loaded" && assetState.balance === 0) {
return null;
}

return (
<InformationCard>
<div className="pt-4">
<h2 className="text-base font-bold leading-4 text-gray-light-12 dark:text-gray-dark-12">My Asset</h2>
</div>
{assetState.status !== "error" ? (
<>
<div className="grid grid-cols-2 gap-4">
<AssetsItem variant="token" />
<AssetsItem variant="value" />
<AssetsItem variant="return" />
<AssetsItem variant="returnDollar" />
</div>
<ButtonTertiary full onClick={async () => await addTokenToMetamask({ token: tokenType.ETHRISE, chainID: chainId, isVaultToken: isVault })}>
Add {isVault ? metadata.vaultTitle : metadata.title} to Wallet
</ButtonTertiary>
</>
) : (
<div className="mx-auto text-center text-red-light-10">
<p>{assetState.error}</p>
<Button className="mx-auto mt-2" onClick={refetchData}>
Retry
</Button>
</div>
)}
</InformationCard>
);
};

const MyAssetsCardContainer: FunctionComponent<MyAssetsCardProps> = ({ isVault = false }) => {
const { state } = useTokenStore();

switch (state.status) {
case "loaded":
return <MyAssetsCard state={state} isVault={isVault} />;
case "loading":
return null;
case "error":
return <Button>Refresh</Button>;
}
};

export default MyAssetsCardContainer;
Loading