From 51faa68d3f7769fdd7448bb36409e9658fea12a9 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Tue, 21 Nov 2023 10:34:50 -0500 Subject: [PATCH 01/93] fix: ignore banks with missing oracle / meta data --- .../src/store/mrgnlendStore.ts | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/packages/marginfi-v2-ui-state/src/store/mrgnlendStore.ts b/packages/marginfi-v2-ui-state/src/store/mrgnlendStore.ts index e1c224d413..fb1628728d 100644 --- a/packages/marginfi-v2-ui-state/src/store/mrgnlendStore.ts +++ b/packages/marginfi-v2-ui-state/src/store/mrgnlendStore.ts @@ -6,7 +6,9 @@ import { loadTokenMetadatas, BankMetadataMap, TokenMetadataMap, + TokenMetadata, } from "@mrgnlabs/mrgn-common"; +import { Bank, OraclePrice } from "@mrgnlabs/marginfi-client-v2"; import { Connection, PublicKey } from "@solana/web3.js"; import { DEFAULT_ACCOUNT_SUMMARY, @@ -151,17 +153,29 @@ const stateCreator: StateCreator = (set, get) => ({ userDataFetched = true; } - const banksWithPriceAndToken = banks.map((bank) => { + const banksWithPriceAndToken: { + bank: Bank; + oraclePrice: OraclePrice; + tokenMetadata: TokenMetadata; + }[] = []; + + banks.forEach((bank) => { const oraclePrice = marginfiClient.getOraclePriceByBank(bank.address); - if (!oraclePrice) throw new Error(`Price info not found for bank ${bank.address.toBase58()}`); + if (!oraclePrice) { + return; + } const bankMetadata = bankMetadataMap[bank.address.toBase58()]; - if (bankMetadata === undefined) throw new Error(`Bank metadata not found for ${bank.address.toBase58()}`); + if (bankMetadata === undefined) { + return; + } const tokenMetadata = getValueInsensitive(tokenMetadataMap, bankMetadata.tokenSymbol); - if (!tokenMetadata) throw new Error(`Token metadata not found for ${bankMetadata.tokenSymbol}`); + if (!tokenMetadata) { + return; + } - return { bank, oraclePrice, tokenMetadata }; + banksWithPriceAndToken.push({ bank, oraclePrice, tokenMetadata }); }); const [extendedBankInfos, extendedBankMetadatas] = banksWithPriceAndToken.reduce( From 7906cfd5cb74293bda37b0a240b352386e509644 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Tue, 21 Nov 2023 11:00:26 -0500 Subject: [PATCH 02/93] chore: add jlp to next config --- apps/marginfi-v2-ui/next.config.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/marginfi-v2-ui/next.config.js b/apps/marginfi-v2-ui/next.config.js index b1a6b2b597..399fc54416 100644 --- a/apps/marginfi-v2-ui/next.config.js +++ b/apps/marginfi-v2-ui/next.config.js @@ -102,6 +102,12 @@ let config = withBundleAnalyzer({ port: "", pathname: "/**", }, + { + protocol: "https", + hostname: "static.jup.ag", + port: "", + pathname: "/jlp/**", + }, ], }, }); From 10684fb821aa2fa78f12d9e388d32589b6fa5b63 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Tue, 21 Nov 2023 11:22:28 -0500 Subject: [PATCH 03/93] chore: try/catch exceptions, skip banks missing data rather than error --- .../src/store/mrgnlendStore.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/marginfi-v2-ui-state/src/store/mrgnlendStore.ts b/packages/marginfi-v2-ui-state/src/store/mrgnlendStore.ts index fb1628728d..0e76a799b2 100644 --- a/packages/marginfi-v2-ui-state/src/store/mrgnlendStore.ts +++ b/packages/marginfi-v2-ui-state/src/store/mrgnlendStore.ts @@ -170,12 +170,16 @@ const stateCreator: StateCreator = (set, get) => ({ return; } - const tokenMetadata = getValueInsensitive(tokenMetadataMap, bankMetadata.tokenSymbol); - if (!tokenMetadata) { - return; - } + try { + const tokenMetadata = getValueInsensitive(tokenMetadataMap, bankMetadata.tokenSymbol); + if (!tokenMetadata) { + return; + } - banksWithPriceAndToken.push({ bank, oraclePrice, tokenMetadata }); + banksWithPriceAndToken.push({ bank, oraclePrice, tokenMetadata }); + } catch (err) { + console.error("error fetching token metadata: ", err); + } }); const [extendedBankInfos, extendedBankMetadatas] = banksWithPriceAndToken.reduce( @@ -185,7 +189,9 @@ const stateCreator: StateCreator = (set, get) => ({ let userData; if (wallet?.publicKey) { const tokenAccount = tokenAccountMap!.get(bank.mint.toBase58()); - if (!tokenAccount) throw new Error(`Token account not found for ${bank.mint.toBase58()}`); + if (!tokenAccount) { + return acc; + } userData = { nativeSolBalance, tokenAccount, From 9015e1e231db6442813414bd2ca8582eadd9ca52 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Wed, 22 Nov 2023 13:47:03 -0500 Subject: [PATCH 04/93] chore: descrease birdeye abort controller to 5 seconds --- packages/marginfi-v2-ui-state/src/lib/mrgnlend.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/marginfi-v2-ui-state/src/lib/mrgnlend.ts b/packages/marginfi-v2-ui-state/src/lib/mrgnlend.ts index a8df64d0b8..9ec23192d9 100644 --- a/packages/marginfi-v2-ui-state/src/lib/mrgnlend.ts +++ b/packages/marginfi-v2-ui-state/src/lib/mrgnlend.ts @@ -132,7 +132,7 @@ export async function fetchBirdeyePrices(mints: PublicKey[], apiKey?: string): P const controller = new AbortController(); const timeoutId = setTimeout(() => { controller.abort(); - }, 10000); + }, 5000); const response = await fetch(`${BIRDEYE_API}/public/multi_price?list_address=${mintList}`, { headers: { Accept: "application/json", From c7fa15cee44fec33c0084f70b9e93df0ba7d4d4b Mon Sep 17 00:00:00 2001 From: Kobe Leenders Date: Tue, 21 Nov 2023 17:35:43 +0100 Subject: [PATCH 05/93] WIP: point fixes --- apps/marginfi-v2-ui/package.json | 2 +- .../src/hooks/useFirebaseAccount.tsx | 12 ++++++---- apps/marginfi-v2-ui/src/pages/points.tsx | 14 +++++++++++ .../marginfi-v2-ui-state/src/lib/firebase.ts | 24 +++++++++++++++++++ 4 files changed, 47 insertions(+), 5 deletions(-) diff --git a/apps/marginfi-v2-ui/package.json b/apps/marginfi-v2-ui/package.json index ef9b7e1a95..4d87b05df8 100644 --- a/apps/marginfi-v2-ui/package.json +++ b/apps/marginfi-v2-ui/package.json @@ -57,7 +57,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "cmdk": "^0.2.0", - "firebase": "^9.22.1", + "firebase": "^10.6.0", "firebase-admin": "^11.9.0", "jsbi": "^4.3.0", "lodash.debounce": "^4.0.8", diff --git a/apps/marginfi-v2-ui/src/hooks/useFirebaseAccount.tsx b/apps/marginfi-v2-ui/src/hooks/useFirebaseAccount.tsx index 5bb4c19ef5..2167119601 100644 --- a/apps/marginfi-v2-ui/src/hooks/useFirebaseAccount.tsx +++ b/apps/marginfi-v2-ui/src/hooks/useFirebaseAccount.tsx @@ -8,15 +8,19 @@ import { useWalletContext } from "./useWalletContext"; const useFirebaseAccount = () => { const { connected, walletAddress } = useWalletContext(); - const [checkForFirebaseUser, setFirebaseUser, signoutFirebaseUser, fetchPoints, resetPoints] = useUserProfileStore( - (state) => [ + const [checkForFirebaseUser, setFirebaseUser, signoutFirebaseUser, fetchPoints, resetPoints, hasUser] = + useUserProfileStore((state) => [ state.checkForFirebaseUser, state.setFirebaseUser, state.signoutFirebaseUser, state.fetchPoints, state.resetPoints, - ] - ); + state.hasUser, + ]); + + useEffect(() => { + console.log({ hasUser }); + }, [hasUser]); useEffect(() => { // NOTE: if more point-specific logic is added, move this to a separate hook diff --git a/apps/marginfi-v2-ui/src/pages/points.tsx b/apps/marginfi-v2-ui/src/pages/points.tsx index 844a028f8f..51b8c611fd 100644 --- a/apps/marginfi-v2-ui/src/pages/points.tsx +++ b/apps/marginfi-v2-ui/src/pages/points.tsx @@ -23,6 +23,9 @@ import { const Points = () => { const { connected } = useWalletContext(); const { query: routerQuery } = useRouter(); + // useUserProfileStore(() => { + // state + // }) const [currentFirebaseUser, hasUser, userPointsData] = useUserProfileStore((state) => [ state.currentFirebaseUser, state.hasUser, @@ -32,6 +35,17 @@ const Points = () => { const referralCode = React.useMemo(() => routerQuery.referralCode as string | undefined, [routerQuery.referralCode]); const [isReferralCopied, setIsReferralCopied] = React.useState(false); + // React.useEffect(() => { + // fetchMrgnlendState({ marginfiConfig: config.mfiConfig, connection, wallet, isOverride }).catch(console.error); + // const id = setInterval(() => { + // setIsRefreshingStore(true); + // fetchMrgnlendState().catch(console.error); + // }, 30_000); + // return () => clearInterval(id); + // }, [wallet, isOverride]); // eslint-disable-line react-hooks/exhaustive-deps + // ^ crucial to omit both `connection` and `fetchMrgnlendState` from the dependency array + // TODO: fix... + return ( <> points diff --git a/packages/marginfi-v2-ui-state/src/lib/firebase.ts b/packages/marginfi-v2-ui-state/src/lib/firebase.ts index 25700e3a5e..57a2d2ce19 100644 --- a/packages/marginfi-v2-ui-state/src/lib/firebase.ts +++ b/packages/marginfi-v2-ui-state/src/lib/firebase.ts @@ -60,6 +60,14 @@ const LoginPayloadStruct = object({ }); type LoginPayload = Infer; +async function loginOrSignup(wallet: Wallet) { + const user = await getUser(wallet.publicKey); + + if (user) { + } else { + } +} + async function login( wallet: Wallet, signingMethod: SigningMethod, @@ -143,6 +151,10 @@ async function loginWithAuthData(signingMethod: SigningMethod, signedAuthDataRaw await signinFirebaseAuth(data.token); } +/** + * @deprecated + * Will be re-added once data is sensitive + */ async function signSignupMemo(wallet: Wallet, authData: SignupPayload): Promise { if (!wallet.publicKey) { throw new Error("Wallet not connected!"); @@ -162,6 +174,10 @@ async function signSignupMemo(wallet: Wallet, authData: SignupPayload): Promise< return signedData; } +/** + * @deprecated + * Will be re-added once data is sensitive + */ async function signSignupTx( wallet: Wallet, authData: SignupPayload, @@ -183,6 +199,10 @@ async function signSignupTx( return signedData; } +/** + * @deprecated + * Will be re-added once data is sensitive + */ async function signLoginMemo(wallet: Wallet, authData: LoginPayload): Promise { if (!wallet.publicKey) { throw new Error("Wallet not connected!"); @@ -202,6 +222,10 @@ async function signLoginMemo(wallet: Wallet, authData: LoginPayload): Promise Date: Wed, 22 Nov 2023 14:42:00 +0100 Subject: [PATCH 06/93] WIP: checkpoint --- .../desktop/Points/PointsSignIn.tsx | 2 +- .../desktop/Points/PointsSignUp.tsx | 2 +- .../src/pages/api/user/login.ts | 26 ++- .../src/pages/api/user/signup.ts | 68 +++--- apps/marginfi-v2-ui/src/pages/points.tsx | 15 +- .../marginfi-v2-ui-state/src/lib/firebase.ts | 118 +++++++---- yarn.lock | 198 +++++++++--------- 7 files changed, 237 insertions(+), 192 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/desktop/Points/PointsSignIn.tsx b/apps/marginfi-v2-ui/src/components/desktop/Points/PointsSignIn.tsx index 1893a84e30..cc679e2b09 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/Points/PointsSignIn.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/Points/PointsSignIn.tsx @@ -25,7 +25,7 @@ export const PointsSignIn: FC = ({}) => { toast.info("Logging in..."); const blockhashInfo = await connection.getLatestBlockhash(); try { - await firebaseApi.login(wallet, useAuthTx ? "tx" : "memo", blockhashInfo); + await firebaseApi.login(wallet); // localStorage.setItem("authData", JSON.stringify(signedAuthData)); toast.success("Logged in successfully"); } catch (loginError: any) { diff --git a/apps/marginfi-v2-ui/src/components/desktop/Points/PointsSignUp.tsx b/apps/marginfi-v2-ui/src/components/desktop/Points/PointsSignUp.tsx index ce3000b641..35d6af91e0 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/Points/PointsSignUp.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/Points/PointsSignUp.tsx @@ -41,7 +41,7 @@ export const PointsSignUp: FC = ({ referralCode }) => { toast.info("Logging in..."); const blockhashInfo = await connection.getLatestBlockhash(); try { - await firebaseApi.signup(wallet, useAuthTx ? "tx" : "memo", blockhashInfo, finalReferralCode); + await firebaseApi.signup(wallet, finalReferralCode); // localStorage.setItem("authData", JSON.stringify(signedAuthData)); toast.success("Signed up successfully"); } catch (signupError: any) { diff --git a/apps/marginfi-v2-ui/src/pages/api/user/login.ts b/apps/marginfi-v2-ui/src/pages/api/user/login.ts index 302b9ef294..92de7640e3 100644 --- a/apps/marginfi-v2-ui/src/pages/api/user/login.ts +++ b/apps/marginfi-v2-ui/src/pages/api/user/login.ts @@ -20,13 +20,15 @@ import { initFirebaseIfNeeded(); export interface LoginRequest { - method: SigningMethod; - signedAuthDataRaw: string; + walletAddress: string; + // method: SigningMethod; + // signedAuthDataRaw: string; } export default async function handler(req: NextApiRequest, res: any) { - const { method, signedAuthDataRaw } = req.body; + const { walletAddress } = req.body; + /* signing logic let signer; try { const loginData = validateAndUnpackLoginData(signedAuthDataRaw, method); @@ -46,15 +48,17 @@ export default async function handler(req: NextApiRequest, res: an status = STATUS_INTERNAL_ERROR; } return res.status(status).json({ error: error.message }); - } + }*/ let user; try { - const userResult = await getFirebaseUserByWallet(signer); + const userResult = await getFirebaseUserByWallet(walletAddress); if (userResult === undefined) { - await logLoginAttempt(signer, null, signedAuthDataRaw, false); + await logLoginAttempt(walletAddress, null, "", false); Sentry.captureException({ message: "User not found" }); return res.status(STATUS_NOT_FOUND).json({ error: "User not found" }); + } else { + await logLoginAttempt(walletAddress, user.uid, "", true); } user = userResult; } catch (error: any) { @@ -62,16 +66,18 @@ export default async function handler(req: NextApiRequest, res: an return res.status(STATUS_INTERNAL_ERROR).json({ error: error.message }); // An unexpected error occurred } - await logLoginAttempt(signer, user.uid, signedAuthDataRaw, true); - // Generate a custom token for the client to log in - const customToken = await admin.auth().createCustomToken(signer); + const customToken = await admin.auth().createCustomToken(walletAddress); - return res.status(STATUS_OK).json({ status: "success", uid: signer, token: customToken }); + return res.status(STATUS_OK).json({ status: "success", uid: walletAddress, token: customToken }); } // -------- Helpers +/** + * @deprecated + * Signing functionality + */ export function validateAndUnpackLoginData( signedAuthDataRaw: string, signingMethod: SigningMethod diff --git a/apps/marginfi-v2-ui/src/pages/api/user/signup.ts b/apps/marginfi-v2-ui/src/pages/api/user/signup.ts index 0c5240636b..1eda0263b8 100644 --- a/apps/marginfi-v2-ui/src/pages/api/user/signup.ts +++ b/apps/marginfi-v2-ui/src/pages/api/user/signup.ts @@ -9,6 +9,7 @@ import base58 from "bs58"; import nacl from "tweetnacl"; import { SigningMethod, + SignupPayload, STATUS_BAD_REQUEST, STATUS_UNAUTHORIZED, STATUS_INTERNAL_ERROR, @@ -19,43 +20,42 @@ import { initFirebaseIfNeeded(); export interface SignupRequest { - method: SigningMethod; - signedAuthDataRaw: string; + walletAddress: string; + payload: SignupPayload; + // method: SigningMethod; + // signedAuthDataRaw: string; } export default async function handler(req: NextApiRequest, res: any) { - const { method, signedAuthDataRaw } = req.body; + const { walletAddress, payload } = req.body; Sentry.setContext("signup_args", { - method, - signedAuthDataRaw, + walletAddress, }); - let signer; - let payload; - try { - const signupData = validateAndUnpackSignupData(signedAuthDataRaw, method); - signer = signupData.signer.toBase58(); - payload = signupData.payload; - } catch (error: any) { - Sentry.captureException(error); - let status; - switch (error.message) { - case "Invalid signup tx": - case "Invalid signup payload": - status = STATUS_BAD_REQUEST; - break; - case "Invalid signature": - status = STATUS_UNAUTHORIZED; - break; - default: - status = STATUS_INTERNAL_ERROR; - } - return res.status(status).json({ error: error.message }); - } + // try { + // const signupData = validateAndUnpackSignupData(signedAuthDataRaw, method); + // signer = signupData.signer.toBase58(); + // payload = signupData.payload; + // } catch (error: any) { + // Sentry.captureException(error); + // let status; + // switch (error.message) { + // case "Invalid signup tx": + // case "Invalid signup payload": + // status = STATUS_BAD_REQUEST; + // break; + // case "Invalid signature": + // status = STATUS_UNAUTHORIZED; + // break; + // default: + // status = STATUS_INTERNAL_ERROR; + // } + // return res.status(status).json({ error: error.message }); + // } try { - const user = await getFirebaseUserByWallet(signer); + const user = await getFirebaseUserByWallet(walletAddress); if (user) { Sentry.captureException({ message: "User already exists" }); return res.status(STATUS_BAD_REQUEST).json({ error: "User already exists" }); @@ -66,23 +66,27 @@ export default async function handler(req: NextApiRequest, res: a } try { - await createFirebaseUser(signer, payload.referralCode); + await createFirebaseUser(walletAddress, payload.referralCode); console.log("successfully created new user"); } catch (createUserError: any) { Sentry.captureException(createUserError); return res.status(STATUS_INTERNAL_ERROR).json({ error: createUserError.message }); } - await logSignupAttempt(signer, payload.uuid, signedAuthDataRaw, true); + await logSignupAttempt(walletAddress, payload.uuid, "", true); // Generate a custom token for the client to sign in - const customToken = await admin.auth().createCustomToken(signer); + const customToken = await admin.auth().createCustomToken(walletAddress); - return res.status(STATUS_OK).json({ status: "success", uid: signer, token: customToken }); + return res.status(STATUS_OK).json({ status: "success", uid: walletAddress, token: customToken }); } // -------- Helpers +/** + * @deprecated + * Signing functionality + */ export function validateAndUnpackSignupData( signedAuthDataRaw: string, signingMethod: SigningMethod diff --git a/apps/marginfi-v2-ui/src/pages/points.tsx b/apps/marginfi-v2-ui/src/pages/points.tsx index 51b8c611fd..f528dd80e5 100644 --- a/apps/marginfi-v2-ui/src/pages/points.tsx +++ b/apps/marginfi-v2-ui/src/pages/points.tsx @@ -23,9 +23,7 @@ import { const Points = () => { const { connected } = useWalletContext(); const { query: routerQuery } = useRouter(); - // useUserProfileStore(() => { - // state - // }) + const [currentFirebaseUser, hasUser, userPointsData] = useUserProfileStore((state) => [ state.currentFirebaseUser, state.hasUser, @@ -35,17 +33,6 @@ const Points = () => { const referralCode = React.useMemo(() => routerQuery.referralCode as string | undefined, [routerQuery.referralCode]); const [isReferralCopied, setIsReferralCopied] = React.useState(false); - // React.useEffect(() => { - // fetchMrgnlendState({ marginfiConfig: config.mfiConfig, connection, wallet, isOverride }).catch(console.error); - // const id = setInterval(() => { - // setIsRefreshingStore(true); - // fetchMrgnlendState().catch(console.error); - // }, 30_000); - // return () => clearInterval(id); - // }, [wallet, isOverride]); // eslint-disable-line react-hooks/exhaustive-deps - // ^ crucial to omit both `connection` and `fetchMrgnlendState` from the dependency array - // TODO: fix... - return ( <> points diff --git a/packages/marginfi-v2-ui-state/src/lib/firebase.ts b/packages/marginfi-v2-ui-state/src/lib/firebase.ts index 57a2d2ce19..57fdccbd2b 100644 --- a/packages/marginfi-v2-ui-state/src/lib/firebase.ts +++ b/packages/marginfi-v2-ui-state/src/lib/firebase.ts @@ -32,6 +32,17 @@ interface UserData { id: string; } +async function loginOrSignup(wallet: Wallet) { + const walletAddress = wallet.publicKey.toBase58(); + const user = await getUser(walletAddress); + + if (user) { + await login(wallet); + } else { + await signup(wallet); + } +} + async function getUser(walletAddress: string): Promise { const response = await fetch("/api/user/get", { method: "POST", @@ -60,25 +71,13 @@ const LoginPayloadStruct = object({ }); type LoginPayload = Infer; -async function loginOrSignup(wallet: Wallet) { - const user = await getUser(wallet.publicKey); - - if (user) { - } else { - } -} +async function login(wallet: Wallet) { + // const authData = { uuid: uuidv4() }; + // const signedAuthDataRaw = + // signingMethod === "tx" ? await signLoginTx(wallet, authData, blockhash) : await signLoginMemo(wallet, authData); + await loginWithAddress(wallet.publicKey.toBase58()); -async function login( - wallet: Wallet, - signingMethod: SigningMethod, - blockhash: BlockhashWithExpiryBlockHeight -): Promise<{ signingMethod: SigningMethod; signedAuthDataRaw: string }> { - const authData = { uuid: uuidv4() }; - const signedAuthDataRaw = - signingMethod === "tx" ? await signLoginTx(wallet, authData, blockhash) : await signLoginMemo(wallet, authData); - await loginWithAuthData(signingMethod, signedAuthDataRaw); - - return { signingMethod, signedAuthDataRaw }; + // return { signingMethod, signedAuthDataRaw }; } const SignupPayloadStruct = object({ @@ -87,12 +86,7 @@ const SignupPayloadStruct = object({ }); type SignupPayload = Infer; -async function signup( - wallet: Wallet, - signingMethod: SigningMethod, - blockhash: BlockhashWithExpiryBlockHeight, - referralCode?: string -): Promise<{ signingMethod: SigningMethod; signedAuthDataRaw: string }> { +async function signup(wallet: Wallet, referralCode?: string) { if (referralCode !== undefined && typeof referralCode !== "string") { throw new Error("Invalid referral code provided."); } @@ -102,15 +96,28 @@ async function signup( uuid, referralCode, }; - const signedAuthDataRaw = - signingMethod === "tx" ? await signSignupTx(wallet, authData, blockhash) : await signSignupMemo(wallet, authData); + // const signedAuthDataRaw = + // signingMethod === "tx" ? await signSignupTx(wallet, authData, blockhash) : await signSignupMemo(wallet, authData); + + await signupWithAddress(wallet.publicKey.toBase58(), authData); + // return { signingMethod, signedAuthDataRaw }; +} + +export { getUser, signup, login, SignupPayloadStruct, LoginPayloadStruct }; +export type { UserData, SignupPayload }; + +// ---------------------------------------------------------------------------- +// Helpers +// ---------------------------------------------------------------------------- + +async function signupWithAddress(walletAddress: string, payload: SignupPayload) { const response = await fetch("/api/user/signup", { method: "POST", headers: { "Content-Type": "application/json", }, - body: JSON.stringify({ method: signingMethod, signedAuthDataRaw }), + body: JSON.stringify({ walletAddress, payload }), }); const data = await response.json(); @@ -126,17 +133,54 @@ async function signup( if (!data.token) throw new Error("Something went wrong during sign-up"); await signinFirebaseAuth(data.token); +} - return { signingMethod, signedAuthDataRaw }; +async function loginWithAddress(walletAddress: string) { + const response = await fetch("/api/user/login", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ walletAddress }), + }); + const data = await response.json(); + + if (!data.token) throw new Error("Something went wrong during sign-in"); + await signinFirebaseAuth(data.token); } -export { getUser, signup, login, SignupPayloadStruct, LoginPayloadStruct }; -export type { UserData, SignupPayload }; +/** + * @deprecated + * Signing functionality + */ +async function signupWithAuthData(signingMethod: SigningMethod, signedAuthDataRaw: string) { + const response = await fetch("/api/user/signup", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ method: signingMethod, signedAuthDataRaw }), + }); + const data = await response.json(); -// ---------------------------------------------------------------------------- -// Helpers -// ---------------------------------------------------------------------------- + switch (response.status) { + case STATUS_BAD_REQUEST: + throw new Error(data.error); + case STATUS_UNAUTHORIZED: + case STATUS_INTERNAL_ERROR: + throw new Error("Something went wrong during sign-up"); + default: { + } + } + + if (!data.token) throw new Error("Something went wrong during sign-up"); + await signinFirebaseAuth(data.token); +} +/** + * @deprecated + * Signing functionality + */ async function loginWithAuthData(signingMethod: SigningMethod, signedAuthDataRaw: string) { const response = await fetch("/api/user/login", { method: "POST", @@ -153,7 +197,7 @@ async function loginWithAuthData(signingMethod: SigningMethod, signedAuthDataRaw /** * @deprecated - * Will be re-added once data is sensitive + * Signing functionality */ async function signSignupMemo(wallet: Wallet, authData: SignupPayload): Promise { if (!wallet.publicKey) { @@ -176,7 +220,7 @@ async function signSignupMemo(wallet: Wallet, authData: SignupPayload): Promise< /** * @deprecated - * Will be re-added once data is sensitive + * Signing functionality */ async function signSignupTx( wallet: Wallet, @@ -201,7 +245,7 @@ async function signSignupTx( /** * @deprecated - * Will be re-added once data is sensitive + * Signing functionality */ async function signLoginMemo(wallet: Wallet, authData: LoginPayload): Promise { if (!wallet.publicKey) { @@ -224,7 +268,7 @@ async function signLoginMemo(wallet: Wallet, authData: LoginPayload): Promise=12.12.47" - "@grpc/grpc-js@~1.8.0": version "1.8.21" resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.8.21.tgz#d282b122c71227859bf6c5866f4c40f4a2696513" @@ -3785,16 +3809,13 @@ "@grpc/proto-loader" "^0.7.0" "@types/node" ">=12.12.47" -"@grpc/proto-loader@^0.6.13": - version "0.6.13" - resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.6.13.tgz#008f989b72a40c60c96cd4088522f09b05ac66bc" - integrity sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g== +"@grpc/grpc-js@~1.9.0": + version "1.9.11" + resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.9.11.tgz#7b21195c910a49c0bb5d0df21d28a30c4e174851" + integrity sha512-QDhMfbTROOXUhLHMroow8f3EHiCKUOh6UwxMP5S3EuXMnWMNSVIhatGZRwkpg9OUTYdZPsDUVH3cOAkWhGFUJw== dependencies: - "@types/long" "^4.0.1" - lodash.camelcase "^4.3.0" - long "^4.0.0" - protobufjs "^6.11.3" - yargs "^16.2.0" + "@grpc/proto-loader" "^0.7.8" + "@types/node" ">=12.12.47" "@grpc/proto-loader@^0.7.0": version "0.7.9" @@ -3806,6 +3827,16 @@ protobufjs "^7.2.4" yargs "^17.7.2" +"@grpc/proto-loader@^0.7.8": + version "0.7.10" + resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.10.tgz#6bf26742b1b54d0a473067743da5d3189d06d720" + integrity sha512-CAqDfoaQ8ykFd9zqBDn4k6iWT9loLAlc2ETmDFS9JCD70gDcnA4L3AFEo2iV7KyAtAAHFW9ftq1Fz+Vsgq80RQ== + dependencies: + lodash.camelcase "^4.3.0" + long "^5.0.0" + protobufjs "^7.2.4" + yargs "^17.7.2" + "@hapi/hoek@^9.0.0": version "9.3.0" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" @@ -11671,15 +11702,6 @@ cliui@^6.0.0: strip-ansi "^6.0.0" wrap-ansi "^6.2.0" -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - cliui@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" @@ -14762,24 +14784,24 @@ firebase-admin@^11.9.0: "@google-cloud/firestore" "^6.6.0" "@google-cloud/storage" "^6.9.5" -firebase@^9.22.1: - version "9.23.0" - resolved "https://registry.yarnpkg.com/firebase/-/firebase-9.23.0.tgz#71fea60d704bfed8e92162911544fd6564a04d0e" - integrity sha512-/4lUVY0lUvBDIaeY1q6dUYhS8Sd18Qb9CgWkPZICUo9IXpJNCEagfNZXBBFCkMTTN5L5gx2Hjr27y21a9NzUcA== +firebase@^10.6.0: + version "10.6.0" + resolved "https://registry.yarnpkg.com/firebase/-/firebase-10.6.0.tgz#cee12b311dbf79d3958e8dd8b12fd335521997eb" + integrity sha512-bnYwHwZ6zB+dM6mGQPEXcFHtAT2WoVzG6H4SIR8HzURVGKJxBW+TqfP3qcJQjTZV3tDqDTo/XZkVmoU/SovV8A== dependencies: "@firebase/analytics" "0.10.0" "@firebase/analytics-compat" "0.2.6" - "@firebase/app" "0.9.13" + "@firebase/app" "0.9.23" "@firebase/app-check" "0.8.0" "@firebase/app-check-compat" "0.3.7" - "@firebase/app-compat" "0.2.13" + "@firebase/app-compat" "0.2.23" "@firebase/app-types" "0.9.0" - "@firebase/auth" "0.23.2" - "@firebase/auth-compat" "0.4.2" - "@firebase/database" "0.14.4" - "@firebase/database-compat" "0.3.4" - "@firebase/firestore" "3.13.0" - "@firebase/firestore-compat" "0.3.12" + "@firebase/auth" "1.4.0" + "@firebase/auth-compat" "0.4.9" + "@firebase/database" "1.0.1" + "@firebase/database-compat" "1.0.1" + "@firebase/firestore" "4.3.2" + "@firebase/firestore-compat" "0.3.22" "@firebase/functions" "0.10.0" "@firebase/functions-compat" "0.3.5" "@firebase/installations" "0.6.4" @@ -20591,7 +20613,7 @@ protobufjs@7.2.4: "@types/node" ">=13.7.0" long "^5.0.0" -protobufjs@^6.10.2, protobufjs@^6.11.3: +protobufjs@^6.10.2: version "6.11.4" resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.4.tgz#29a412c38bf70d89e537b6d02d904a6f448173aa" integrity sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw== @@ -24837,11 +24859,6 @@ yargs-parser@^18.1.2, yargs-parser@^18.1.3: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^20.2.2: - version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== - yargs-parser@^21.0.1, yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" @@ -24880,19 +24897,6 @@ yargs@^15.1.0, yargs@^15.3.1: y18n "^4.0.0" yargs-parser "^18.1.2" -yargs@^16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - yargs@^17.0.1, yargs@^17.3.1, yargs@^17.6.2, yargs@^17.7.1, yargs@^17.7.2: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" From 7b8f1d63b58a13b10fc69b6e3ab3e3e08b1765cc Mon Sep 17 00:00:00 2001 From: Kobe Leenders Date: Wed, 22 Nov 2023 18:05:43 +0100 Subject: [PATCH 07/93] fix(mfi-v2-ui): points improvement --- .../desktop/Points/PointsSignIn.tsx | 5 +- .../desktop/Points/PointsSignUp.tsx | 4 +- .../src/hooks/useFirebaseAccount.tsx | 14 ++++-- .../src/pages/api/user/login.ts | 6 +-- .../src/pages/api/user/signup.ts | 46 +++++++++---------- apps/marginfi-v2-ui/src/pages/points.tsx | 11 +---- .../marginfi-v2-ui-state/src/lib/firebase.ts | 26 ++++------- 7 files changed, 43 insertions(+), 69 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/desktop/Points/PointsSignIn.tsx b/apps/marginfi-v2-ui/src/components/desktop/Points/PointsSignIn.tsx index cc679e2b09..8986338847 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/Points/PointsSignIn.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/Points/PointsSignIn.tsx @@ -10,7 +10,6 @@ import { firebaseApi } from "@mrgnlabs/marginfi-v2-ui-state"; import { WalletButton } from "~/components/common/Wallet"; import { useWalletContext } from "~/hooks/useWalletContext"; import { MrgnTooltip } from "~/components/common/MrgnTooltip"; - interface PointsSignInProps {} export const PointsSignIn: FC = ({}) => { @@ -23,10 +22,8 @@ export const PointsSignIn: FC = ({}) => { return; } toast.info("Logging in..."); - const blockhashInfo = await connection.getLatestBlockhash(); try { - await firebaseApi.login(wallet); - // localStorage.setItem("authData", JSON.stringify(signedAuthData)); + await firebaseApi.login(wallet.publicKey.toBase58()); toast.success("Logged in successfully"); } catch (loginError: any) { toast.error(loginError.message); diff --git a/apps/marginfi-v2-ui/src/components/desktop/Points/PointsSignUp.tsx b/apps/marginfi-v2-ui/src/components/desktop/Points/PointsSignUp.tsx index 35d6af91e0..a92ce4ff2f 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/Points/PointsSignUp.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/Points/PointsSignUp.tsx @@ -39,10 +39,8 @@ export const PointsSignUp: FC = ({ referralCode }) => { return; } toast.info("Logging in..."); - const blockhashInfo = await connection.getLatestBlockhash(); try { - await firebaseApi.signup(wallet, finalReferralCode); - // localStorage.setItem("authData", JSON.stringify(signedAuthData)); + await firebaseApi.signup(wallet.publicKey.toBase58(), finalReferralCode); toast.success("Signed up successfully"); } catch (signupError: any) { toast.error(signupError.message); diff --git a/apps/marginfi-v2-ui/src/hooks/useFirebaseAccount.tsx b/apps/marginfi-v2-ui/src/hooks/useFirebaseAccount.tsx index 2167119601..00ec5e80c6 100644 --- a/apps/marginfi-v2-ui/src/hooks/useFirebaseAccount.tsx +++ b/apps/marginfi-v2-ui/src/hooks/useFirebaseAccount.tsx @@ -1,12 +1,18 @@ import { firebaseApi } from "@mrgnlabs/marginfi-v2-ui-state"; import { onAuthStateChanged } from "firebase/auth"; import { useEffect } from "react"; +import { useRouter } from "next/router"; + import { toast } from "react-toastify"; import { useUserProfileStore } from "~/store"; import { useWalletContext } from "./useWalletContext"; +import React from "react"; const useFirebaseAccount = () => { const { connected, walletAddress } = useWalletContext(); + const { query: routerQuery } = useRouter(); + + const referralCode = React.useMemo(() => routerQuery.referralCode as string | undefined, [routerQuery.referralCode]); const [checkForFirebaseUser, setFirebaseUser, signoutFirebaseUser, fetchPoints, resetPoints, hasUser] = useUserProfileStore((state) => [ @@ -18,10 +24,6 @@ const useFirebaseAccount = () => { state.hasUser, ]); - useEffect(() => { - console.log({ hasUser }); - }, [hasUser]); - useEffect(() => { // NOTE: if more point-specific logic is added, move this to a separate hook const unsubscribe = onAuthStateChanged(firebaseApi.auth, (newUser) => { @@ -39,8 +41,10 @@ const useFirebaseAccount = () => { // Wallet connection side effect (auto-login attempt) useEffect(() => { if (!walletAddress) return; + + firebaseApi.loginOrSignup(walletAddress.toBase58(), referralCode).catch(console.error); checkForFirebaseUser(walletAddress.toBase58()); - }, [walletAddress, checkForFirebaseUser]); + }, [walletAddress, checkForFirebaseUser, referralCode]); // Wallet disconnection/change side effect (auto-logout) useEffect(() => { diff --git a/apps/marginfi-v2-ui/src/pages/api/user/login.ts b/apps/marginfi-v2-ui/src/pages/api/user/login.ts index 92de7640e3..994077956a 100644 --- a/apps/marginfi-v2-ui/src/pages/api/user/login.ts +++ b/apps/marginfi-v2-ui/src/pages/api/user/login.ts @@ -21,8 +21,6 @@ initFirebaseIfNeeded(); export interface LoginRequest { walletAddress: string; - // method: SigningMethod; - // signedAuthDataRaw: string; } export default async function handler(req: NextApiRequest, res: any) { @@ -50,7 +48,6 @@ export default async function handler(req: NextApiRequest, res: an return res.status(status).json({ error: error.message }); }*/ - let user; try { const userResult = await getFirebaseUserByWallet(walletAddress); if (userResult === undefined) { @@ -58,9 +55,8 @@ export default async function handler(req: NextApiRequest, res: an Sentry.captureException({ message: "User not found" }); return res.status(STATUS_NOT_FOUND).json({ error: "User not found" }); } else { - await logLoginAttempt(walletAddress, user.uid, "", true); + await logLoginAttempt(walletAddress, userResult.uid, "", true); } - user = userResult; } catch (error: any) { Sentry.captureException(error); return res.status(STATUS_INTERNAL_ERROR).json({ error: error.message }); // An unexpected error occurred diff --git a/apps/marginfi-v2-ui/src/pages/api/user/signup.ts b/apps/marginfi-v2-ui/src/pages/api/user/signup.ts index 1eda0263b8..02bb0b83c3 100644 --- a/apps/marginfi-v2-ui/src/pages/api/user/signup.ts +++ b/apps/marginfi-v2-ui/src/pages/api/user/signup.ts @@ -9,7 +9,6 @@ import base58 from "bs58"; import nacl from "tweetnacl"; import { SigningMethod, - SignupPayload, STATUS_BAD_REQUEST, STATUS_UNAUTHORIZED, STATUS_INTERNAL_ERROR, @@ -21,9 +20,7 @@ initFirebaseIfNeeded(); export interface SignupRequest { walletAddress: string; - payload: SignupPayload; - // method: SigningMethod; - // signedAuthDataRaw: string; + payload: firebaseApi.SignupPayload; } export default async function handler(req: NextApiRequest, res: any) { @@ -33,26 +30,27 @@ export default async function handler(req: NextApiRequest, res: a walletAddress, }); - // try { - // const signupData = validateAndUnpackSignupData(signedAuthDataRaw, method); - // signer = signupData.signer.toBase58(); - // payload = signupData.payload; - // } catch (error: any) { - // Sentry.captureException(error); - // let status; - // switch (error.message) { - // case "Invalid signup tx": - // case "Invalid signup payload": - // status = STATUS_BAD_REQUEST; - // break; - // case "Invalid signature": - // status = STATUS_UNAUTHORIZED; - // break; - // default: - // status = STATUS_INTERNAL_ERROR; - // } - // return res.status(status).json({ error: error.message }); - // } + /* signing logic + try { + const signupData = validateAndUnpackSignupData(signedAuthDataRaw, method); + signer = signupData.signer.toBase58(); + payload = signupData.payload; + } catch (error: any) { + Sentry.captureException(error); + let status; + switch (error.message) { + case "Invalid signup tx": + case "Invalid signup payload": + status = STATUS_BAD_REQUEST; + break; + case "Invalid signature": + status = STATUS_UNAUTHORIZED; + break; + default: + status = STATUS_INTERNAL_ERROR; + } + return res.status(status).json({ error: error.message }); + }*/ try { const user = await getFirebaseUserByWallet(walletAddress); diff --git a/apps/marginfi-v2-ui/src/pages/points.tsx b/apps/marginfi-v2-ui/src/pages/points.tsx index f528dd80e5..17c8a01d69 100644 --- a/apps/marginfi-v2-ui/src/pages/points.tsx +++ b/apps/marginfi-v2-ui/src/pages/points.tsx @@ -1,7 +1,6 @@ import React from "react"; import Link from "next/link"; -import { useRouter } from "next/router"; import { Button } from "@mui/material"; import FileCopyIcon from "@mui/icons-material/FileCopy"; @@ -14,15 +13,12 @@ import { PageHeader } from "~/components/common/PageHeader"; import { PointsLeaderBoard, PointsOverview, - PointsSignIn, - PointsSignUp, PointsCheckingUser, PointsConnectWallet, } from "~/components/desktop/Points"; const Points = () => { const { connected } = useWalletContext(); - const { query: routerQuery } = useRouter(); const [currentFirebaseUser, hasUser, userPointsData] = useUserProfileStore((state) => [ state.currentFirebaseUser, @@ -30,7 +26,6 @@ const Points = () => { state.userPointsData, ]); - const referralCode = React.useMemo(() => routerQuery.referralCode as string | undefined, [routerQuery.referralCode]); const [isReferralCopied, setIsReferralCopied] = React.useState(false); return ( @@ -41,12 +36,8 @@ const Points = () => { ) : currentFirebaseUser ? ( - ) : hasUser === null ? ( - - ) : hasUser ? ( - ) : ( - + )}
@@ -110,3 +117,50 @@ const Home = () => { }; export default Home; + +const MultipleAccountsBanner = ({ + selectedAccount, + marginfiAccounts, + fetchMrgnlendState, +}: { + selectedAccount: MarginfiAccountWrapper; + marginfiAccounts: MarginfiAccountWrapper[]; + fetchMrgnlendState: any; +}) => { + const [expanded, setExpanded] = useState(false); + return ( +
+
+
setExpanded((current) => !current)} + > + +
+
+
+

Multiple accounts were found (support coming soon).

+
+
+
+ {expanded && ( +
+ {marginfiAccounts.map((account, index) => ( +
{ + localStorage.setItem("mfiAccount", account.address.toBase58()); + fetchMrgnlendState(); + }} + > +
{selectedAccount.address.equals(account.address) && }
{" "} + {account.address.toBase58()} +
+ ))} +
+ )} +
+
+ ); +}; diff --git a/packages/marginfi-v2-ui-state/src/store/mrgnlendStore.ts b/packages/marginfi-v2-ui-state/src/store/mrgnlendStore.ts index 0e76a799b2..3ee7b7cf0a 100644 --- a/packages/marginfi-v2-ui-state/src/store/mrgnlendStore.ts +++ b/packages/marginfi-v2-ui-state/src/store/mrgnlendStore.ts @@ -39,12 +39,12 @@ interface MrgnlendState { userDataFetched: boolean; isRefreshingStore: boolean; marginfiClient: MarginfiClient | null; + marginfiAccounts: MarginfiAccountWrapper[]; bankMetadataMap: BankMetadataMap; tokenMetadataMap: TokenMetadataMap; extendedBankMetadatas: ExtendedBankMetadata[]; extendedBankInfos: ExtendedBankInfo[]; protocolStats: ProtocolStats; - marginfiAccountCount: number; selectedAccount: MarginfiAccountWrapper | null; nativeSolBalance: number; accountSummary: AccountSummary; @@ -86,6 +86,7 @@ const stateCreator: StateCreator = (set, get) => ({ userDataFetched: false, isRefreshingStore: false, marginfiClient: null, + marginfiAccounts: [], bankMetadataMap: {}, tokenMetadataMap: {}, extendedBankMetadatas: [], @@ -149,7 +150,27 @@ const stateCreator: StateCreator = (set, get) => ({ nativeSolBalance = tokenData.nativeSolBalance; tokenAccountMap = tokenData.tokenAccountMap; marginfiAccounts = marginfiAccountWrappers; - selectedAccount = marginfiAccounts[0]; + + //@ts-ignore + const selectedAccountAddress = localStorage.getItem("mfiAccount"); + if (!selectedAccountAddress && marginfiAccounts.length > 0) { + // if no account is saved, select the highest value account (first one) + selectedAccount = marginfiAccounts[0]; + } else { + // if account is saved, select it if found, otherwise forget saved one + const maybeSelectedAccount = marginfiAccounts.find( + (account) => account.address.toBase58() === selectedAccountAddress + ); + + if (maybeSelectedAccount) { + selectedAccount = maybeSelectedAccount; + } else { + //@ts-ignore + localStorage.removeItem("mfiAccount"); + selectedAccount = null; + } + } + userDataFetched = true; } @@ -238,6 +259,7 @@ const stateCreator: StateCreator = (set, get) => ({ userDataFetched, isRefreshingStore: false, marginfiClient, + marginfiAccounts, bankMetadataMap, tokenMetadataMap, extendedBankInfos: sortedExtendedBankInfos, @@ -248,7 +270,6 @@ const stateCreator: StateCreator = (set, get) => ({ tvl: deposits - borrows, pointsTotal: pointSummary.points_total, }, - marginfiAccountCount: marginfiAccounts.length, selectedAccount, nativeSolBalance, accountSummary, From c17bdf3f4e8bce2acfb1b2debbc511e69569811d Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Thu, 23 Nov 2023 13:50:37 -0500 Subject: [PATCH 15/93] feat: account switcher select component --- apps/marginfi-v2-ui/src/pages/index.tsx | 87 +++++++++++++++---------- 1 file changed, 52 insertions(+), 35 deletions(-) diff --git a/apps/marginfi-v2-ui/src/pages/index.tsx b/apps/marginfi-v2-ui/src/pages/index.tsx index f135a21b98..4e6b552100 100644 --- a/apps/marginfi-v2-ui/src/pages/index.tsx +++ b/apps/marginfi-v2-ui/src/pages/index.tsx @@ -1,16 +1,22 @@ -import React, { useState } from "react"; +import React from "react"; + import dynamic from "next/dynamic"; + +import { MarginfiAccountWrapper } from "@mrgnlabs/marginfi-client-v2"; import { shortenAddress } from "@mrgnlabs/mrgn-common"; + import config from "~/config/marginfi"; +import { Desktop, Mobile } from "~/mediaQueries"; import { useMrgnlendStore } from "~/store"; import { useConnection } from "~/hooks/useConnection"; import { useWalletContext } from "~/hooks/useWalletContext"; + import { Banner } from "~/components/desktop/Banner"; -import { PageHeader } from "~/components/common/PageHeader"; import { OverlaySpinner } from "~/components/desktop/OverlaySpinner"; -import { Desktop, Mobile } from "~/mediaQueries"; -import { IconAlertTriangleFilled, IconChevronRight } from "@tabler/icons-react"; -import { MarginfiAccountWrapper } from "@mrgnlabs/marginfi-client-v2"; +import { PageHeader } from "~/components/common/PageHeader"; + +import { IconAlertTriangle } from "~/components/ui/icons"; +import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger } from "~/components/ui/select"; const DesktopAccountSummary = dynamic( async () => (await import("~/components/desktop/DesktopAccountSummary")).DesktopAccountSummary, @@ -95,6 +101,8 @@ const Home = () => { selectedAccount={selectedAccount} marginfiAccounts={marginfiAccounts} fetchMrgnlendState={fetchMrgnlendState} + isRefreshing={isRefreshingStore} + setIsRefreshing={setIsRefreshingStore} /> )} @@ -122,44 +130,53 @@ const MultipleAccountsBanner = ({ selectedAccount, marginfiAccounts, fetchMrgnlendState, + isRefreshing, + setIsRefreshing, }: { selectedAccount: MarginfiAccountWrapper; marginfiAccounts: MarginfiAccountWrapper[]; fetchMrgnlendState: any; + isRefreshing: boolean; + setIsRefreshing: (isRefreshingStore: boolean) => void; }) => { - const [expanded, setExpanded] = useState(false); + const shortAddress = React.useMemo( + () => shortenAddress(selectedAccount.address.toBase58()), + [selectedAccount.address] + ); + return ( -
+
-
setExpanded((current) => !current)} - > - -
-
-
-

Multiple accounts were found (support coming soon).

-
-
+
+ +

+ Multiple accounts found (support coming soon) +

+
+
+

Select account:

+
- {expanded && ( -
- {marginfiAccounts.map((account, index) => ( -
{ - localStorage.setItem("mfiAccount", account.address.toBase58()); - fetchMrgnlendState(); - }} - > -
{selectedAccount.address.equals(account.address) && }
{" "} - {account.address.toBase58()} -
- ))} -
- )}
); From c3c0ffc897649b0c784ed9d4b535cd23198b49f6 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Thu, 23 Nov 2023 13:58:39 -0500 Subject: [PATCH 16/93] feat: mobile account switcher --- apps/marginfi-v2-ui/src/pages/index.tsx | 13 +++++++++++-- .../marginfi-client-v2/src/models/account/pure.ts | 2 -- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/marginfi-v2-ui/src/pages/index.tsx b/apps/marginfi-v2-ui/src/pages/index.tsx index 4e6b552100..cd1878d8c9 100644 --- a/apps/marginfi-v2-ui/src/pages/index.tsx +++ b/apps/marginfi-v2-ui/src/pages/index.tsx @@ -116,6 +116,15 @@ const Home = () => { lend + {walletAddress && selectedAccount && marginfiAccounts.length > 1 && ( + + )}
@@ -165,11 +174,11 @@ const MultipleAccountsBanner = ({ }} > {isRefreshing ? "Loading..." : shortAddress} - + Accounts {marginfiAccounts.map((account, index) => ( - + {account.address.toBase58()} ))} diff --git a/packages/marginfi-client-v2/src/models/account/pure.ts b/packages/marginfi-client-v2/src/models/account/pure.ts index 627c7277a9..8157e2d65b 100644 --- a/packages/marginfi-client-v2/src/models/account/pure.ts +++ b/packages/marginfi-client-v2/src/models/account/pure.ts @@ -295,10 +295,8 @@ class MarginfiAccount { // ------------------------------------- // // collateral bank with positive weights // // ------------------------------------- // - console.log("here"); // bypass volatility factor if no liabilities or if all collateral is untied if (liabilitiesInit.isZero() || initCollateralForBank.lte(freeCollateral)) { - console.log("aqui"); return entireBalance; } From d4088c54083fbecd649851e0bf1e00aab4a91007 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Fri, 24 Nov 2023 11:54:04 -0500 Subject: [PATCH 17/93] feat: add trust wallet support --- .../common/Wallet/WalletAuthDialog.tsx | 2 + .../src/components/ui/icons.tsx | 63 +++++++++++++++++++ apps/marginfi-v2-ui/src/config/wallets.ts | 2 + 3 files changed, 67 insertions(+) diff --git a/apps/marginfi-v2-ui/src/components/common/Wallet/WalletAuthDialog.tsx b/apps/marginfi-v2-ui/src/components/common/Wallet/WalletAuthDialog.tsx index f53c507628..1651c6a95e 100644 --- a/apps/marginfi-v2-ui/src/components/common/Wallet/WalletAuthDialog.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Wallet/WalletAuthDialog.tsx @@ -25,6 +25,7 @@ import { IconSolflareWallet, IconWalletConnectWallet, IconGlowWallet, + IconTrustWallet, IconChevronDown, } from "~/components/ui/icons"; @@ -62,6 +63,7 @@ const walletIcons: { [key: string]: React.ReactNode } = { Backpack: , WalletConnect: , Glow: , + Trust: , }; export const WalletAuthDialog = () => { diff --git a/apps/marginfi-v2-ui/src/components/ui/icons.tsx b/apps/marginfi-v2-ui/src/components/ui/icons.tsx index 622e7819af..f7fb664e8f 100644 --- a/apps/marginfi-v2-ui/src/components/ui/icons.tsx +++ b/apps/marginfi-v2-ui/src/components/ui/icons.tsx @@ -264,6 +264,68 @@ const IconGlowWallet = ({ size = 24, className }: IconProps) => ( ); +const IconTrustWallet = ({ size = 24, className }: IconProps) => ( + + + + + + + + + + + + + + + + + + + + + + + + +); + const IconLoader = ({ size = 24, className }: IconProps) => ( ); @@ -403,6 +465,7 @@ export { IconBackpackWallet, IconWalletConnectWallet, IconGlowWallet, + IconTrustWallet, IconLoader, IconPieChart, IconReceiveMoney, diff --git a/apps/marginfi-v2-ui/src/config/wallets.ts b/apps/marginfi-v2-ui/src/config/wallets.ts index 104548cb10..25809de764 100644 --- a/apps/marginfi-v2-ui/src/config/wallets.ts +++ b/apps/marginfi-v2-ui/src/config/wallets.ts @@ -5,6 +5,7 @@ import { BackpackWalletAdapter, WalletConnectWalletAdapter, CoinbaseWalletAdapter, + TrustWalletAdapter, } from "@solana/wallet-adapter-wallets"; import { createDefaultAddressSelector, @@ -31,6 +32,7 @@ export const WALLET_ADAPTERS = [ new BackpackWalletAdapter(), new GlowWalletAdapter(), new CoinbaseWalletAdapter(), + new TrustWalletAdapter(), new WalletConnectWalletAdapter({ network: WalletAdapterNetwork.Mainnet, options: { From 823d8848e8f7d2cbf3fe426ac6b5e8c1eb1435d1 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Mon, 20 Nov 2023 18:02:14 -0500 Subject: [PATCH 18/93] feat: new asset banner (wip) --- .../common/AssetList/NewAsssetBanner.tsx | 58 +++++++++++++++++++ .../src/components/common/AssetList/index.ts | 1 + .../desktop/AssetsList/AssetsList.tsx | 18 +++++- 3 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx diff --git a/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx b/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx new file mode 100644 index 0000000000..8752e70a85 --- /dev/null +++ b/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx @@ -0,0 +1,58 @@ +import React from "react"; +import Image from "next/image"; +import { Button } from "~/components/ui/button"; +import { IconX } from "~/components/ui/icons"; + +type NewAssetBannerProps = { + asset: string; + image: string; +}; + +export const NewAssetBanner = ({ asset, image }: NewAssetBannerProps) => { + const assetTicker = React.useMemo(() => "$" + asset.toUpperCase(), [asset]); + + return ( +
+
+
+ {asset} +
+
+

{assetTicker} is now available on margnfi

+
    +
  • + +
  • +
  • + +
  • +
+
+
+ +
+ ); +}; + +type NewAssetBannerListProps = { + assets: { + asset: string; + image: string; + }[]; +}; + +export const NewAssetBannerList = ({ assets }: NewAssetBannerListProps) => { + return ( +
+ {assets.map((asset) => ( + + ))} +
+ ); +}; diff --git a/apps/marginfi-v2-ui/src/components/common/AssetList/index.ts b/apps/marginfi-v2-ui/src/components/common/AssetList/index.ts index ece413cd56..28b9487ae6 100644 --- a/apps/marginfi-v2-ui/src/components/common/AssetList/index.ts +++ b/apps/marginfi-v2-ui/src/components/common/AssetList/index.ts @@ -1,3 +1,4 @@ export * from "./AssetRowAction"; export * from "./AssetRowInputBox"; export * from "./LSTDialog"; +export * from "./NewAsssetBanner"; diff --git a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx index 18aa112ee7..d25354808f 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx @@ -8,7 +8,7 @@ import { useMrgnlendStore, useUserProfileStore } from "~/store"; import { useWalletContext } from "~/hooks/useWalletContext"; import { LoadingAsset, AssetRow } from "./AssetRow"; -import { LSTDialog, LSTDialogVariants } from "~/components/common/AssetList"; +import { LSTDialog, LSTDialogVariants, NewAssetBannerList } from "~/components/common/AssetList"; import { MrgnTooltip } from "~/components/common/MrgnTooltip"; import { MrgnLabeledSwitch } from "~/components/common"; @@ -99,7 +99,7 @@ const AssetsList: FC = () => { return ( <> -
+
{ onClick={() => setIsInLendingMode(!isInLendingMode)} />
+ +
From 7d1a97239f90f36da983459419a947e9359757b7 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Tue, 21 Nov 2023 13:07:26 -0500 Subject: [PATCH 19/93] feat: ui for filter positions toggle / pool type dropdown --- .../desktop/AssetsList/AssetsList.tsx | 55 ++++++++++++++++--- .../MobileAssetsList/MobileAssetsList.tsx | 1 + 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx index d25354808f..adf4056ee8 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx @@ -4,13 +4,15 @@ import { useHotkeys } from "react-hotkeys-hook"; import { Card, Table, TableHead, TableBody, TableContainer, TableCell, TableRow } from "@mui/material"; import Typography from "@mui/material/Typography"; -import { useMrgnlendStore, useUserProfileStore } from "~/store"; +import { useMrgnlendStore, useUserProfileStore, useUiStore } from "~/store"; import { useWalletContext } from "~/hooks/useWalletContext"; +import { cn } from "~/utils"; import { LoadingAsset, AssetRow } from "./AssetRow"; import { LSTDialog, LSTDialogVariants, NewAssetBannerList } from "~/components/common/AssetList"; -import { MrgnTooltip } from "~/components/common/MrgnTooltip"; -import { MrgnLabeledSwitch } from "~/components/common"; +import { MrgnTooltip, MrgnLabeledSwitch, MrgnContainedSwitch } from "~/components/common"; + +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select"; const AssetsList: FC = () => { // const { selectedAccount, nativeSolBalance } = useStore(); @@ -26,6 +28,7 @@ const AssetsList: FC = () => { state.showBadges, state.setShowBadges, ]); + const [setIsWalletAuthDialogOpen] = useUiStore((state) => [state.setIsWalletAuthDialogOpen]); const inputRefs = useRef>({}); const [isInLendingMode, setIsInLendingMode] = useState(true); @@ -33,6 +36,8 @@ const AssetsList: FC = () => { const [isLSTDialogOpen, setIsLSTDialogOpen] = useState(false); const [lstDialogVariant, setLSTDialogVariant] = useState(null); const [lstDialogCallback, setLSTDialogCallback] = useState<(() => void) | null>(null); + const [isFiltered, setIsFiltered] = useState(false); + const togglePositions = () => setIsFiltered((previousState) => !previousState); // Enter hotkey mode useHotkeys( @@ -100,13 +105,45 @@ const AssetsList: FC = () => { return ( <>
-
- setIsInLendingMode(!isInLendingMode)} +
+
+ setIsInLendingMode(!isInLendingMode)} + /> +
+
+ Filter pools + +
+
+ +
{ + e.stopPropagation(); + if (connected) return; + setIsWalletAuthDialogOpen(true); + }} + > + +
Filter my positions
{ checked={isFiltered} onChange={togglePositions} inputProps={{ "aria-label": "controlled" }} + className={cn(!connected && "pointer-events-none")} />
Filter my positions
From e89d610c32c1b1466f3dc162b79748a0ced9694b Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Tue, 21 Nov 2023 13:23:22 -0500 Subject: [PATCH 20/93] feat: filter pools by type --- .../desktop/AssetsList/AssetsList.tsx | 659 +++++++++++------- apps/marginfi-v2-ui/src/store/uiStore.ts | 5 + apps/marginfi-v2-ui/src/types.ts | 6 + 3 files changed, 435 insertions(+), 235 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx index adf4056ee8..2e27631d20 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx @@ -1,3 +1,5 @@ +import type { PoolTypes } from "~/types"; + import React, { FC, useEffect, useRef, useState } from "react"; import Image from "next/image"; import { useHotkeys } from "react-hotkeys-hook"; @@ -28,7 +30,11 @@ const AssetsList: FC = () => { state.showBadges, state.setShowBadges, ]); - const [setIsWalletAuthDialogOpen] = useUiStore((state) => [state.setIsWalletAuthDialogOpen]); + const [setIsWalletAuthDialogOpen, poolFilter, setPoolFilter] = useUiStore((state) => [ + state.setIsWalletAuthDialogOpen, + state.poolFilter, + state.setPoolFilter, + ]); const inputRefs = useRef>({}); const [isInLendingMode, setIsInLendingMode] = useState(true); @@ -116,14 +122,19 @@ const AssetsList: FC = () => {
Filter pools - { + setPoolFilter(value as PoolTypes); + }} + > - All pools - Global pools - Isolated pools + All pools + Global pool + Isolated pools
@@ -164,143 +175,274 @@ const AssetsList: FC = () => {
- - - - -
- Global pool -
-
- -
- Price - - - Realtime prices - - - Powered by Pyth and Switchboard. - - - } - placement="top" - > - - -
-
- -
- {isInLendingMode ? "APY" : "APR"} - - - {isInLendingMode ? "APY" : "APR"} - - - {isInLendingMode - ? "What you'll earn on deposits over a year. This includes compounding. All marginfi deposits are compounded hourly." - : "What you'll pay for your borrows, or the price of a loan. This does not include compounding. All marginfi borrows are compounded hourly."} - - - } - placement="top" - > - - -
-
- -
- {isInLendingMode ? "Weight" : "LTV"} - - - {isInLendingMode ? "Weight" : "LTV"} - - - {isInLendingMode - ? "How much your assets count for collateral, relative to their USD value. The higher the weight, the more collateral you can borrow against it." - : "How much you can borrow against your free collateral. The higher the LTV, the more you can borrow against your free collateral."} - - - } - placement="top" + {poolFilter !== "isolated" && ( +
+ + + +
+ Global pool +
+
+ +
+ Price + + + Realtime prices + + + Powered by Pyth and Switchboard. + + + } + placement="top" + > + + +
+
+ +
+ {isInLendingMode ? "APY" : "APR"} + + + {isInLendingMode ? "APY" : "APR"} + + + {isInLendingMode + ? "What you'll earn on deposits over a year. This includes compounding. All marginfi deposits are compounded hourly." + : "What you'll pay for your borrows, or the price of a loan. This does not include compounding. All marginfi borrows are compounded hourly."} + + + } + placement="top" + > + + +
+
+ +
+ {isInLendingMode ? "Weight" : "LTV"} + + + {isInLendingMode ? "Weight" : "LTV"} + + + {isInLendingMode + ? "How much your assets count for collateral, relative to their USD value. The higher the weight, the more collateral you can borrow against it." + : "How much you can borrow against your free collateral. The higher the LTV, the more you can borrow against your free collateral."} + + + } + placement="top" + > + + +
+
+ +
+ {isInLendingMode ? "Deposits" : "Available"} + + + {isInLendingMode ? "Total deposits" : "Total available"} + + + {isInLendingMode + ? "Total marginfi deposits for each asset. Everything is denominated in native tokens." + : "The amount of tokens available to borrow for each asset. Calculated as the minimum of the asset's borrow limit and available liquidity that has not yet been borrowed."} + + + } + placement="top" + > + + +
+
+ + {/*******************************/} + {/* [START]: ZOOM-BASED COLUMNS */} + {/*******************************/} + + {lendZoomLevel < 2 && ( + - - - - - -
- {isInLendingMode ? "Deposits" : "Available"} - - - {isInLendingMode ? "Total deposits" : "Total available"} - - - {isInLendingMode - ? "Total marginfi deposits for each asset. Everything is denominated in native tokens." - : "The amount of tokens available to borrow for each asset. Calculated as the minimum of the asset's borrow limit and available liquidity that has not yet been borrowed."} - - - } - placement="top" +
+ Global limit + + + {isInLendingMode ? "Global deposit cap" : "Global borrow cap"} + + Each marginfi pool has global deposit and borrow limits, also known as caps. This is the + total amount that all users combined can deposit or borrow of a given token. + + } + placement="top" + > + + +
+ + )} + + {lendZoomLevel < 3 && ( + - -
-
-
+
+ Utilization + + + Pool utilization + + What percentage of supplied tokens have been borrowed. This helps determine interest + rates. This is not based on the global pool limits, which can limit utilization. + + } + placement="top" + > + + +
+ + )} - {/*******************************/} - {/* [START]: ZOOM-BASED COLUMNS */} - {/*******************************/} + {/*******************************/} + {/* [END]: ZOOM-BASED COLUMNS */} + {/*******************************/} - {lendZoomLevel < 2 && ( +
Wallet Amt.
+
+ + +
+
+ + + {sortedBanks + .filter((b) => !b.info.state.isIsolated) + .map((bank, i) => + isStoreInitialized ? ( + void) => { + setLSTDialogVariant(variant); + setIsLSTDialogOpen(true); + if (onClose) { + setLSTDialogCallback(() => onClose); + } + }} + /> + ) : ( + + ) + )} + +
+ )} + {poolFilter !== "global" && ( + + + + +
+ + Isolated pools + + + + Isolated pools are risky ⚠️ + + Assets in isolated pools cannot be used as collateral. When you borrow an isolated asset, + you cannot borrow other assets. Isolated pools should be considered particularly risky. As + always, remember that marginfi is a decentralized protocol and all deposited funds are at + risk. + + } + placement="top" + > + + +
+
+
- Global limit + Price - {isInLendingMode ? "Global deposit cap" : "Global borrow cap"} + Realtime prices - Each marginfi pool has global deposit and borrow limits, also known as caps. This is the - total amount that all users combined can deposit or borrow of a given token. + + Powered by Pyth and Switchboard. + } placement="top" @@ -309,24 +451,76 @@ const AssetsList: FC = () => {
- )} - - {lendZoomLevel < 3 && (
- Utilization + {isInLendingMode ? "APY" : "APR"} - Pool utilization + {isInLendingMode ? "APY" : "APR"} - What percentage of supplied tokens have been borrowed. This helps determine interest - rates. This is not based on the global pool limits, which can limit utilization. + + {isInLendingMode + ? "What you'll earn on deposits over a year. This includes compounding. All marginfi deposits are compounded hourly." + : "What you'll pay for your borrows, or the price of a loan. This does not include compounding. All marginfi borrows are compounded hourly."} + + + } + placement="top" + > + + +
+
+ +
+ {isInLendingMode ? "Weight" : "LTV"} + + + {isInLendingMode ? "Weight" : "LTV"} + + + {isInLendingMode + ? "How much your assets count for collateral, relative to their USD value. The higher the weight, the more collateral you can borrow against it." + : "How much you can borrow against your free collateral. The higher the LTV, the more you can borrow against your free collateral."} + + + } + placement="top" + > + + +
+
+ +
+ {isInLendingMode ? "Deposits" : "Available"} + + + {isInLendingMode ? "Total deposits" : "Total available"} + + + {isInLendingMode + ? "Total marginfi deposits for each asset. Everything is denominated in native tokens." + : "The amount of tokens available to borrow for each asset. Calculated as the minimum of the asset's borrow limit and available liquidity that has not yet been borrowed."} + } placement="top" @@ -335,109 +529,104 @@ const AssetsList: FC = () => {
- )} - {/*******************************/} - {/* [END]: ZOOM-BASED COLUMNS */} - {/*******************************/} + {/*******************************/} + {/* [START]: ZOOM-BASED COLUMNS */} + {/*******************************/} - -
Wallet Amt.
-
- - -
-
+ {lendZoomLevel < 2 && ( + +
+ Global limit + + + {isInLendingMode ? "Global deposit cap" : "Global borrow cap"} + + Each marginfi pool has global deposit and borrow limits, also known as caps. This is the + total amount that all users combined can deposit or borrow of a given token. + + } + placement="top" + > + + +
+
+ )} - - {sortedBanks - .filter((b) => !b.info.state.isIsolated) - .map((bank, i) => - isStoreInitialized ? ( - void) => { - setLSTDialogVariant(variant); - setIsLSTDialogOpen(true); - if (onClose) { - setLSTDialogCallback(() => onClose); - } - }} - /> - ) : ( - - ) - )} - - - - -
- - Isolated pools - - - - Isolated pools are risky ⚠️ - - Assets in isolated pools cannot be used as collateral. When you borrow an isolated asset, - you cannot borrow other assets. Isolated pools should be considered particularly risky. As - always, remember that marginfi is a decentralized protocol and all deposited funds are at - risk. - - } - placement="top" + {lendZoomLevel < 3 && ( + - - -
-
-
-
- - {sortedBanks - .filter((b) => b.info.state.isIsolated) - .map((bank) => - isStoreInitialized ? ( - - ) : ( - - ) - )} - -
+
+ Utilization + + + Pool utilization + + What percentage of supplied tokens have been borrowed. This helps determine interest + rates. This is not based on the global pool limits, which can limit utilization. + + } + placement="top" + > + info + +
+ + )} + + {/*******************************/} + {/* [END]: ZOOM-BASED COLUMNS */} + {/*******************************/} + + +
Wallet Amt.
+
+ + + + + + {sortedBanks + .filter((b) => b.info.state.isIsolated) + .map((bank) => + isStoreInitialized ? ( + + ) : ( + + ) + )} + + + )}
diff --git a/apps/marginfi-v2-ui/src/store/uiStore.ts b/apps/marginfi-v2-ui/src/store/uiStore.ts index a077673e31..4d284a3c92 100644 --- a/apps/marginfi-v2-ui/src/store/uiStore.ts +++ b/apps/marginfi-v2-ui/src/store/uiStore.ts @@ -1,3 +1,4 @@ +import { PoolTypes } from "~/types"; import { create, StateCreator } from "zustand"; interface UiState { @@ -7,6 +8,7 @@ interface UiState { isWalletAuthDialogOpen: boolean; isWalletOpen: boolean; isWalletOnrampActive: boolean; + poolFilter: PoolTypes; // Actions setIsMenuDrawerOpen: (isOpen: boolean) => void; @@ -14,6 +16,7 @@ interface UiState { setIsWalletAuthDialogOpen: (isOpen: boolean) => void; setIsWalletOpen: (isOpen: boolean) => void; setIsOnrampActive: (isOnrampActive: boolean) => void; + setPoolFilter: (poolType: PoolTypes) => void; } function createUiStore() { @@ -27,6 +30,7 @@ const stateCreator: StateCreator = (set, get) => ({ isWalletAuthDialogOpen: false, isWalletOpen: false, isWalletOnrampActive: false, + poolFilter: PoolTypes.ALL, // Actions setIsMenuDrawerOpen: (isOpen: boolean) => set({ isMenuDrawerOpen: isOpen }), @@ -34,6 +38,7 @@ const stateCreator: StateCreator = (set, get) => ({ setIsWalletAuthDialogOpen: (isOpen: boolean) => set({ isWalletAuthDialogOpen: isOpen }), setIsWalletOpen: (isOpen: boolean) => set({ isWalletOpen: isOpen }), setIsOnrampActive: (isOnrampActive: boolean) => set({ isWalletOnrampActive: isOnrampActive }), + setPoolFilter: (poolType: PoolTypes) => set({ poolFilter: poolType }), }); export { createUiStore }; diff --git a/apps/marginfi-v2-ui/src/types.ts b/apps/marginfi-v2-ui/src/types.ts index 275001bb7a..1fefb92782 100644 --- a/apps/marginfi-v2-ui/src/types.ts +++ b/apps/marginfi-v2-ui/src/types.ts @@ -78,3 +78,9 @@ export type MayanSwapInfo = { toToken: string; fromAmount: number; }; + +export enum PoolTypes { + ALL = "all", + GLOBAL = "global", + ISOLATED = "isolated", +} From 22a3106a844583951484dfff602ddf1eb1c63388 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Tue, 21 Nov 2023 13:37:19 -0500 Subject: [PATCH 21/93] feat: move lending mode to uiStore and add to new asset banner controls --- .../common/AssetList/NewAsssetBanner.tsx | 27 +++++++++++++++++-- .../desktop/AssetsList/AssetsList.tsx | 19 ++++++++----- apps/marginfi-v2-ui/src/store/uiStore.ts | 6 ++++- apps/marginfi-v2-ui/src/types.ts | 5 ++++ 4 files changed, 47 insertions(+), 10 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx b/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx index 8752e70a85..caa4ae4003 100644 --- a/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx +++ b/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx @@ -1,5 +1,10 @@ +import { LendingModes, PoolTypes } from "~/types"; + import React from "react"; import Image from "next/image"; + +import { useUiStore } from "~/store"; + import { Button } from "~/components/ui/button"; import { IconX } from "~/components/ui/icons"; @@ -9,6 +14,8 @@ type NewAssetBannerProps = { }; export const NewAssetBanner = ({ asset, image }: NewAssetBannerProps) => { + const [setLendingMode, setPoolFilter] = useUiStore((state) => [state.setLendingMode, state.setPoolFilter]); + const assetTicker = React.useMemo(() => "$" + asset.toUpperCase(), [asset]); return ( @@ -21,12 +28,28 @@ export const NewAssetBanner = ({ asset, image }: NewAssetBannerProps) => {

{assetTicker} is now available on margnfi

  • -
  • -
  • diff --git a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx index 2e27631d20..dabfa651a0 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx @@ -1,4 +1,4 @@ -import type { PoolTypes } from "~/types"; +import { LendingModes, PoolTypes } from "~/types"; import React, { FC, useEffect, useRef, useState } from "react"; import Image from "next/image"; @@ -30,14 +30,15 @@ const AssetsList: FC = () => { state.showBadges, state.setShowBadges, ]); - const [setIsWalletAuthDialogOpen, poolFilter, setPoolFilter] = useUiStore((state) => [ - state.setIsWalletAuthDialogOpen, + const [lendingMode, setLendingMode, poolFilter, setPoolFilter, setIsWalletAuthDialogOpen] = useUiStore((state) => [ + state.lendingMode, + state.setLendingMode, state.poolFilter, state.setPoolFilter, + state.setIsWalletAuthDialogOpen, ]); const inputRefs = useRef>({}); - const [isInLendingMode, setIsInLendingMode] = useState(true); const [isHotkeyMode, setIsHotkeyMode] = useState(false); const [isLSTDialogOpen, setIsLSTDialogOpen] = useState(false); const [lstDialogVariant, setLSTDialogVariant] = useState(null); @@ -45,6 +46,8 @@ const AssetsList: FC = () => { const [isFiltered, setIsFiltered] = useState(false); const togglePositions = () => setIsFiltered((previousState) => !previousState); + const isInLendingMode = React.useMemo(() => lendingMode === LendingModes.LEND, [lendingMode]); + // Enter hotkey mode useHotkeys( "meta + k", @@ -91,7 +94,7 @@ const AssetsList: FC = () => { "q", () => { if (isHotkeyMode) { - setIsInLendingMode((prevMode) => !prevMode); + setLendingMode(lendingMode === LendingModes.LEND ? LendingModes.BORROW : LendingModes.LEND); setIsHotkeyMode(false); setShowBadges(false); } @@ -116,8 +119,10 @@ const AssetsList: FC = () => { setIsInLendingMode(!isInLendingMode)} + checked={lendingMode === LendingModes.BORROW} + onClick={() => + setLendingMode(lendingMode === LendingModes.LEND ? LendingModes.BORROW : LendingModes.LEND) + } />
diff --git a/apps/marginfi-v2-ui/src/store/uiStore.ts b/apps/marginfi-v2-ui/src/store/uiStore.ts index 4d284a3c92..720160b3b6 100644 --- a/apps/marginfi-v2-ui/src/store/uiStore.ts +++ b/apps/marginfi-v2-ui/src/store/uiStore.ts @@ -1,4 +1,4 @@ -import { PoolTypes } from "~/types"; +import { LendingModes, PoolTypes } from "~/types"; import { create, StateCreator } from "zustand"; interface UiState { @@ -8,6 +8,7 @@ interface UiState { isWalletAuthDialogOpen: boolean; isWalletOpen: boolean; isWalletOnrampActive: boolean; + lendingMode: LendingModes; poolFilter: PoolTypes; // Actions @@ -16,6 +17,7 @@ interface UiState { setIsWalletAuthDialogOpen: (isOpen: boolean) => void; setIsWalletOpen: (isOpen: boolean) => void; setIsOnrampActive: (isOnrampActive: boolean) => void; + setLendingMode: (lendingMode: LendingModes) => void; setPoolFilter: (poolType: PoolTypes) => void; } @@ -30,6 +32,7 @@ const stateCreator: StateCreator = (set, get) => ({ isWalletAuthDialogOpen: false, isWalletOpen: false, isWalletOnrampActive: false, + lendingMode: LendingModes.LEND, poolFilter: PoolTypes.ALL, // Actions @@ -38,6 +41,7 @@ const stateCreator: StateCreator = (set, get) => ({ setIsWalletAuthDialogOpen: (isOpen: boolean) => set({ isWalletAuthDialogOpen: isOpen }), setIsWalletOpen: (isOpen: boolean) => set({ isWalletOpen: isOpen }), setIsOnrampActive: (isOnrampActive: boolean) => set({ isWalletOnrampActive: isOnrampActive }), + setLendingMode: (lendingMode: LendingModes) => set({ lendingMode: lendingMode }), setPoolFilter: (poolType: PoolTypes) => set({ poolFilter: poolType }), }); diff --git a/apps/marginfi-v2-ui/src/types.ts b/apps/marginfi-v2-ui/src/types.ts index 1fefb92782..d75c4ee103 100644 --- a/apps/marginfi-v2-ui/src/types.ts +++ b/apps/marginfi-v2-ui/src/types.ts @@ -79,6 +79,11 @@ export type MayanSwapInfo = { fromAmount: number; }; +export enum LendingModes { + LEND = "lend", + BORROW = "borrow", +} + export enum PoolTypes { ALL = "all", GLOBAL = "global", From 399cf5825ba055634e50378a34e07d744773c055 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Tue, 21 Nov 2023 14:04:28 -0500 Subject: [PATCH 22/93] feat: highlight asset in new asset banner controls --- .../common/AssetList/NewAsssetBanner.tsx | 51 ++++++++++++------- .../desktop/AssetsList/AssetRow/AssetRow.tsx | 5 +- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx b/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx index caa4ae4003..3eeaf3dc2b 100644 --- a/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx +++ b/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx @@ -18,6 +18,37 @@ export const NewAssetBanner = ({ asset, image }: NewAssetBannerProps) => { const assetTicker = React.useMemo(() => "$" + asset.toUpperCase(), [asset]); + const highlightAsset = React.useCallback(() => { + if (!document) return; + const assetRows = document.querySelectorAll("[data-asset-row]"); + const assetRow = document.querySelector(`[data-asset-row="${asset}"]`); + console.log(asset, assetRow); + if (!assetRow) return; + + assetRows.forEach((row) => row.classList.add("opacity-30", "hover:bg-[#171C1F]")); + assetRow.scrollIntoView({ behavior: "smooth", block: "center" }); + assetRow.classList.remove("opacity-30"); + assetRow.classList.add("duration-500", "opacity-100"); + + setTimeout(() => { + assetRow.classList.remove("duration-150"); + }, 1000); + + setTimeout(() => { + assetRows.forEach((row) => row.classList.remove("opacity-30", "opacity-100", "hover:bg-[#171C1F]")); + }, 3000); + }, [asset]); + + const deposit = React.useCallback(() => { + setLendingMode(LendingModes.LEND); + setTimeout(() => highlightAsset(), 100); + }, [setLendingMode, setPoolFilter, asset]); + + const borrow = React.useCallback(() => { + setLendingMode(LendingModes.BORROW); + setTimeout(() => highlightAsset(), 100); + }, [setLendingMode, setPoolFilter, asset]); + return (
@@ -28,28 +59,12 @@ export const NewAssetBanner = ({ asset, image }: NewAssetBannerProps) => {

{assetTicker} is now available on margnfi

  • -
  • -
  • diff --git a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx index 707ad0533a..37992eef1c 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx @@ -207,7 +207,10 @@ const AssetRow: FC<{ ]); return ( - + Date: Tue, 21 Nov 2023 15:00:33 -0500 Subject: [PATCH 23/93] feat: filter user positions --- .../common/AssetList/NewAsssetBanner.tsx | 25 +- .../desktop/AssetsList/AssetsList.tsx | 1016 +++++++++-------- apps/marginfi-v2-ui/src/pages/index.tsx | 5 - apps/marginfi-v2-ui/src/store/uiStore.ts | 8 +- 4 files changed, 539 insertions(+), 515 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx b/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx index 3eeaf3dc2b..d26a57c875 100644 --- a/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx +++ b/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx @@ -1,5 +1,3 @@ -import { LendingModes, PoolTypes } from "~/types"; - import React from "react"; import Image from "next/image"; @@ -8,13 +6,20 @@ import { useUiStore } from "~/store"; import { Button } from "~/components/ui/button"; import { IconX } from "~/components/ui/icons"; +import { LendingModes, PoolTypes } from "~/types"; + type NewAssetBannerProps = { asset: string; image: string; }; export const NewAssetBanner = ({ asset, image }: NewAssetBannerProps) => { - const [setLendingMode, setPoolFilter] = useUiStore((state) => [state.setLendingMode, state.setPoolFilter]); + const [poolFilter, setLendingMode, setPoolFilter, setIsFilteredUserPositions] = useUiStore((state) => [ + state.poolFilter, + state.setLendingMode, + state.setPoolFilter, + state.setIsFilteredUserPositions, + ]); const assetTicker = React.useMemo(() => "$" + asset.toUpperCase(), [asset]); @@ -28,24 +33,24 @@ export const NewAssetBanner = ({ asset, image }: NewAssetBannerProps) => { assetRows.forEach((row) => row.classList.add("opacity-30", "hover:bg-[#171C1F]")); assetRow.scrollIntoView({ behavior: "smooth", block: "center" }); assetRow.classList.remove("opacity-30"); - assetRow.classList.add("duration-500", "opacity-100"); - - setTimeout(() => { - assetRow.classList.remove("duration-150"); - }, 1000); + assetRow.classList.add("animate-pulse"); setTimeout(() => { - assetRows.forEach((row) => row.classList.remove("opacity-30", "opacity-100", "hover:bg-[#171C1F]")); - }, 3000); + assetRows.forEach((row) => row.classList.remove("opacity-30", "animate-pulse", "hover:bg-[#171C1F]")); + }, 2500); }, [asset]); const deposit = React.useCallback(() => { setLendingMode(LendingModes.LEND); + if (poolFilter === PoolTypes.GLOBAL) setPoolFilter(PoolTypes.ALL); + setIsFilteredUserPositions(false); setTimeout(() => highlightAsset(), 100); }, [setLendingMode, setPoolFilter, asset]); const borrow = React.useCallback(() => { setLendingMode(LendingModes.BORROW); + if (poolFilter === PoolTypes.GLOBAL) setPoolFilter(PoolTypes.ALL); + setIsFilteredUserPositions(false); setTimeout(() => highlightAsset(), 100); }, [setLendingMode, setPoolFilter, asset]); diff --git a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx index dabfa651a0..bfc8d90532 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx @@ -1,6 +1,5 @@ -import { LendingModes, PoolTypes } from "~/types"; - import React, { FC, useEffect, useRef, useState } from "react"; +import dynamic from "next/dynamic"; import Image from "next/image"; import { useHotkeys } from "react-hotkeys-hook"; import { Card, Table, TableHead, TableBody, TableContainer, TableCell, TableRow } from "@mui/material"; @@ -16,9 +15,15 @@ import { MrgnTooltip, MrgnLabeledSwitch, MrgnContainedSwitch } from "~/component import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select"; +import { LendingModes, PoolTypes } from "~/types"; + +const UserPositions = dynamic(async () => (await import("~/components/desktop/UserPositions")).UserPositions, { + ssr: false, +}); + const AssetsList: FC = () => { // const { selectedAccount, nativeSolBalance } = useStore(); - const { connected } = useWalletContext(); + const { connected, walletAddress } = useWalletContext(); const [isStoreInitialized, sortedBanks, nativeSolBalance, selectedAccount] = useMrgnlendStore((state) => [ state.initialized, state.extendedBankInfos, @@ -30,11 +35,21 @@ const AssetsList: FC = () => { state.showBadges, state.setShowBadges, ]); - const [lendingMode, setLendingMode, poolFilter, setPoolFilter, setIsWalletAuthDialogOpen] = useUiStore((state) => [ + const [ + lendingMode, + setLendingMode, + poolFilter, + setPoolFilter, + isFilteredUserPositions, + setIsFilteredUserPositions, + setIsWalletAuthDialogOpen, + ] = useUiStore((state) => [ state.lendingMode, state.setLendingMode, state.poolFilter, state.setPoolFilter, + state.isFilteredUserPositions, + state.setIsFilteredUserPositions, state.setIsWalletAuthDialogOpen, ]); @@ -43,8 +58,6 @@ const AssetsList: FC = () => { const [isLSTDialogOpen, setIsLSTDialogOpen] = useState(false); const [lstDialogVariant, setLSTDialogVariant] = useState(null); const [lstDialogCallback, setLSTDialogCallback] = useState<(() => void) | null>(null); - const [isFiltered, setIsFiltered] = useState(false); - const togglePositions = () => setIsFiltered((previousState) => !previousState); const isInLendingMode = React.useMemo(() => lendingMode === LendingModes.LEND, [lendingMode]); @@ -113,528 +126,533 @@ const AssetsList: FC = () => { return ( <> -
    -
    -
    - - setLendingMode(lendingMode === LendingModes.LEND ? LendingModes.BORROW : LendingModes.LEND) - } - /> + <> +
    +
    +
    + + setLendingMode(lendingMode === LendingModes.LEND ? LendingModes.BORROW : LendingModes.LEND) + } + /> +
    +
    + Filter pools + +
    -
    - Filter pools - + +
    { + e.stopPropagation(); + if (connected) return; + setIsWalletAuthDialogOpen(true); + }} + > + setIsFilteredUserPositions(!isFilteredUserPositions)} + inputProps={{ "aria-label": "controlled" }} + className={cn(!connected && "pointer-events-none")} + /> +
    Filter my positions
    -
    -
    { - e.stopPropagation(); - if (connected) return; - setIsWalletAuthDialogOpen(true); - }} - > - -
    Filter my positions
    - -
    - -
    - - - {poolFilter !== "isolated" && ( - - - - -
    - Global pool -
    -
    - -
    - Price - - - Realtime prices - - - Powered by Pyth and Switchboard. - - - } - placement="top" + {!isFilteredUserPositions && ( +
    + + + {poolFilter !== "isolated" && ( +
    + + + +
    + Global pool +
    +
    + - - - - - -
    - {isInLendingMode ? "APY" : "APR"} - - - {isInLendingMode ? "APY" : "APR"} - - - {isInLendingMode - ? "What you'll earn on deposits over a year. This includes compounding. All marginfi deposits are compounded hourly." - : "What you'll pay for your borrows, or the price of a loan. This does not include compounding. All marginfi borrows are compounded hourly."} - - - } - placement="top" +
    + Price + + + Realtime prices + + + Powered by Pyth and Switchboard. + + + } + placement="top" + > + + +
    + + - -
    -
    -
    - -
    - {isInLendingMode ? "Weight" : "LTV"} - - - {isInLendingMode ? "Weight" : "LTV"} - - - {isInLendingMode - ? "How much your assets count for collateral, relative to their USD value. The higher the weight, the more collateral you can borrow against it." - : "How much you can borrow against your free collateral. The higher the LTV, the more you can borrow against your free collateral."} - - - } - placement="top" +
    + {isInLendingMode ? "APY" : "APR"} + + + {isInLendingMode ? "APY" : "APR"} + + + {isInLendingMode + ? "What you'll earn on deposits over a year. This includes compounding. All marginfi deposits are compounded hourly." + : "What you'll pay for your borrows, or the price of a loan. This does not include compounding. All marginfi borrows are compounded hourly."} + + + } + placement="top" + > + + +
    + + - -
    -
    -
    - -
    - {isInLendingMode ? "Deposits" : "Available"} - - - {isInLendingMode ? "Total deposits" : "Total available"} - - - {isInLendingMode - ? "Total marginfi deposits for each asset. Everything is denominated in native tokens." - : "The amount of tokens available to borrow for each asset. Calculated as the minimum of the asset's borrow limit and available liquidity that has not yet been borrowed."} - - - } - placement="top" +
    + {isInLendingMode ? "Weight" : "LTV"} + + + {isInLendingMode ? "Weight" : "LTV"} + + + {isInLendingMode + ? "How much your assets count for collateral, relative to their USD value. The higher the weight, the more collateral you can borrow against it." + : "How much you can borrow against your free collateral. The higher the LTV, the more you can borrow against your free collateral."} + + + } + placement="top" + > + + +
    + + - -
    -
    -
    +
    + {isInLendingMode ? "Deposits" : "Available"} + + + {isInLendingMode ? "Total deposits" : "Total available"} + + + {isInLendingMode + ? "Total marginfi deposits for each asset. Everything is denominated in native tokens." + : "The amount of tokens available to borrow for each asset. Calculated as the minimum of the asset's borrow limit and available liquidity that has not yet been borrowed."} + + + } + placement="top" + > + + +
    + - {/*******************************/} - {/* [START]: ZOOM-BASED COLUMNS */} - {/*******************************/} + {/*******************************/} + {/* [START]: ZOOM-BASED COLUMNS */} + {/*******************************/} - {lendZoomLevel < 2 && ( - -
    - Global limit - - - {isInLendingMode ? "Global deposit cap" : "Global borrow cap"} - - Each marginfi pool has global deposit and borrow limits, also known as caps. This is the - total amount that all users combined can deposit or borrow of a given token. - - } - placement="top" + {lendZoomLevel < 2 && ( + - - -
    -
    - )} +
    + Global limit + + + {isInLendingMode ? "Global deposit cap" : "Global borrow cap"} + + Each marginfi pool has global deposit and borrow limits, also known as caps. This is + the total amount that all users combined can deposit or borrow of a given token. + + } + placement="top" + > + + +
    + + )} - {lendZoomLevel < 3 && ( - -
    - Utilization - - - Pool utilization - - What percentage of supplied tokens have been borrowed. This helps determine interest - rates. This is not based on the global pool limits, which can limit utilization. - - } - placement="top" + {lendZoomLevel < 3 && ( + - - -
    -
    - )} +
    + Utilization + + + Pool utilization + + What percentage of supplied tokens have been borrowed. This helps determine interest + rates. This is not based on the global pool limits, which can limit utilization. + + } + placement="top" + > + + +
    + + )} - {/*******************************/} - {/* [END]: ZOOM-BASED COLUMNS */} - {/*******************************/} + {/*******************************/} + {/* [END]: ZOOM-BASED COLUMNS */} + {/*******************************/} - -
    Wallet Amt.
    -
    - - -
    -
    - - - {sortedBanks - .filter((b) => !b.info.state.isIsolated) - .map((bank, i) => - isStoreInitialized ? ( - void) => { - setLSTDialogVariant(variant); - setIsLSTDialogOpen(true); - if (onClose) { - setLSTDialogCallback(() => onClose); - } - }} - /> - ) : ( - - ) - )} - -
    - )} - {poolFilter !== "global" && ( - - - - -
    - - Isolated pools - - - - Isolated pools are risky ⚠️ - - Assets in isolated pools cannot be used as collateral. When you borrow an isolated asset, - you cannot borrow other assets. Isolated pools should be considered particularly risky. As - always, remember that marginfi is a decentralized protocol and all deposited funds are at - risk. - - } - placement="top" + - - -
    -
    - -
    - Price - - - Realtime prices - - - Powered by Pyth and Switchboard. - - - } - placement="top" +
    Wallet Amt.
    + + + + + + + + {sortedBanks + .filter((b) => !b.info.state.isIsolated) + .map((bank, i) => + isStoreInitialized ? ( + void) => { + setLSTDialogVariant(variant); + setIsLSTDialogOpen(true); + if (onClose) { + setLSTDialogCallback(() => onClose); + } + }} + /> + ) : ( + + ) + )} + +
    + )} + {poolFilter !== "global" && ( + + + + +
    + + Isolated pools + + + + Isolated pools are risky ⚠️ + + Assets in isolated pools cannot be used as collateral. When you borrow an isolated + asset, you cannot borrow other assets. Isolated pools should be considered + particularly risky. As always, remember that marginfi is a decentralized protocol and + all deposited funds are at risk. + + } + placement="top" + > + + +
    +
    + - - - - - -
    - {isInLendingMode ? "APY" : "APR"} - - - {isInLendingMode ? "APY" : "APR"} - - - {isInLendingMode - ? "What you'll earn on deposits over a year. This includes compounding. All marginfi deposits are compounded hourly." - : "What you'll pay for your borrows, or the price of a loan. This does not include compounding. All marginfi borrows are compounded hourly."} - - - } - placement="top" +
    + Price + + + Realtime prices + + + Powered by Pyth and Switchboard. + + + } + placement="top" + > + + +
    + + - -
    -
    -
    - -
    - {isInLendingMode ? "Weight" : "LTV"} - - - {isInLendingMode ? "Weight" : "LTV"} - - - {isInLendingMode - ? "How much your assets count for collateral, relative to their USD value. The higher the weight, the more collateral you can borrow against it." - : "How much you can borrow against your free collateral. The higher the LTV, the more you can borrow against your free collateral."} - - - } - placement="top" +
    + {isInLendingMode ? "APY" : "APR"} + + + {isInLendingMode ? "APY" : "APR"} + + + {isInLendingMode + ? "What you'll earn on deposits over a year. This includes compounding. All marginfi deposits are compounded hourly." + : "What you'll pay for your borrows, or the price of a loan. This does not include compounding. All marginfi borrows are compounded hourly."} + + + } + placement="top" + > + + +
    + + - -
    -
    -
    - -
    - {isInLendingMode ? "Deposits" : "Available"} - - - {isInLendingMode ? "Total deposits" : "Total available"} - - - {isInLendingMode - ? "Total marginfi deposits for each asset. Everything is denominated in native tokens." - : "The amount of tokens available to borrow for each asset. Calculated as the minimum of the asset's borrow limit and available liquidity that has not yet been borrowed."} - - - } - placement="top" +
    + {isInLendingMode ? "Weight" : "LTV"} + + + {isInLendingMode ? "Weight" : "LTV"} + + + {isInLendingMode + ? "How much your assets count for collateral, relative to their USD value. The higher the weight, the more collateral you can borrow against it." + : "How much you can borrow against your free collateral. The higher the LTV, the more you can borrow against your free collateral."} + + + } + placement="top" + > + + +
    + + - -
    -
    -
    +
    + {isInLendingMode ? "Deposits" : "Available"} + + + {isInLendingMode ? "Total deposits" : "Total available"} + + + {isInLendingMode + ? "Total marginfi deposits for each asset. Everything is denominated in native tokens." + : "The amount of tokens available to borrow for each asset. Calculated as the minimum of the asset's borrow limit and available liquidity that has not yet been borrowed."} + + + } + placement="top" + > + + +
    + - {/*******************************/} - {/* [START]: ZOOM-BASED COLUMNS */} - {/*******************************/} + {/*******************************/} + {/* [START]: ZOOM-BASED COLUMNS */} + {/*******************************/} - {lendZoomLevel < 2 && ( - -
    - Global limit - - - {isInLendingMode ? "Global deposit cap" : "Global borrow cap"} - - Each marginfi pool has global deposit and borrow limits, also known as caps. This is the - total amount that all users combined can deposit or borrow of a given token. - - } - placement="top" + {lendZoomLevel < 2 && ( + - - -
    -
    - )} +
    + Global limit + + + {isInLendingMode ? "Global deposit cap" : "Global borrow cap"} + + Each marginfi pool has global deposit and borrow limits, also known as caps. This is + the total amount that all users combined can deposit or borrow of a given token. + + } + placement="top" + > + + +
    + + )} - {lendZoomLevel < 3 && ( - -
    - Utilization - - - Pool utilization - - What percentage of supplied tokens have been borrowed. This helps determine interest - rates. This is not based on the global pool limits, which can limit utilization. - - } - placement="top" + {lendZoomLevel < 3 && ( + - - -
    -
    - )} +
    + Utilization + + + Pool utilization + + What percentage of supplied tokens have been borrowed. This helps determine interest + rates. This is not based on the global pool limits, which can limit utilization. + + } + placement="top" + > + + +
    + + )} - {/*******************************/} - {/* [END]: ZOOM-BASED COLUMNS */} - {/*******************************/} + {/*******************************/} + {/* [END]: ZOOM-BASED COLUMNS */} + {/*******************************/} - -
    Wallet Amt.
    -
    - - -
    -
    - - {sortedBanks - .filter((b) => b.info.state.isIsolated) - .map((bank) => - isStoreInitialized ? ( - - ) : ( - - ) - )} - -
    - )} -
    -
    -
    + +
    Wallet Amt.
    +
    + + + + + + {sortedBanks + .filter((b) => b.info.state.isIsolated) + .map((bank) => + isStoreInitialized ? ( + + ) : ( + + ) + )} + + + )} + + +
    + )} + {walletAddress && } + (await import("~/components/desktop/AssetsList")).AssetsList, { ssr: false }); -const UserPositions = dynamic(async () => (await import("~/components/desktop/UserPositions")).UserPositions, { - ssr: false, -}); - const MobileAssetsList = dynamic(async () => (await import("~/components/mobile/MobileAssetsList")).MobileAssetsList, { ssr: false, }); @@ -109,7 +105,6 @@ const Home = () => {
    - {walletAddress && }
    diff --git a/apps/marginfi-v2-ui/src/store/uiStore.ts b/apps/marginfi-v2-ui/src/store/uiStore.ts index 720160b3b6..d87c68036e 100644 --- a/apps/marginfi-v2-ui/src/store/uiStore.ts +++ b/apps/marginfi-v2-ui/src/store/uiStore.ts @@ -1,6 +1,7 @@ -import { LendingModes, PoolTypes } from "~/types"; import { create, StateCreator } from "zustand"; +import { LendingModes, PoolTypes } from "~/types"; + interface UiState { // State isMenuDrawerOpen: boolean; @@ -8,6 +9,7 @@ interface UiState { isWalletAuthDialogOpen: boolean; isWalletOpen: boolean; isWalletOnrampActive: boolean; + isFilteredUserPositions: boolean; lendingMode: LendingModes; poolFilter: PoolTypes; @@ -17,6 +19,7 @@ interface UiState { setIsWalletAuthDialogOpen: (isOpen: boolean) => void; setIsWalletOpen: (isOpen: boolean) => void; setIsOnrampActive: (isOnrampActive: boolean) => void; + setIsFilteredUserPositions: (isFilteredUserPositions: boolean) => void; setLendingMode: (lendingMode: LendingModes) => void; setPoolFilter: (poolType: PoolTypes) => void; } @@ -32,6 +35,7 @@ const stateCreator: StateCreator = (set, get) => ({ isWalletAuthDialogOpen: false, isWalletOpen: false, isWalletOnrampActive: false, + isFilteredUserPositions: false, lendingMode: LendingModes.LEND, poolFilter: PoolTypes.ALL, @@ -41,6 +45,8 @@ const stateCreator: StateCreator = (set, get) => ({ setIsWalletAuthDialogOpen: (isOpen: boolean) => set({ isWalletAuthDialogOpen: isOpen }), setIsWalletOpen: (isOpen: boolean) => set({ isWalletOpen: isOpen }), setIsOnrampActive: (isOnrampActive: boolean) => set({ isWalletOnrampActive: isOnrampActive }), + setIsFilteredUserPositions: (isFilteredUserPositions: boolean) => + set({ isFilteredUserPositions: isFilteredUserPositions }), setLendingMode: (lendingMode: LendingModes) => set({ lendingMode: lendingMode }), setPoolFilter: (poolType: PoolTypes) => set({ poolFilter: poolType }), }); From 810c07e23e24fc5af247728decbb010719e07154 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Tue, 21 Nov 2023 15:04:26 -0500 Subject: [PATCH 24/93] chore: remove global pool option from pool type filter --- .../desktop/AssetsList/AssetsList.tsx | 393 +++++++++--------- apps/marginfi-v2-ui/src/types.ts | 1 - 2 files changed, 189 insertions(+), 205 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx index bfc8d90532..3868e41e03 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx @@ -10,7 +10,7 @@ import { useWalletContext } from "~/hooks/useWalletContext"; import { cn } from "~/utils"; import { LoadingAsset, AssetRow } from "./AssetRow"; -import { LSTDialog, LSTDialogVariants, NewAssetBannerList } from "~/components/common/AssetList"; +import { LSTDialog, LSTDialogVariants, NewAssetBanner, NewAssetBannerList } from "~/components/common/AssetList"; import { MrgnTooltip, MrgnLabeledSwitch, MrgnContainedSwitch } from "~/components/common"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select"; @@ -152,7 +152,6 @@ const AssetsList: FC = () => { All pools - Global pool Isolated pools @@ -176,19 +175,7 @@ const AssetsList: FC = () => {
    Filter my positions
    - +
{!isFilteredUserPositions && ( @@ -420,101 +407,155 @@ const AssetsList: FC = () => { )} - {poolFilter !== "global" && ( - - - - -
- - Isolated pools - - - - Isolated pools are risky ⚠️ - - Assets in isolated pools cannot be used as collateral. When you borrow an isolated - asset, you cannot borrow other assets. Isolated pools should be considered - particularly risky. As always, remember that marginfi is a decentralized protocol and - all deposited funds are at risk. - - } - placement="top" - > - - -
-
- -
- Price - - - Realtime prices - - - Powered by Pyth and Switchboard. - - - } - placement="top" - > - - -
-
- -
- {isInLendingMode ? "APY" : "APR"} - - - {isInLendingMode ? "APY" : "APR"} - - - {isInLendingMode - ? "What you'll earn on deposits over a year. This includes compounding. All marginfi deposits are compounded hourly." - : "What you'll pay for your borrows, or the price of a loan. This does not include compounding. All marginfi borrows are compounded hourly."} - - - } - placement="top" - > - - -
-
+
+ + + +
+ + Isolated pools + + + + Isolated pools are risky ⚠️ + + Assets in isolated pools cannot be used as collateral. When you borrow an isolated + asset, you cannot borrow other assets. Isolated pools should be considered particularly + risky. As always, remember that marginfi is a decentralized protocol and all deposited + funds are at risk. + + } + placement="top" + > + + +
+
+ +
+ Price + + + Realtime prices + + + Powered by Pyth and Switchboard. + + + } + placement="top" + > + + +
+
+ +
+ {isInLendingMode ? "APY" : "APR"} + + + {isInLendingMode ? "APY" : "APR"} + + + {isInLendingMode + ? "What you'll earn on deposits over a year. This includes compounding. All marginfi deposits are compounded hourly." + : "What you'll pay for your borrows, or the price of a loan. This does not include compounding. All marginfi borrows are compounded hourly."} + + + } + placement="top" + > + + +
+
+ +
+ {isInLendingMode ? "Weight" : "LTV"} + + + {isInLendingMode ? "Weight" : "LTV"} + + + {isInLendingMode + ? "How much your assets count for collateral, relative to their USD value. The higher the weight, the more collateral you can borrow against it." + : "How much you can borrow against your free collateral. The higher the LTV, the more you can borrow against your free collateral."} + + + } + placement="top" + > + + +
+
+ +
+ {isInLendingMode ? "Deposits" : "Available"} + + + {isInLendingMode ? "Total deposits" : "Total available"} + + + {isInLendingMode + ? "Total marginfi deposits for each asset. Everything is denominated in native tokens." + : "The amount of tokens available to borrow for each asset. Calculated as the minimum of the asset's borrow limit and available liquidity that has not yet been borrowed."} + + + } + placement="top" + > + + +
+
+ + {/*******************************/} + {/* [START]: ZOOM-BASED COLUMNS */} + {/*******************************/} + + {lendZoomLevel < 2 && (
- {isInLendingMode ? "Weight" : "LTV"} + Global limit - {isInLendingMode ? "Weight" : "LTV"} + {isInLendingMode ? "Global deposit cap" : "Global borrow cap"} - - {isInLendingMode - ? "How much your assets count for collateral, relative to their USD value. The higher the weight, the more collateral you can borrow against it." - : "How much you can borrow against your free collateral. The higher the LTV, the more you can borrow against your free collateral."} - + Each marginfi pool has global deposit and borrow limits, also known as caps. This is + the total amount that all users combined can deposit or borrow of a given token. } placement="top" @@ -523,24 +564,24 @@ const AssetsList: FC = () => {
+ )} + + {lendZoomLevel < 3 && (
- {isInLendingMode ? "Deposits" : "Available"} + Utilization - {isInLendingMode ? "Total deposits" : "Total available"} + Pool utilization - - {isInLendingMode - ? "Total marginfi deposits for each asset. Everything is denominated in native tokens." - : "The amount of tokens available to borrow for each asset. Calculated as the minimum of the asset's borrow limit and available liquidity that has not yet been borrowed."} - + What percentage of supplied tokens have been borrowed. This helps determine interest + rates. This is not based on the global pool limits, which can limit utilization. } placement="top" @@ -549,104 +590,48 @@ const AssetsList: FC = () => {
+ )} - {/*******************************/} - {/* [START]: ZOOM-BASED COLUMNS */} - {/*******************************/} - - {lendZoomLevel < 2 && ( - -
- Global limit - - - {isInLendingMode ? "Global deposit cap" : "Global borrow cap"} - - Each marginfi pool has global deposit and borrow limits, also known as caps. This is - the total amount that all users combined can deposit or borrow of a given token. - - } - placement="top" - > - - -
-
- )} + {/*******************************/} + {/* [END]: ZOOM-BASED COLUMNS */} + {/*******************************/} - {lendZoomLevel < 3 && ( - -
- Utilization - - - Pool utilization - - What percentage of supplied tokens have been borrowed. This helps determine interest - rates. This is not based on the global pool limits, which can limit utilization. - - } - placement="top" - > - - -
-
- )} - - {/*******************************/} - {/* [END]: ZOOM-BASED COLUMNS */} - {/*******************************/} - - -
Wallet Amt.
-
- - -
-
- - {sortedBanks - .filter((b) => b.info.state.isIsolated) - .map((bank) => - isStoreInitialized ? ( - - ) : ( - - ) - )} - -
- )} + +
Wallet Amt.
+
+ + + + + + {sortedBanks + .filter((b) => b.info.state.isIsolated) + .map((bank) => + isStoreInitialized ? ( + + ) : ( + + ) + )} + +
diff --git a/apps/marginfi-v2-ui/src/types.ts b/apps/marginfi-v2-ui/src/types.ts index d75c4ee103..dede46fe73 100644 --- a/apps/marginfi-v2-ui/src/types.ts +++ b/apps/marginfi-v2-ui/src/types.ts @@ -86,6 +86,5 @@ export enum LendingModes { export enum PoolTypes { ALL = "all", - GLOBAL = "global", ISOLATED = "isolated", } From 41b7178ee300298853c755f648466b9128ffba01 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Fri, 24 Nov 2023 11:13:03 -0500 Subject: [PATCH 25/93] feat: share asset filters between mobile / desktop --- .../common/AssetList/AssetListFilters.tsx | 112 +++++++ .../AssetList/AssetListFilters.utils.tsx} | 48 +-- .../src/components/common/AssetList/index.ts | 2 + .../desktop/AssetsList/AssetsList.tsx | 127 ++++---- .../MobileAssetsList/MobileAssetsList.tsx | 296 ++++++++---------- .../src/components/ui/icons.tsx | 4 + apps/marginfi-v2-ui/src/store/index.ts | 4 +- apps/marginfi-v2-ui/src/store/uiStore.ts | 37 ++- apps/marginfi-v2-ui/src/types.ts | 20 ++ 9 files changed, 360 insertions(+), 290 deletions(-) create mode 100644 apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.tsx rename apps/marginfi-v2-ui/src/components/{mobile/MobileAssetsList/MobileAssetsList.utils.ts => common/AssetList/AssetListFilters.utils.tsx} (59%) diff --git a/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.tsx b/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.tsx new file mode 100644 index 0000000000..aecc017b72 --- /dev/null +++ b/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.tsx @@ -0,0 +1,112 @@ +import React from "react"; + +import { useUiStore, SORT_OPTIONS_MAP } from "~/store"; +import { cn } from "~/utils"; +import { useWalletContext } from "~/hooks/useWalletContext"; + +import { MrgnLabeledSwitch } from "~/components/common/MrgnLabeledSwitch"; +import { MrgnContainedSwitch } from "~/components/common/MrgnContainedSwitch"; +import { NewAssetBanner } from "~/components/common/AssetList"; + +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select"; + +import { LendingModes, PoolTypes, SortType, SortAssetOption } from "~/types"; + +export const AssetListFilters = () => { + const { connected } = useWalletContext(); + const [ + lendingMode, + setLendingMode, + poolFilter, + setPoolFilter, + isFilteredUserPositions, + setIsFilteredUserPositions, + setIsWalletAuthDialogOpen, + sortOption, + setSortOption, + ] = useUiStore((state) => [ + state.lendingMode, + state.setLendingMode, + state.poolFilter, + state.setPoolFilter, + state.isFilteredUserPositions, + state.setIsFilteredUserPositions, + state.setIsWalletAuthDialogOpen, + state.sortOption, + state.setSortOption, + ]); + + return ( +
+
+
+ setLendingMode(lendingMode === LendingModes.LEND ? LendingModes.BORROW : LendingModes.LEND)} + /> +
+
+ Filter + +
+
+ Sort + +
+
+ +
{ + e.stopPropagation(); + if (connected) return; + setIsWalletAuthDialogOpen(true); + }} + > + setIsFilteredUserPositions(!isFilteredUserPositions)} + inputProps={{ "aria-label": "controlled" }} + className={cn(!connected && "pointer-events-none")} + /> +
Filter my positions
+
+ + +
+ ); +}; diff --git a/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/MobileAssetsList.utils.ts b/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.utils.tsx similarity index 59% rename from apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/MobileAssetsList.utils.ts rename to apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.utils.tsx index 969ab18af1..696af1bbb1 100644 --- a/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/MobileAssetsList.utils.ts +++ b/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.utils.tsx @@ -1,51 +1,5 @@ import { ExtendedBankInfo, Emissions } from "@mrgnlabs/marginfi-v2-ui-state"; -import { ArrowDownward, ArrowUpward } from "@mui/icons-material"; - -export type SortAssetOption = { - label: string; - borrowLabel?: string; - Icon: any; - value: SortType; - field: "APY" | "TVL"; - direction: sortDirection; -}; - -type sortDirection = "ASC" | "DESC"; - -export type SortType = "APY_ASC" | "APY_DESC" | "TVL_ASC" | "TVL_DESC"; - -export const SORT_OPTIONS_MAP: { [key in SortType]: SortAssetOption } = { - APY_DESC: { - label: "Apy", - borrowLabel: "Apr", - Icon: ArrowDownward, - value: "APY_DESC", - field: "APY", - direction: "DESC", - }, - APY_ASC: { - label: "Apy", - borrowLabel: "Apr", - Icon: ArrowUpward, - value: "APY_ASC", - field: "APY", - direction: "ASC", - }, - TVL_DESC: { - label: "Tvl", - Icon: ArrowDownward, - value: "TVL_DESC", - field: "TVL", - direction: "DESC", - }, - TVL_ASC: { - label: "Tvl", - Icon: ArrowUpward, - value: "TVL_ASC", - field: "TVL", - direction: "ASC", - }, -}; +import type { sortDirection } from "~/types"; export const sortApRate = (banks: ExtendedBankInfo[], isInLendingMode: boolean, direction: sortDirection) => { return banks.sort((a, b) => { diff --git a/apps/marginfi-v2-ui/src/components/common/AssetList/index.ts b/apps/marginfi-v2-ui/src/components/common/AssetList/index.ts index 28b9487ae6..62364d013c 100644 --- a/apps/marginfi-v2-ui/src/components/common/AssetList/index.ts +++ b/apps/marginfi-v2-ui/src/components/common/AssetList/index.ts @@ -2,3 +2,5 @@ export * from "./AssetRowAction"; export * from "./AssetRowInputBox"; export * from "./LSTDialog"; export * from "./NewAsssetBanner"; +export * from "./AssetListFilters"; +export * from "./AssetListFilters.utils"; diff --git a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx index 3868e41e03..4cf9542191 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx @@ -2,20 +2,18 @@ import React, { FC, useEffect, useRef, useState } from "react"; import dynamic from "next/dynamic"; import Image from "next/image"; import { useHotkeys } from "react-hotkeys-hook"; +import { ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; import { Card, Table, TableHead, TableBody, TableContainer, TableCell, TableRow } from "@mui/material"; import Typography from "@mui/material/Typography"; import { useMrgnlendStore, useUserProfileStore, useUiStore } from "~/store"; import { useWalletContext } from "~/hooks/useWalletContext"; -import { cn } from "~/utils"; import { LoadingAsset, AssetRow } from "./AssetRow"; -import { LSTDialog, LSTDialogVariants, NewAssetBanner, NewAssetBannerList } from "~/components/common/AssetList"; -import { MrgnTooltip, MrgnLabeledSwitch, MrgnContainedSwitch } from "~/components/common"; +import { LSTDialog, LSTDialogVariants, AssetListFilters, sortApRate, sortTvl } from "~/components/common/AssetList"; +import { MrgnTooltip } from "~/components/common"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select"; - -import { LendingModes, PoolTypes } from "~/types"; +import { LendingModes } from "~/types"; const UserPositions = dynamic(async () => (await import("~/components/desktop/UserPositions")).UserPositions, { ssr: false, @@ -24,7 +22,7 @@ const UserPositions = dynamic(async () => (await import("~/components/desktop/Us const AssetsList: FC = () => { // const { selectedAccount, nativeSolBalance } = useStore(); const { connected, walletAddress } = useWalletContext(); - const [isStoreInitialized, sortedBanks, nativeSolBalance, selectedAccount] = useMrgnlendStore((state) => [ + const [isStoreInitialized, extendedBankInfos, nativeSolBalance, selectedAccount] = useMrgnlendStore((state) => [ state.initialized, state.extendedBankInfos, state.nativeSolBalance, @@ -35,22 +33,13 @@ const AssetsList: FC = () => { state.showBadges, state.setShowBadges, ]); - const [ - lendingMode, - setLendingMode, - poolFilter, - setPoolFilter, - isFilteredUserPositions, - setIsFilteredUserPositions, - setIsWalletAuthDialogOpen, - ] = useUiStore((state) => [ + const [lendingMode, setLendingMode, poolFilter, isFilteredUserPositions, sortOption] = useUiStore((state) => [ state.lendingMode, state.setLendingMode, state.poolFilter, - state.setPoolFilter, state.isFilteredUserPositions, - state.setIsFilteredUserPositions, - state.setIsWalletAuthDialogOpen, + state.sortOption, + state.setSortOption, ]); const inputRefs = useRef>({}); @@ -61,6 +50,47 @@ const AssetsList: FC = () => { const isInLendingMode = React.useMemo(() => lendingMode === LendingModes.LEND, [lendingMode]); + const sortBanks = React.useCallback( + (banks: ExtendedBankInfo[]) => { + if (sortOption.field === "APY") { + return sortApRate(banks, isInLendingMode, sortOption.direction); + } else if (sortOption.field === "TVL") { + return sortTvl(banks, sortOption.direction); + } else { + return banks; + } + }, + [isInLendingMode, sortOption] + ); + + const globalBanks = React.useMemo(() => { + const filteredBanks = + extendedBankInfos && + extendedBankInfos + .filter((b) => !b.info.state.isIsolated) + .filter((b) => (isFilteredUserPositions ? b.isActive : true)); + + if (isStoreInitialized && sortOption && filteredBanks) { + return sortBanks(filteredBanks); + } else { + return filteredBanks; + } + }, [isStoreInitialized, extendedBankInfos, sortOption, isFilteredUserPositions, sortBanks]); + + const isolatedBanks = React.useMemo(() => { + const filteredBanks = + extendedBankInfos && + extendedBankInfos + .filter((b) => b.info.state.isIsolated) + .filter((b) => (isFilteredUserPositions ? b.isActive : true)); + + if (isStoreInitialized && sortOption && filteredBanks) { + return sortBanks(filteredBanks); + } else { + return filteredBanks; + } + }, [isStoreInitialized, extendedBankInfos, sortOption, isFilteredUserPositions, sortBanks]); + // Enter hotkey mode useHotkeys( "meta + k", @@ -78,13 +108,13 @@ const AssetsList: FC = () => { // Handle number keys in hotkey mode useHotkeys( - sortedBanks + extendedBankInfos .filter((b) => !b.info.state.isIsolated) .map((_, i) => `${i + 1}`) .join(", "), (_, handler) => { if (isHotkeyMode) { - const globalBankTokenNames = sortedBanks + const globalBankTokenNames = extendedBankInfos .filter((b) => !b.info.state.isIsolated) .sort( (a, b) => b.info.state.totalDeposits * b.info.state.price - a.info.state.totalDeposits * a.info.state.price @@ -127,56 +157,7 @@ const AssetsList: FC = () => { return ( <> <> -
-
-
- - setLendingMode(lendingMode === LendingModes.LEND ? LendingModes.BORROW : LendingModes.LEND) - } - /> -
-
- Filter pools - -
-
- -
{ - e.stopPropagation(); - if (connected) return; - setIsWalletAuthDialogOpen(true); - }} - > - setIsFilteredUserPositions(!isFilteredUserPositions)} - inputProps={{ "aria-label": "controlled" }} - className={cn(!connected && "pointer-events-none")} - /> -
Filter my positions
-
- - -
+ {!isFilteredUserPositions && (
@@ -373,7 +354,7 @@ const AssetsList: FC = () => { - {sortedBanks + {globalBanks .filter((b) => !b.info.state.isIsolated) .map((bank, i) => isStoreInitialized ? ( @@ -608,7 +589,7 @@ const AssetsList: FC = () => { - {sortedBanks + {isolatedBanks .filter((b) => b.info.state.isIsolated) .map((bank) => isStoreInitialized ? ( diff --git a/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/MobileAssetsList.tsx b/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/MobileAssetsList.tsx index a3b49be875..aeeb65836e 100644 --- a/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/MobileAssetsList.tsx +++ b/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/MobileAssetsList.tsx @@ -1,46 +1,40 @@ -import React, { FC, useCallback, useMemo, useRef, useState } from "react"; +import React from "react"; + import Image from "next/image"; + import { ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; -import { SelectChangeEvent, Skeleton, Typography } from "@mui/material"; +import { Skeleton, Typography } from "@mui/material"; + import { useMrgnlendStore, useUiStore } from "~/store"; import { useWalletContext } from "~/hooks/useWalletContext"; -import { MrgnContainedSwitch, MrgnLabeledSwitch, MrgnTooltip } from "~/components/common"; -import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectLabel, - SelectTrigger, - SelectValue, -} from "~/components/ui/select"; - -import { AssetCard } from "./AssetCard"; -import { LSTDialog, LSTDialogVariants } from "~/components/common/AssetList"; -import { SORT_OPTIONS_MAP, SortAssetOption, SortType, sortApRate, sortTvl } from "./MobileAssetsList.utils"; -import { cn } from "~/utils/themeUtils"; - -export const MobileAssetsList: FC = () => { - const [isFiltered, setIsFiltered] = useState(false); - const [sortOption, setSortOption] = useState(SORT_OPTIONS_MAP.TVL_DESC); - const togglePositions = () => setIsFiltered((previousState) => !previousState); +import { MrgnLabeledSwitch, MrgnTooltip } from "~/components/common"; +import { LSTDialog, LSTDialogVariants, AssetListFilters, sortApRate, sortTvl } from "~/components/common/AssetList"; +import { AssetCard } from "~/components/mobile/MobileAssetsList/AssetCard"; + +export const MobileAssetsList = () => { const { connected } = useWalletContext(); - const [setIsWalletAuthDialogOpen] = useUiStore((state) => [state.setIsWalletAuthDialogOpen]); const [isStoreInitialized, extendedBankInfos, nativeSolBalance, selectedAccount] = useMrgnlendStore((state) => [ state.initialized, state.extendedBankInfos, state.nativeSolBalance, state.selectedAccount, ]); - const inputRefs = useRef>({}); - const [isInLendingMode, setIsInLendingMode] = useState(true); - const [isLSTDialogOpen, setIsLSTDialogOpen] = useState(false); - const [lstDialogVariant, setLSTDialogVariant] = useState(null); - const [lstDialogCallback, setLSTDialogCallback] = useState<(() => void) | null>(null); - const sortBanks = useCallback( + const [isFilteredUserPositions, sortOption, poolFilter] = useUiStore((state) => [ + state.isFilteredUserPositions, + state.sortOption, + state.poolFilter, + ]); + + const inputRefs = React.useRef>({}); + const [isInLendingMode, setIsInLendingMode] = React.useState(true); + const [isLSTDialogOpen, setIsLSTDialogOpen] = React.useState(false); + const [lstDialogVariant, setLSTDialogVariant] = React.useState(null); + const [lstDialogCallback, setLSTDialogCallback] = React.useState<(() => void) | null>(null); + + const sortBanks = React.useCallback( (banks: ExtendedBankInfo[]) => { if (sortOption.field === "APY") { return sortApRate(banks, isInLendingMode, sortOption.direction); @@ -53,36 +47,37 @@ export const MobileAssetsList: FC = () => { [isInLendingMode, sortOption] ); - const globalBanks = useMemo(() => { + const globalBanks = React.useMemo(() => { const filteredBanks = extendedBankInfos && - extendedBankInfos.filter((b) => !b.info.state.isIsolated).filter((b) => (isFiltered ? b.isActive : true)); + extendedBankInfos + .filter((b) => !b.info.state.isIsolated) + .filter((b) => (isFilteredUserPositions ? b.isActive : true)); if (isStoreInitialized && sortOption && filteredBanks) { return sortBanks(filteredBanks); } else { return filteredBanks; } - }, [isStoreInitialized, extendedBankInfos, sortOption, isFiltered, sortBanks]); + }, [isStoreInitialized, extendedBankInfos, sortOption, isFilteredUserPositions, sortBanks]); - const isolatedBanks = useMemo(() => { + const isolatedBanks = React.useMemo(() => { const filteredBanks = extendedBankInfos && - extendedBankInfos.filter((b) => b.info.state.isIsolated).filter((b) => (isFiltered ? b.isActive : true)); + extendedBankInfos + .filter((b) => b.info.state.isIsolated) + .filter((b) => (isFilteredUserPositions ? b.isActive : true)); if (isStoreInitialized && sortOption && filteredBanks) { return sortBanks(filteredBanks); } else { return filteredBanks; } - }, [isStoreInitialized, extendedBankInfos, sortOption, isFiltered, sortBanks]); - - const handleSortChange = (event: SelectChangeEvent) => { - setSortOption(SORT_OPTIONS_MAP[event.target.value as SortType]); - }; + }, [isStoreInitialized, extendedBankInfos, sortOption, isFilteredUserPositions, sortBanks]); return ( <> +
{ />
-
-
{ - e.stopPropagation(); - if (connected) return; - setIsWalletAuthDialogOpen(true); - }} - > - -
Filter my positions
-
-
- -
-
+
-
- - Global pool - - {isStoreInitialized && globalBanks ? ( - globalBanks.length > 0 ? ( + {poolFilter !== "isolated" && ( +
+ + Global pool + + {isStoreInitialized && globalBanks ? ( + globalBanks.length > 0 ? ( +
+ {globalBanks.map((bank) => ( + void) => { + setLSTDialogVariant(variant); + setIsLSTDialogOpen(true); + if (onClose) { + setLSTDialogCallback(() => onClose); + } + }} + /> + ))} +
+ ) : ( + + No {isInLendingMode ? "lending" : "borrowing"} {isFilteredUserPositions ? "positions" : "pools"}{" "} + found. + + ) + ) : (
- {globalBanks.map((bank) => ( - void) => { - setLSTDialogVariant(variant); - setIsLSTDialogOpen(true); - if (onClose) { - setLSTDialogCallback(() => onClose); - } - }} + {[...Array(6)].map((_, i) => ( + ))}
+ )} +
+ )} + {poolFilter === "isolated" && ( +
+ + Isolated pools + + + Isolated pools are risky ⚠️ + + Assets in isolated pools cannot be used as collateral. When you borrow an isolated asset, you cannot + borrow other assets. Isolated pools should be considered particularly risky. As always, remember + that marginfi is a decentralized protocol and all deposited funds are at risk. + + } + placement="top" + > + info + + + + {isStoreInitialized && globalBanks ? ( + isolatedBanks.length > 0 ? ( +
+ {isolatedBanks.map((bank, i) => ( + + ))} +
+ ) : ( + + No {isInLendingMode ? "lending" : "borrowing"} {isFilteredUserPositions ? "positions" : "pools"}{" "} + found. + + ) ) : ( - - No {isInLendingMode ? "lending" : "borrowing"} {isFiltered ? "positions" : "pools"} found. - - ) - ) : ( -
- {[...Array(6)].map((_, i) => ( - - ))} -
- )} -
-
- - Isolated pool - - - Isolated pools are risky ⚠️ - - Assets in isolated pools cannot be used as collateral. When you borrow an isolated asset, you cannot - borrow other assets. Isolated pools should be considered particularly risky. As always, remember that - marginfi is a decentralized protocol and all deposited funds are at risk. - - } - placement="top" - > - info - - - - {isStoreInitialized && globalBanks ? ( - isolatedBanks.length > 0 ? ( -
- {isolatedBanks.map((bank, i) => ( - + {[...Array(6)].map((_, i) => ( + ))}
- ) : ( - - No {isInLendingMode ? "lending" : "borrowing"} {isFiltered ? "positions" : "pools"} found. - - ) - ) : ( -
- {[...Array(6)].map((_, i) => ( - - ))} -
- )} -
+ )} +
+ )}
> = createUiStore(); const useMrgnlendStore: UseBoundStore> = createPersistentMrgnlendStore(); const useUserProfileStore: UseBoundStore> = createUserProfileStore(); -export { useUiStore, useMrgnlendStore, useUserProfileStore }; +export { useUiStore, useMrgnlendStore, useUserProfileStore, SORT_OPTIONS_MAP }; diff --git a/apps/marginfi-v2-ui/src/store/uiStore.ts b/apps/marginfi-v2-ui/src/store/uiStore.ts index d87c68036e..78d6cd9d75 100644 --- a/apps/marginfi-v2-ui/src/store/uiStore.ts +++ b/apps/marginfi-v2-ui/src/store/uiStore.ts @@ -1,6 +1,35 @@ import { create, StateCreator } from "zustand"; -import { LendingModes, PoolTypes } from "~/types"; +import { LendingModes, PoolTypes, SortType, sortDirection, SortAssetOption } from "~/types"; + +const SORT_OPTIONS_MAP: { [key in SortType]: SortAssetOption } = { + APY_DESC: { + label: "APY highest to lowest", + borrowLabel: "APR highest to lowest", + value: SortType.APY_DESC, + field: "APY", + direction: sortDirection.DESC, + }, + APY_ASC: { + label: "APY lowest to highest", + borrowLabel: "APR lowest to highest", + value: SortType.APY_ASC, + field: "APY", + direction: sortDirection.ASC, + }, + TVL_DESC: { + label: "TVL highest to lowest", + value: SortType.TVL_DESC, + field: "TVL", + direction: sortDirection.DESC, + }, + TVL_ASC: { + label: "TVL lowest to highest", + value: SortType.TVL_ASC, + field: "TVL", + direction: sortDirection.DESC, + }, +}; interface UiState { // State @@ -12,6 +41,7 @@ interface UiState { isFilteredUserPositions: boolean; lendingMode: LendingModes; poolFilter: PoolTypes; + sortOption: SortAssetOption; // Actions setIsMenuDrawerOpen: (isOpen: boolean) => void; @@ -22,6 +52,7 @@ interface UiState { setIsFilteredUserPositions: (isFilteredUserPositions: boolean) => void; setLendingMode: (lendingMode: LendingModes) => void; setPoolFilter: (poolType: PoolTypes) => void; + setSortOption: (sortOption: SortAssetOption) => void; } function createUiStore() { @@ -38,6 +69,7 @@ const stateCreator: StateCreator = (set, get) => ({ isFilteredUserPositions: false, lendingMode: LendingModes.LEND, poolFilter: PoolTypes.ALL, + sortOption: SORT_OPTIONS_MAP[SortType.APY_DESC], // Actions setIsMenuDrawerOpen: (isOpen: boolean) => set({ isMenuDrawerOpen: isOpen }), @@ -49,7 +81,8 @@ const stateCreator: StateCreator = (set, get) => ({ set({ isFilteredUserPositions: isFilteredUserPositions }), setLendingMode: (lendingMode: LendingModes) => set({ lendingMode: lendingMode }), setPoolFilter: (poolType: PoolTypes) => set({ poolFilter: poolType }), + setSortOption: (sortOption: SortAssetOption) => set({ sortOption: sortOption }), }); -export { createUiStore }; +export { createUiStore, SORT_OPTIONS_MAP }; export type { UiState }; diff --git a/apps/marginfi-v2-ui/src/types.ts b/apps/marginfi-v2-ui/src/types.ts index dede46fe73..4f3d0838c5 100644 --- a/apps/marginfi-v2-ui/src/types.ts +++ b/apps/marginfi-v2-ui/src/types.ts @@ -88,3 +88,23 @@ export enum PoolTypes { ALL = "all", ISOLATED = "isolated", } + +export type SortAssetOption = { + label: string; + borrowLabel?: string; + value: SortType; + field: "APY" | "TVL"; + direction: sortDirection; +}; + +export enum sortDirection { + ASC = "ASC", + DESC = "DESC", +} + +export enum SortType { + APY_ASC = "APY_ASC", + APY_DESC = "APY_DESC", + TVL_ASC = "TVL_ASC", + TVL_DESC = "TVL_DESC", +} From 3a0cd77a2cdb14f564656319845d41781ccdbce6 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Fri, 24 Nov 2023 11:33:00 -0500 Subject: [PATCH 26/93] feat: pool sort / filter layout --- .../common/AssetList/AssetListFilters.tsx | 127 ++++++++++-------- .../MobileAssetsList/MobileAssetsList.tsx | 20 +-- .../src/components/ui/icons.tsx | 6 + 3 files changed, 80 insertions(+), 73 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.tsx b/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.tsx index aecc017b72..f2e321f554 100644 --- a/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.tsx +++ b/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.tsx @@ -9,8 +9,9 @@ import { MrgnContainedSwitch } from "~/components/common/MrgnContainedSwitch"; import { NewAssetBanner } from "~/components/common/AssetList"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select"; +import { IconFilter, IconSortAscending, IconSortDescending } from "~/components/ui/icons"; -import { LendingModes, PoolTypes, SortType, SortAssetOption } from "~/types"; +import { LendingModes, PoolTypes, SortType, sortDirection, SortAssetOption } from "~/types"; export const AssetListFilters = () => { const { connected } = useWalletContext(); @@ -38,8 +39,8 @@ export const AssetListFilters = () => { return (
-
-
+
+
{ onClick={() => setLendingMode(lendingMode === LendingModes.LEND ? LendingModes.BORROW : LendingModes.LEND)} />
-
- Filter - +
{ + e.stopPropagation(); + if (connected) return; + setIsWalletAuthDialogOpen(true); + }} + > + setIsFilteredUserPositions(!isFilteredUserPositions)} + inputProps={{ "aria-label": "controlled" }} + className={cn(!connected && "pointer-events-none")} + /> +
Filter my positions
-
- Sort - +
+
+ +
+
+ +
- -
{ - e.stopPropagation(); - if (connected) return; - setIsWalletAuthDialogOpen(true); - }} - > - setIsFilteredUserPositions(!isFilteredUserPositions)} - inputProps={{ "aria-label": "controlled" }} - className={cn(!connected && "pointer-events-none")} - /> -
Filter my positions
-
-
); diff --git a/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/MobileAssetsList.tsx b/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/MobileAssetsList.tsx index aeeb65836e..32ca0fddb8 100644 --- a/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/MobileAssetsList.tsx +++ b/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/MobileAssetsList.tsx @@ -8,10 +8,12 @@ import { Skeleton, Typography } from "@mui/material"; import { useMrgnlendStore, useUiStore } from "~/store"; import { useWalletContext } from "~/hooks/useWalletContext"; -import { MrgnLabeledSwitch, MrgnTooltip } from "~/components/common"; +import { MrgnTooltip } from "~/components/common"; import { LSTDialog, LSTDialogVariants, AssetListFilters, sortApRate, sortTvl } from "~/components/common/AssetList"; import { AssetCard } from "~/components/mobile/MobileAssetsList/AssetCard"; +import { LendingModes } from "~/types"; + export const MobileAssetsList = () => { const { connected } = useWalletContext(); @@ -22,14 +24,15 @@ export const MobileAssetsList = () => { state.selectedAccount, ]); - const [isFilteredUserPositions, sortOption, poolFilter] = useUiStore((state) => [ + const [lendingMode, isFilteredUserPositions, sortOption, poolFilter] = useUiStore((state) => [ + state.lendingMode, state.isFilteredUserPositions, state.sortOption, state.poolFilter, ]); + const isInLendingMode = React.useMemo(() => lendingMode === LendingModes.LEND, [lendingMode]); const inputRefs = React.useRef>({}); - const [isInLendingMode, setIsInLendingMode] = React.useState(true); const [isLSTDialogOpen, setIsLSTDialogOpen] = React.useState(false); const [lstDialogVariant, setLSTDialogVariant] = React.useState(null); const [lstDialogCallback, setLSTDialogCallback] = React.useState<(() => void) | null>(null); @@ -78,17 +81,6 @@ export const MobileAssetsList = () => { return ( <> -
-
- setIsInLendingMode(!isInLendingMode)} - /> -
-
-
{poolFilter !== "isolated" && (
diff --git a/apps/marginfi-v2-ui/src/components/ui/icons.tsx b/apps/marginfi-v2-ui/src/components/ui/icons.tsx index da3e7e3031..f14404c26b 100644 --- a/apps/marginfi-v2-ui/src/components/ui/icons.tsx +++ b/apps/marginfi-v2-ui/src/components/ui/icons.tsx @@ -20,6 +20,9 @@ import { IconExternalLink, IconUserPlus, IconClockHour4, + IconFilter, + IconSortAscending, + IconSortDescending, } from "@tabler/icons-react"; import { cn } from "~/utils/themeUtils"; @@ -459,6 +462,9 @@ export { IconUserPlus, IconBrandDiscordFilled, IconClockHour4, + IconFilter, + IconSortAscending, + IconSortDescending, // customed icons IconBrandGoogle, From a76bea856e9122e3fd557d5bce2c97b1d552c9b6 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Fri, 24 Nov 2023 11:39:40 -0500 Subject: [PATCH 27/93] feat: hide new asset banner --- .../common/AssetList/NewAsssetBanner.tsx | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx b/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx index d26a57c875..791325c2d7 100644 --- a/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx +++ b/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx @@ -21,6 +21,8 @@ export const NewAssetBanner = ({ asset, image }: NewAssetBannerProps) => { state.setIsFilteredUserPositions, ]); + const [isHidden, setIsHidden] = React.useState(true); + const assetTicker = React.useMemo(() => "$" + asset.toUpperCase(), [asset]); const highlightAsset = React.useCallback(() => { @@ -30,7 +32,7 @@ export const NewAssetBanner = ({ asset, image }: NewAssetBannerProps) => { console.log(asset, assetRow); if (!assetRow) return; - assetRows.forEach((row) => row.classList.add("opacity-30", "hover:bg-[#171C1F]")); + assetRows.forEach((row) => row.classList.add("opacity-30", "hover:!bg-[#171C1F]")); assetRow.scrollIntoView({ behavior: "smooth", block: "center" }); assetRow.classList.remove("opacity-30"); assetRow.classList.add("animate-pulse"); @@ -42,18 +44,30 @@ export const NewAssetBanner = ({ asset, image }: NewAssetBannerProps) => { const deposit = React.useCallback(() => { setLendingMode(LendingModes.LEND); - if (poolFilter === PoolTypes.GLOBAL) setPoolFilter(PoolTypes.ALL); + if (poolFilter === PoolTypes.ALL) setPoolFilter(PoolTypes.ALL); setIsFilteredUserPositions(false); setTimeout(() => highlightAsset(), 100); }, [setLendingMode, setPoolFilter, asset]); const borrow = React.useCallback(() => { setLendingMode(LendingModes.BORROW); - if (poolFilter === PoolTypes.GLOBAL) setPoolFilter(PoolTypes.ALL); + if (poolFilter === PoolTypes.ALL) setPoolFilter(PoolTypes.ALL); setIsFilteredUserPositions(false); setTimeout(() => highlightAsset(), 100); }, [setLendingMode, setPoolFilter, asset]); + const hideBanner = React.useCallback(() => { + setIsHidden(true); + localStorage.setItem("mfiHideNewAssetBanner", "true"); + }, [setIsHidden]); + + React.useEffect(() => { + const isHidden = localStorage.getItem("mfiHideNewAssetBanner"); + if (!isHidden) setIsHidden(false); + }, []); + + if (isHidden) return null; + return (
@@ -76,7 +90,7 @@ export const NewAssetBanner = ({ asset, image }: NewAssetBannerProps) => {
-
From ee1e4a68fa5a169c583ff3180864961c6674b354 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Fri, 24 Nov 2023 11:44:11 -0500 Subject: [PATCH 28/93] chore: add missing deps to new asset banner callbacks --- .../src/components/common/AssetList/NewAsssetBanner.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx b/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx index 791325c2d7..f3ff052e72 100644 --- a/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx +++ b/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx @@ -47,14 +47,14 @@ export const NewAssetBanner = ({ asset, image }: NewAssetBannerProps) => { if (poolFilter === PoolTypes.ALL) setPoolFilter(PoolTypes.ALL); setIsFilteredUserPositions(false); setTimeout(() => highlightAsset(), 100); - }, [setLendingMode, setPoolFilter, asset]); + }, [setLendingMode, poolFilter, setPoolFilter, asset, setIsFilteredUserPositions, highlightAsset]); const borrow = React.useCallback(() => { setLendingMode(LendingModes.BORROW); if (poolFilter === PoolTypes.ALL) setPoolFilter(PoolTypes.ALL); setIsFilteredUserPositions(false); setTimeout(() => highlightAsset(), 100); - }, [setLendingMode, setPoolFilter, asset]); + }, [setLendingMode, poolFilter, setPoolFilter, asset, setIsFilteredUserPositions, highlightAsset]); const hideBanner = React.useCallback(() => { setIsHidden(true); From 09223ce4b35ae7d8a6cf6835cdf352fa151edcc7 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Fri, 24 Nov 2023 11:44:20 -0500 Subject: [PATCH 29/93] fix: incorrect tvl sort direction --- apps/marginfi-v2-ui/src/store/uiStore.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/marginfi-v2-ui/src/store/uiStore.ts b/apps/marginfi-v2-ui/src/store/uiStore.ts index 78d6cd9d75..a894877e1c 100644 --- a/apps/marginfi-v2-ui/src/store/uiStore.ts +++ b/apps/marginfi-v2-ui/src/store/uiStore.ts @@ -27,7 +27,7 @@ const SORT_OPTIONS_MAP: { [key in SortType]: SortAssetOption } = { label: "TVL lowest to highest", value: SortType.TVL_ASC, field: "TVL", - direction: sortDirection.DESC, + direction: sortDirection.ASC, }, }; From 7be2e1786f2ca1104449a28438441e3825d6e75b Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Fri, 24 Nov 2023 12:20:47 -0500 Subject: [PATCH 30/93] feat: add stable / lst filter options --- .../common/AssetList/AssetListFilters.tsx | 2 + .../AssetList/AssetListFilters.utils.tsx | 4 + .../common/AssetList/NewAsssetBanner.tsx | 4 +- .../desktop/AssetsList/AssetsList.tsx | 400 +++++++++--------- apps/marginfi-v2-ui/src/types.ts | 2 + 5 files changed, 216 insertions(+), 196 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.tsx b/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.tsx index f2e321f554..07e16616d7 100644 --- a/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.tsx +++ b/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.tsx @@ -81,6 +81,8 @@ export const AssetListFilters = () => { All pools Isolated pools + Stablecoins + LSTs
diff --git a/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.utils.tsx b/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.utils.tsx index 696af1bbb1..93e00b7f3c 100644 --- a/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.utils.tsx +++ b/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.utils.tsx @@ -1,6 +1,10 @@ import { ExtendedBankInfo, Emissions } from "@mrgnlabs/marginfi-v2-ui-state"; import type { sortDirection } from "~/types"; +export const STABLECOINS = ["USDC", "USDT", "UXD"]; + +export const LSTS = ["LST", "stSOL", "jitoSOL", "bSOL", "mSOL"]; + export const sortApRate = (banks: ExtendedBankInfo[], isInLendingMode: boolean, direction: sortDirection) => { return banks.sort((a, b) => { const apRateA = diff --git a/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx b/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx index f3ff052e72..ec0aa5a70d 100644 --- a/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx +++ b/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx @@ -44,14 +44,14 @@ export const NewAssetBanner = ({ asset, image }: NewAssetBannerProps) => { const deposit = React.useCallback(() => { setLendingMode(LendingModes.LEND); - if (poolFilter === PoolTypes.ALL) setPoolFilter(PoolTypes.ALL); + if (poolFilter !== PoolTypes.ALL) setPoolFilter(PoolTypes.ALL); setIsFilteredUserPositions(false); setTimeout(() => highlightAsset(), 100); }, [setLendingMode, poolFilter, setPoolFilter, asset, setIsFilteredUserPositions, highlightAsset]); const borrow = React.useCallback(() => { setLendingMode(LendingModes.BORROW); - if (poolFilter === PoolTypes.ALL) setPoolFilter(PoolTypes.ALL); + if (poolFilter !== PoolTypes.ALL) setPoolFilter(PoolTypes.ALL); setIsFilteredUserPositions(false); setTimeout(() => highlightAsset(), 100); }, [setLendingMode, poolFilter, setPoolFilter, asset, setIsFilteredUserPositions, highlightAsset]); diff --git a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx index 4cf9542191..d54c8debb2 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx @@ -10,7 +10,15 @@ import { useMrgnlendStore, useUserProfileStore, useUiStore } from "~/store"; import { useWalletContext } from "~/hooks/useWalletContext"; import { LoadingAsset, AssetRow } from "./AssetRow"; -import { LSTDialog, LSTDialogVariants, AssetListFilters, sortApRate, sortTvl } from "~/components/common/AssetList"; +import { + LSTDialog, + LSTDialogVariants, + AssetListFilters, + sortApRate, + sortTvl, + STABLECOINS, + LSTS, +} from "~/components/common/AssetList"; import { MrgnTooltip } from "~/components/common"; import { LendingModes } from "~/types"; @@ -356,8 +364,10 @@ const AssetsList: FC = () => { {globalBanks .filter((b) => !b.info.state.isIsolated) - .map((bank, i) => - isStoreInitialized ? ( + .map((bank, i) => { + if (poolFilter === "stable" && !STABLECOINS.includes(bank.meta.tokenSymbol)) return null; + if (poolFilter === "lst" && !LSTS.includes(bank.meta.tokenSymbol)) return null; + return isStoreInitialized ? ( { isInLendingMode={isInLendingMode} bankMetadata={bank.meta} /> - ) - )} + ); + })} )} - - - - -
- - Isolated pools - - - - Isolated pools are risky ⚠️ - - Assets in isolated pools cannot be used as collateral. When you borrow an isolated - asset, you cannot borrow other assets. Isolated pools should be considered particularly - risky. As always, remember that marginfi is a decentralized protocol and all deposited - funds are at risk. - - } - placement="top" - > - - -
-
- -
- Price - - - Realtime prices - - - Powered by Pyth and Switchboard. - - - } - placement="top" - > - - -
-
- -
- {isInLendingMode ? "APY" : "APR"} - - - {isInLendingMode ? "APY" : "APR"} - - - {isInLendingMode - ? "What you'll earn on deposits over a year. This includes compounding. All marginfi deposits are compounded hourly." - : "What you'll pay for your borrows, or the price of a loan. This does not include compounding. All marginfi borrows are compounded hourly."} - - - } - placement="top" - > - - -
-
- -
- {isInLendingMode ? "Weight" : "LTV"} - - - {isInLendingMode ? "Weight" : "LTV"} - - - {isInLendingMode - ? "How much your assets count for collateral, relative to their USD value. The higher the weight, the more collateral you can borrow against it." - : "How much you can borrow against your free collateral. The higher the LTV, the more you can borrow against your free collateral."} - - - } - placement="top" - > - - -
-
- -
- {isInLendingMode ? "Deposits" : "Available"} - - - {isInLendingMode ? "Total deposits" : "Total available"} - - - {isInLendingMode - ? "Total marginfi deposits for each asset. Everything is denominated in native tokens." - : "The amount of tokens available to borrow for each asset. Calculated as the minimum of the asset's borrow limit and available liquidity that has not yet been borrowed."} - - - } - placement="top" - > - - -
-
- - {/*******************************/} - {/* [START]: ZOOM-BASED COLUMNS */} - {/*******************************/} - - {lendZoomLevel < 2 && ( + {poolFilter !== "stable" && poolFilter !== "lst" && ( +
+ + + +
+ + Isolated pools + + + + Isolated pools are risky ⚠️ + + Assets in isolated pools cannot be used as collateral. When you borrow an isolated + asset, you cannot borrow other assets. Isolated pools should be considered + particularly risky. As always, remember that marginfi is a decentralized protocol and + all deposited funds are at risk. + + } + placement="top" + > + + +
+
- Global limit + Price - {isInLendingMode ? "Global deposit cap" : "Global borrow cap"} + Realtime prices - Each marginfi pool has global deposit and borrow limits, also known as caps. This is - the total amount that all users combined can deposit or borrow of a given token. + + Powered by Pyth and Switchboard. + + + } + placement="top" + > + + +
+
+ +
+ {isInLendingMode ? "APY" : "APR"} + + + {isInLendingMode ? "APY" : "APR"} + + + {isInLendingMode + ? "What you'll earn on deposits over a year. This includes compounding. All marginfi deposits are compounded hourly." + : "What you'll pay for your borrows, or the price of a loan. This does not include compounding. All marginfi borrows are compounded hourly."} + } placement="top" @@ -545,24 +475,24 @@ const AssetsList: FC = () => {
- )} - - {lendZoomLevel < 3 && (
- Utilization + {isInLendingMode ? "Weight" : "LTV"} - Pool utilization + {isInLendingMode ? "Weight" : "LTV"} - What percentage of supplied tokens have been borrowed. This helps determine interest - rates. This is not based on the global pool limits, which can limit utilization. + + {isInLendingMode + ? "How much your assets count for collateral, relative to their USD value. The higher the weight, the more collateral you can borrow against it." + : "How much you can borrow against your free collateral. The higher the LTV, the more you can borrow against your free collateral."} + } placement="top" @@ -571,48 +501,130 @@ const AssetsList: FC = () => {
- )} - - {/*******************************/} - {/* [END]: ZOOM-BASED COLUMNS */} - {/*******************************/} - - -
Wallet Amt.
-
- - -
-
- - {isolatedBanks - .filter((b) => b.info.state.isIsolated) - .map((bank) => - isStoreInitialized ? ( - - ) : ( - - ) - )} - -
+ +
+ {isInLendingMode ? "Deposits" : "Available"} + + + {isInLendingMode ? "Total deposits" : "Total available"} + + + {isInLendingMode + ? "Total marginfi deposits for each asset. Everything is denominated in native tokens." + : "The amount of tokens available to borrow for each asset. Calculated as the minimum of the asset's borrow limit and available liquidity that has not yet been borrowed."} + + + } + placement="top" + > + info + +
+
+ + {/*******************************/} + {/* [START]: ZOOM-BASED COLUMNS */} + {/*******************************/} + + {lendZoomLevel < 2 && ( + +
+ Global limit + + + {isInLendingMode ? "Global deposit cap" : "Global borrow cap"} + + Each marginfi pool has global deposit and borrow limits, also known as caps. This is + the total amount that all users combined can deposit or borrow of a given token. + + } + placement="top" + > + info + +
+
+ )} + + {lendZoomLevel < 3 && ( + +
+ Utilization + + + Pool utilization + + What percentage of supplied tokens have been borrowed. This helps determine interest + rates. This is not based on the global pool limits, which can limit utilization. + + } + placement="top" + > + info + +
+
+ )} + + {/*******************************/} + {/* [END]: ZOOM-BASED COLUMNS */} + {/*******************************/} + + +
Wallet Amt.
+
+ + + + + + {isolatedBanks + .filter((b) => b.info.state.isIsolated) + .map((bank) => + isStoreInitialized ? ( + + ) : ( + + ) + )} + + + )}
diff --git a/apps/marginfi-v2-ui/src/types.ts b/apps/marginfi-v2-ui/src/types.ts index 4f3d0838c5..473aa36c01 100644 --- a/apps/marginfi-v2-ui/src/types.ts +++ b/apps/marginfi-v2-ui/src/types.ts @@ -87,6 +87,8 @@ export enum LendingModes { export enum PoolTypes { ALL = "all", ISOLATED = "isolated", + STABLE = "stable", + LST = "lst", } export type SortAssetOption = { From b9492a6673c198054fafb700c0cecb03b1d19666 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Fri, 24 Nov 2023 13:17:18 -0500 Subject: [PATCH 31/93] feat: switch LST filter option to SOL / LST --- .../src/components/common/AssetList/AssetListFilters.tsx | 2 +- .../src/components/common/AssetList/AssetListFilters.utils.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.tsx b/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.tsx index 07e16616d7..c615636f93 100644 --- a/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.tsx +++ b/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.tsx @@ -82,7 +82,7 @@ export const AssetListFilters = () => { All pools Isolated pools Stablecoins - LSTs + SOL / LST
diff --git a/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.utils.tsx b/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.utils.tsx index 93e00b7f3c..89729658a4 100644 --- a/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.utils.tsx +++ b/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.utils.tsx @@ -3,7 +3,7 @@ import type { sortDirection } from "~/types"; export const STABLECOINS = ["USDC", "USDT", "UXD"]; -export const LSTS = ["LST", "stSOL", "jitoSOL", "bSOL", "mSOL"]; +export const LSTS = ["SOL", "LST", "stSOL", "JitoSOL", "bSOL", "mSOL"]; export const sortApRate = (banks: ExtendedBankInfo[], isInLendingMode: boolean, direction: sortDirection) => { return banks.sort((a, b) => { From 41e59010ddba7c3cb3693acb028cdb523d4cf6a3 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Fri, 24 Nov 2023 13:38:51 -0500 Subject: [PATCH 32/93] fix: disable filter / sort when user positions active --- .../src/components/common/AssetList/AssetListFilters.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.tsx b/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.tsx index c615636f93..e20301de8c 100644 --- a/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.tsx +++ b/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.tsx @@ -68,6 +68,7 @@ export const AssetListFilters = () => {
setSortOption(SORT_OPTIONS_MAP[value as SortType])} > From cd1d55ac640355a16e30599a4fcc556d5bb76471 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Fri, 24 Nov 2023 16:55:31 -0500 Subject: [PATCH 33/93] fix: mobile filter / sort onChange --- .../MobileAssetsList/MobileAssetsList.tsx | 76 +++++++++++-------- 1 file changed, 45 insertions(+), 31 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/MobileAssetsList.tsx b/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/MobileAssetsList.tsx index 32ca0fddb8..5fb1c72c0e 100644 --- a/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/MobileAssetsList.tsx +++ b/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/MobileAssetsList.tsx @@ -9,7 +9,15 @@ import { useMrgnlendStore, useUiStore } from "~/store"; import { useWalletContext } from "~/hooks/useWalletContext"; import { MrgnTooltip } from "~/components/common"; -import { LSTDialog, LSTDialogVariants, AssetListFilters, sortApRate, sortTvl } from "~/components/common/AssetList"; +import { + LSTDialog, + LSTDialogVariants, + AssetListFilters, + sortApRate, + sortTvl, + STABLECOINS, + LSTS, +} from "~/components/common/AssetList"; import { AssetCard } from "~/components/mobile/MobileAssetsList/AssetCard"; import { LendingModes } from "~/types"; @@ -90,24 +98,28 @@ export const MobileAssetsList = () => { {isStoreInitialized && globalBanks ? ( globalBanks.length > 0 ? (
- {globalBanks.map((bank) => ( - void) => { - setLSTDialogVariant(variant); - setIsLSTDialogOpen(true); - if (onClose) { - setLSTDialogCallback(() => onClose); - } - }} - /> - ))} + {globalBanks.map((bank) => { + if (poolFilter === "stable" && !STABLECOINS.includes(bank.meta.tokenSymbol)) return null; + if (poolFilter === "lst" && !LSTS.includes(bank.meta.tokenSymbol)) return null; + return ( + void) => { + setLSTDialogVariant(variant); + setIsLSTDialogOpen(true); + if (onClose) { + setLSTDialogCallback(() => onClose); + } + }} + /> + ); + })}
) : ( @@ -129,7 +141,7 @@ export const MobileAssetsList = () => { )}
)} - {poolFilter === "isolated" && ( + {poolFilter !== "stable" && poolFilter !== "lst" && (
Isolated pools @@ -153,17 +165,19 @@ export const MobileAssetsList = () => { {isStoreInitialized && globalBanks ? ( isolatedBanks.length > 0 ? (
- {isolatedBanks.map((bank, i) => ( - - ))} + {isolatedBanks.map((bank, i) => { + return ( + + ); + })}
) : ( From bf45604094338bb169a1a977a9ae17b2580dc489 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Fri, 24 Nov 2023 19:36:52 -0500 Subject: [PATCH 34/93] chore: switch tvl to $ in asset list filters --- apps/marginfi-v2-ui/src/store/uiStore.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/marginfi-v2-ui/src/store/uiStore.ts b/apps/marginfi-v2-ui/src/store/uiStore.ts index a894877e1c..7ead4b1942 100644 --- a/apps/marginfi-v2-ui/src/store/uiStore.ts +++ b/apps/marginfi-v2-ui/src/store/uiStore.ts @@ -18,13 +18,13 @@ const SORT_OPTIONS_MAP: { [key in SortType]: SortAssetOption } = { direction: sortDirection.ASC, }, TVL_DESC: { - label: "TVL highest to lowest", + label: "$ highest to lowest", value: SortType.TVL_DESC, field: "TVL", direction: sortDirection.DESC, }, TVL_ASC: { - label: "TVL lowest to highest", + label: "$ lowest to highest", value: SortType.TVL_ASC, field: "TVL", direction: sortDirection.ASC, From dd243799cbf8d5505b16068817f224f801d05db8 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Fri, 24 Nov 2023 20:04:45 -0500 Subject: [PATCH 35/93] chore: change name of asset banner local storage val --- .../src/components/common/AssetList/NewAsssetBanner.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx b/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx index ec0aa5a70d..9abaee1a13 100644 --- a/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx +++ b/apps/marginfi-v2-ui/src/components/common/AssetList/NewAsssetBanner.tsx @@ -58,11 +58,11 @@ export const NewAssetBanner = ({ asset, image }: NewAssetBannerProps) => { const hideBanner = React.useCallback(() => { setIsHidden(true); - localStorage.setItem("mfiHideNewAssetBanner", "true"); + localStorage.setItem("mrgnNewAssetAcknowledged", "true"); }, [setIsHidden]); React.useEffect(() => { - const isHidden = localStorage.getItem("mfiHideNewAssetBanner"); + const isHidden = localStorage.getItem("mrgnNewAssetAcknowledged"); if (!isHidden) setIsHidden(false); }, []); From b5d7090599d3943eff858b9128c39e0dd06f7cd6 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Fri, 24 Nov 2023 20:05:18 -0500 Subject: [PATCH 36/93] fix: add asset-row data attr to mobile asset cards to fix scroll / highlight --- .../mobile/MobileAssetsList/AssetCard/AssetCard.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/AssetCard/AssetCard.tsx b/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/AssetCard/AssetCard.tsx index e38b7da734..05f051e752 100644 --- a/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/AssetCard/AssetCard.tsx +++ b/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/AssetCard/AssetCard.tsx @@ -154,7 +154,10 @@ export const AssetCard: FC<{ ); return ( -
+
Date: Fri, 24 Nov 2023 20:06:04 -0500 Subject: [PATCH 37/93] chore: remove debugs --- .../DesktopAccountSummary.tsx | 11 ++++++----- apps/marginfi-v2-ui/src/pages/index.tsx | 16 ++++++++++------ .../src/models/account/pure.ts | 4 ---- .../marginfi-v2-ui-state/src/lib/firebase.ts | 1 - 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/desktop/DesktopAccountSummary/DesktopAccountSummary.tsx b/apps/marginfi-v2-ui/src/components/desktop/DesktopAccountSummary/DesktopAccountSummary.tsx index 09437627b3..9833d0b64d 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/DesktopAccountSummary/DesktopAccountSummary.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/DesktopAccountSummary/DesktopAccountSummary.tsx @@ -1,15 +1,18 @@ -import React, { FC } from "react"; +import React from "react"; + import dynamic from "next/dynamic"; import { useMrgnlendStore } from "~/store"; +import { cn } from "~/utils"; import { useWalletContext } from "~/hooks/useWalletContext"; + import { UserStats } from "~/components/common/AccountSummary"; const GlobalStats = dynamic(async () => (await import("~/components/common/AccountSummary/GlobalStats")).GlobalStats, { ssr: false, }); -const AccountSummary: FC = () => { +const AccountSummary = () => { const [isStoreInitialized, accountSummary, protocolStats, selectedAccount] = useMrgnlendStore((state) => [ state.initialized, state.accountSummary, @@ -18,10 +21,8 @@ const AccountSummary: FC = () => { ]); const { connected } = useWalletContext(); - // console.log(isStoreInitialized, accountSummary, protocolStats, selectedAccount); - return ( -
+
Global stats diff --git a/apps/marginfi-v2-ui/src/pages/index.tsx b/apps/marginfi-v2-ui/src/pages/index.tsx index 7b4fb20b66..fd88bd2c31 100644 --- a/apps/marginfi-v2-ui/src/pages/index.tsx +++ b/apps/marginfi-v2-ui/src/pages/index.tsx @@ -34,18 +34,22 @@ const Home = () => { const { walletAddress, wallet, isOverride } = useWalletContext(); const { connection } = useConnection(); const debounceId = React.useRef(null); - const [fetchMrgnlendState, setIsRefreshingStore, marginfiAccounts, selectedAccount] = useMrgnlendStore((state) => [ + const [ + fetchMrgnlendState, + isStoreInitialized, + isRefreshingStore, + setIsRefreshingStore, + marginfiAccounts, + selectedAccount, + ] = useMrgnlendStore((state) => [ state.fetchMrgnlendState, + state.initialized, + state.isRefreshingStore, state.setIsRefreshingStore, state.marginfiAccounts, state.selectedAccount, ]); - const [isStoreInitialized, isRefreshingStore] = useMrgnlendStore((state) => [ - state.initialized, - state.isRefreshingStore, - ]); - React.useEffect(() => { const fetchData = () => { setIsRefreshingStore(true); diff --git a/packages/marginfi-client-v2/src/models/account/pure.ts b/packages/marginfi-client-v2/src/models/account/pure.ts index 8157e2d65b..dd0d41566f 100644 --- a/packages/marginfi-client-v2/src/models/account/pure.ts +++ b/packages/marginfi-client-v2/src/models/account/pure.ts @@ -302,13 +302,9 @@ class MarginfiAccount { // apply volatility factor to avoid failure due to price volatility / slippage const initUntiedCollateralForBank = freeCollateral.times(_volatilityFactor); - console.log("initUntiedCollateralForBank", initUntiedCollateralForBank.toFixed(6)); const priceLowestBias = bank.getPrice(priceInfo, PriceBias.Lowest); - console.log("priceLowestBias", priceLowestBias.toFixed(6)); const initWeightedPrice = priceLowestBias.times(initAssetWeight); - console.log("initWeightedPrice", initWeightedPrice.toFixed(6)); const maxWithdraw = initUntiedCollateralForBank.div(initWeightedPrice); - console.log("maxWithdraw", maxWithdraw.toFixed(6)); return maxWithdraw; } diff --git a/packages/marginfi-v2-ui-state/src/lib/firebase.ts b/packages/marginfi-v2-ui-state/src/lib/firebase.ts index 644a2b1f9d..d806dcaf9e 100644 --- a/packages/marginfi-v2-ui-state/src/lib/firebase.ts +++ b/packages/marginfi-v2-ui-state/src/lib/firebase.ts @@ -284,7 +284,6 @@ async function signLoginTx( async function signinFirebaseAuth(token: string) { try { await signInWithCustomToken(auth, token); - console.log("Signed user in."); } catch (error: any) { console.error("Error signing in with custom token: ", error); if (error.code === "auth/network-request-failed") { From 14b0f348d2a443ecb236e697fad998044af2dcaf Mon Sep 17 00:00:00 2001 From: man0s <95379755+losman0s@users.noreply.github.com> Date: Thu, 23 Nov 2023 19:06:56 +0800 Subject: [PATCH 38/93] feat: add liquidation price to position data --- .../src/models/account/pure.ts | 44 ++++++++++++++++++- .../src/models/account/wrapper.ts | 6 +++ .../marginfi-v2-ui-state/src/lib/mrgnlend.ts | 29 ++++++------ 3 files changed, 64 insertions(+), 15 deletions(-) diff --git a/packages/marginfi-client-v2/src/models/account/pure.ts b/packages/marginfi-client-v2/src/models/account/pure.ts index dd0d41566f..16315cd88c 100644 --- a/packages/marginfi-client-v2/src/models/account/pure.ts +++ b/packages/marginfi-client-v2/src/models/account/pure.ts @@ -81,12 +81,16 @@ class MarginfiAccount { computeHealthComponents( banks: Map, oraclePrices: Map, - marginReqType: MarginRequirementType + marginReqType: MarginRequirementType, + excludedBanks: PublicKey[] = [] ): { assets: BigNumber; liabilities: BigNumber; } { - const [assets, liabilities] = this.activeBalances + const filteredBalances = this.activeBalances.filter( + (accountBalance) => !excludedBanks.find(b => b.equals(accountBalance.bankPk)) + ); + const [assets, liabilities] = filteredBalances .map((accountBalance) => { const bank = banks.get(accountBalance.bankPk.toBase58()); if (!bank) throw Error(`Bank ${shortenAddress(accountBalance.bankPk)} not found`); @@ -309,6 +313,42 @@ class MarginfiAccount { return maxWithdraw; } + /** + * Calculate the price at which the user position for the given bank will lead to liquidation, all other prices constant. + */ + public computeLiquidationPriceForBank( + banks: Map, + oraclePrices: Map, + bankAddress: PublicKey, + ): number | null { + const bank = banks.get(bankAddress.toBase58()); + if (!bank) throw Error(`Bank ${bankAddress.toBase58()} not found`); + const priceInfo = oraclePrices.get(bankAddress.toBase58()); + if (!priceInfo) throw Error(`Price info for ${bankAddress.toBase58()} not found`); + + const balance = this.getBalance(bankAddress); + + if (!balance.active) return null; + + const isLending = balance.liabilityShares.isZero(); + const { assets, liabilities } = this.computeHealthComponents(banks, oraclePrices, MarginRequirementType.Maintenance, [bankAddress]); + const { assets: assetQuantityUi, liabilities: liabQuantitiesUi } = balance.computeQuantityUi(bank); + + if (isLending) { + if (liabilities.eq(0)) return null; + + const assetWeight = bank.getAssetWeight(MarginRequirementType.Maintenance); + const priceConfidence = bank.getPrice(priceInfo, PriceBias.None).minus(bank.getPrice(priceInfo, PriceBias.Lowest)); + const liquidationPrice = liabilities.minus(assets).div(assetQuantityUi.times(assetWeight)).plus(priceConfidence); + return liquidationPrice.toNumber(); + } else { + const liabWeight = bank.getLiabilityWeight(MarginRequirementType.Maintenance); + const priceConfidence = bank.getPrice(priceInfo, PriceBias.Highest).minus(bank.getPrice(priceInfo, PriceBias.None)); + const liquidationPrice = assets.minus(liabilities).div(liabQuantitiesUi.times(liabWeight)).minus(priceConfidence); + return liquidationPrice.toNumber(); + } + } + // Calculate the max amount of collateral to liquidate to bring an account maint health to 0 (assuming negative health). // // The asset amount is bounded by 2 constraints, diff --git a/packages/marginfi-client-v2/src/models/account/wrapper.ts b/packages/marginfi-client-v2/src/models/account/wrapper.ts index 135c09686f..0251178524 100644 --- a/packages/marginfi-client-v2/src/models/account/wrapper.ts +++ b/packages/marginfi-client-v2/src/models/account/wrapper.ts @@ -160,6 +160,12 @@ class MarginfiAccountWrapper { ); } + public computeLiquidationPriceForBank( + bankAddress: PublicKey, + ): number | null { + return this._marginfiAccount.computeLiquidationPriceForBank(this.client.banks, this.client.oraclePrices, bankAddress); + } + public computeNetApy(): number { return this._marginfiAccount.computeNetApy(this.client.banks, this.client.oraclePrices); } diff --git a/packages/marginfi-v2-ui-state/src/lib/mrgnlend.ts b/packages/marginfi-v2-ui-state/src/lib/mrgnlend.ts index 9ec23192d9..83cd083f75 100644 --- a/packages/marginfi-v2-ui-state/src/lib/mrgnlend.ts +++ b/packages/marginfi-v2-ui-state/src/lib/mrgnlend.ts @@ -283,19 +283,18 @@ function makeExtendedBankInfo( } // Calculate user-specific info relevant to their active position in this bank - const position = makeLendingPosition(positionRaw, bank, bankInfo, oraclePrice); - const maxWithdraw = userData.marginfiAccount - ? floor( - Math.min( - userData.marginfiAccount - .computeMaxWithdrawForBank(bank.address, { volatilityFactor: VOLATILITY_FACTOR }) - .toNumber(), - bankInfo.availableLiquidity - ), - bankInfo.mintDecimals - ) - : 0; + const marginfiAccount = userData.marginfiAccount as MarginfiAccountWrapper; + + const position = makeLendingPosition(positionRaw, bank, bankInfo, oraclePrice, marginfiAccount); + + const maxWithdraw = floor( + Math.min( + marginfiAccount.computeMaxWithdrawForBank(bank.address, { volatilityFactor: VOLATILITY_FACTOR }).toNumber(), + bankInfo.availableLiquidity + ), + bankInfo.mintDecimals + ); let maxRepay = 0; if (position) { @@ -325,7 +324,8 @@ function makeLendingPosition( balance: Balance, bank: Bank, bankInfo: BankState, - oraclePrice: OraclePrice + oraclePrice: OraclePrice, + marginfiAccount: MarginfiAccountWrapper ): LendingPosition { const amounts = balance.computeQuantity(bank); const usdValues = balance.computeUsdValue(bank, oraclePrice, MarginRequirementType.Equity); @@ -338,11 +338,13 @@ function makeLendingPosition( const isDust = uiToNative(amount, bankInfo.mintDecimals).isZero(); const weightedUSDValue = isLending ? weightedUSDValues.assets.toNumber() : weightedUSDValues.liabilities.toNumber(); const usdValue = isLending ? usdValues.assets.toNumber() : usdValues.liabilities.toNumber(); + const liquidationPrice = marginfiAccount.computeLiquidationPriceForBank(bank.address); return { amount, usdValue, weightedUSDValue, + liquidationPrice, isLending, isDust, }; @@ -501,6 +503,7 @@ interface LendingPosition { amount: number; usdValue: number; weightedUSDValue: number; + liquidationPrice: number | null; isDust: boolean; } From c67ae5f73f3868c7ab7d3b6d3e481dd181f45a4b Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Fri, 24 Nov 2023 19:51:47 -0500 Subject: [PATCH 39/93] chore: fetch and log user positions in asset list --- .../desktop/AssetsList/AssetsList.tsx | 41 ++++++++++++++----- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx index d54c8debb2..a2d978928a 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx @@ -1,15 +1,18 @@ -import React, { FC, useEffect, useRef, useState } from "react"; +import React from "react"; + import dynamic from "next/dynamic"; import Image from "next/image"; + import { useHotkeys } from "react-hotkeys-hook"; -import { ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; import { Card, Table, TableHead, TableBody, TableContainer, TableCell, TableRow } from "@mui/material"; import Typography from "@mui/material/Typography"; +import { ExtendedBankInfo, ActiveBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; + import { useMrgnlendStore, useUserProfileStore, useUiStore } from "~/store"; import { useWalletContext } from "~/hooks/useWalletContext"; -import { LoadingAsset, AssetRow } from "./AssetRow"; +import { LoadingAsset, AssetRow } from "~/components/desktop/AssetsList/AssetRow"; import { LSTDialog, LSTDialogVariants, @@ -27,7 +30,7 @@ const UserPositions = dynamic(async () => (await import("~/components/desktop/Us ssr: false, }); -const AssetsList: FC = () => { +const AssetsList = () => { // const { selectedAccount, nativeSolBalance } = useStore(); const { connected, walletAddress } = useWalletContext(); const [isStoreInitialized, extendedBankInfos, nativeSolBalance, selectedAccount] = useMrgnlendStore((state) => [ @@ -50,11 +53,11 @@ const AssetsList: FC = () => { state.setSortOption, ]); - const inputRefs = useRef>({}); - const [isHotkeyMode, setIsHotkeyMode] = useState(false); - const [isLSTDialogOpen, setIsLSTDialogOpen] = useState(false); - const [lstDialogVariant, setLSTDialogVariant] = useState(null); - const [lstDialogCallback, setLSTDialogCallback] = useState<(() => void) | null>(null); + const inputRefs = React.useRef>({}); + const [isHotkeyMode, setIsHotkeyMode] = React.useState(false); + const [isLSTDialogOpen, setIsLSTDialogOpen] = React.useState(false); + const [lstDialogVariant, setLSTDialogVariant] = React.useState(null); + const [lstDialogCallback, setLSTDialogCallback] = React.useState<(() => void) | null>(null); const isInLendingMode = React.useMemo(() => lendingMode === LendingModes.LEND, [lendingMode]); @@ -99,6 +102,22 @@ const AssetsList: FC = () => { } }, [isStoreInitialized, extendedBankInfos, sortOption, isFilteredUserPositions, sortBanks]); + const activeBankInfos = React.useMemo( + () => extendedBankInfos.filter((balance) => balance.isActive), + [extendedBankInfos] + ) as ActiveBankInfo[]; + + const { lendPositions, borrowPositions } = React.useMemo( + () => ({ + lendPositions: activeBankInfos.filter((bankInfo) => bankInfo.position.isLending), + borrowPositions: activeBankInfos.filter((bankInfo) => !bankInfo.position.isLending), + }), + [activeBankInfos] + ); + + console.log("lendPositions", lendPositions); + console.log("borrowPositions", borrowPositions); + // Enter hotkey mode useHotkeys( "meta + k", @@ -154,8 +173,8 @@ const AssetsList: FC = () => { ); // Hack required to circumvent rehydration error - const [hasMounted, setHasMounted] = useState(false); - useEffect(() => { + const [hasMounted, setHasMounted] = React.useState(false); + React.useEffect(() => { setHasMounted(true); }, []); if (!hasMounted) { From 63df0732ebb5948839be713473de4ac7b891bcde Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Fri, 24 Nov 2023 20:38:45 -0500 Subject: [PATCH 40/93] feat: conditionally render position row and print out user position details (wip) --- .../desktop/AssetsList/AssetRow/AssetRow.tsx | 531 ++++++++++-------- .../desktop/AssetsList/AssetsList.tsx | 11 +- apps/marginfi-v2-ui/src/styles/globals.css | 2 +- 3 files changed, 297 insertions(+), 247 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx index 37992eef1c..87096c9944 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx @@ -5,7 +5,13 @@ import { TableCell, TableRow, Tooltip, Typography } from "@mui/material"; import { useMrgnlendStore, useUserProfileStore } from "~/store"; import Badge from "@mui/material/Badge"; import { WSOL_MINT, numeralFormatter, percentFormatter, usdFormatter } from "@mrgnlabs/mrgn-common"; -import { ExtendedBankInfo, ActionType, getCurrentAction, ExtendedBankMetadata } from "@mrgnlabs/marginfi-v2-ui-state"; +import { + ExtendedBankInfo, + ActiveBankInfo, + ActionType, + getCurrentAction, + ExtendedBankMetadata, +} from "@mrgnlabs/marginfi-v2-ui-state"; import { MarginfiAccountWrapper, PriceBias } from "@mrgnlabs/marginfi-client-v2"; import { MrgnTooltip } from "~/components/common/MrgnTooltip"; import { AssetRowInputBox, AssetRowAction, LSTDialogVariants } from "~/components/common/AssetList"; @@ -40,6 +46,7 @@ const AssetRow: FC<{ hasHotkey: boolean; showHotkeyBadges?: boolean; badgeContent?: string; + userPosition?: ActiveBankInfo; showLSTDialog?: (variant: LSTDialogVariants, callback?: () => void) => void; }> = ({ bank, @@ -50,6 +57,7 @@ const AssetRow: FC<{ hasHotkey, showHotkeyBadges, badgeContent, + userPosition, showLSTDialog, }) => { const [lendZoomLevel, denominationUSD] = useUserProfileStore((state) => [state.lendZoomLevel, state.denominationUSD]); @@ -207,275 +215,310 @@ const AssetRow: FC<{ ]); return ( - - -
- {bank.meta.tokenLogoUri && ( - {bank.meta.tokenSymbol} - )} -
{bank.meta.tokenSymbol}
-
-
- - - - -
- {bank.info.state.emissionsRate > 0 && - EMISSION_MINT_INFO_MAP.get(bank.meta.tokenSymbol) !== undefined && - isInLendingMode && ( -
- - - Liquidity rewards - - {`${percentFormatter.format(bank.info.state.lendingRate)} Supply APY + ${percentFormatter.format( - bank.info.state.emissionsRate - )} ${EMISSION_MINT_INFO_MAP.get(bank.meta.tokenSymbol)!.tokenSymbol} rewards.`} -
- - Learn more. - - - } - placement="left" - > - info -
-
+
+ {bank.meta.tokenLogoUri && ( + {bank.meta.tokenSymbol} )} -
- {rateAP} +
{bank.meta.tokenSymbol}
-
- - - - {assetWeight} - + - + assetPrice * 0.1 ? "⚠️" : ""} + className="bg-transparent" + sx={{ + "& .MuiBadge-badge": { + fontSize: 20, + }, + }} + invisible={assetPriceOffset > assetPrice * 0.1 ? false : true} + > + {assetPrice >= 0.01 + ? lendZoomLevel < 2 + ? `${ + assetPrice > 9999 ? numeralFormatter(assetPrice) : usdFormatter.format(assetPrice) + } ± ${assetPriceOffset.toFixed(2)}` + : usdFormatter.format(assetPrice) + : `$${assetPrice.toExponential(2)}`} + + + - {/*******************************/} - {/* [START]: ZOOM-BASED COLUMNS */} - {/*******************************/} + +
+ {bank.info.state.emissionsRate > 0 && + EMISSION_MINT_INFO_MAP.get(bank.meta.tokenSymbol) !== undefined && + isInLendingMode && ( +
+ + + Liquidity rewards + + {`${percentFormatter.format( + bank.info.state.lendingRate + )} Supply APY + ${percentFormatter.format(bank.info.state.emissionsRate)} ${ + EMISSION_MINT_INFO_MAP.get(bank.meta.tokenSymbol)!.tokenSymbol + } rewards.`} +
+ + Learn more. + + + } + placement="left" + > + info +
+
+ )} +
+ {rateAP} +
+
+
- {lendZoomLevel < 2 && ( - {denominationUSD ? usdFormatter.format(bankCap * bank.info.state.price) : numeralFormatter(bankCap)} + {assetWeight} - )} - {lendZoomLevel < 3 && ( - )} - {/*******************************/} - {/* [END]: ZOOM-BASED COLUMNS */} - {/*******************************/} + {/*******************************/} + {/* [START]: ZOOM-BASED COLUMNS */} + {/*******************************/} - - {denominationUSD - ? usdFormatter.format( - (bank.info.state.mint.equals(WSOL_MINT) - ? bank.userInfo.tokenAccount.balance + nativeSolBalance - : bank.userInfo.tokenAccount.balance) * bank.info.state.price - ) - : numeralFormatter( - bank.info.state.mint.equals(WSOL_MINT) - ? bank.userInfo.tokenAccount.balance + nativeSolBalance - : bank.userInfo.tokenAccount.balance - )} - + {lendZoomLevel < 2 && ( + + {denominationUSD ? usdFormatter.format(bankCap * bank.info.state.price) : numeralFormatter(bankCap)} + + )} - - + {percentFormatter.format(bank.info.state.utilizationRate / 100)} + + )} + + {/*******************************/} + {/* [END]: ZOOM-BASED COLUMNS */} + {/*******************************/} + + - - - + {denominationUSD + ? usdFormatter.format( + (bank.info.state.mint.equals(WSOL_MINT) + ? bank.userInfo.tokenAccount.balance + nativeSolBalance + : bank.userInfo.tokenAccount.balance) * bank.info.state.price + ) + : numeralFormatter( + bank.info.state.mint.equals(WSOL_MINT) + ? bank.userInfo.tokenAccount.balance + nativeSolBalance + : bank.userInfo.tokenAccount.balance + )} + - - + + + + + + + +
+ + {showCloseBalance ? "Close" : currentAction} + +
+
+
+ + {userPosition && ( + -
- - {showCloseBalance ? "Close" : currentAction} - -
- - -
+ +
+

Your position details

+
+
{userPosition.position.isLending ? "Lending" : "Borrowing"}
+
{userPosition.position.amount}
+
USD Value
+
{userPosition.position.usdValue}
+ {userPosition.position.liquidationPrice && userPosition.position.liquidationPrice > 0 && ( + <> +
Liq price
+
{userPosition.position.liquidationPrice}
+ + )} +
+
+
+ + )} + + ); }; diff --git a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx index a2d978928a..0f0d9caec2 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx @@ -195,7 +195,7 @@ const AssetsList = () => { className="table-fixed" style={{ borderCollapse: "separate", - borderSpacing: "0px 8px", + borderSpacing: "0px 0px", }} > @@ -386,6 +386,12 @@ const AssetsList = () => { .map((bank, i) => { if (poolFilter === "stable" && !STABLECOINS.includes(bank.meta.tokenSymbol)) return null; if (poolFilter === "lst" && !LSTS.includes(bank.meta.tokenSymbol)) return null; + + // check to see if bank is in open positions + const userPosition = activeBankInfos.filter( + (activeBankInfo) => activeBankInfo.meta.tokenSymbol === bank.meta.tokenSymbol + ); + return isStoreInitialized ? ( { hasHotkey={true} showHotkeyBadges={showBadges} badgeContent={`${i + 1}`} + userPosition={userPosition.length > 0 ? userPosition[0] : undefined} showLSTDialog={(variant: LSTDialogVariants, onClose?: () => void) => { setLSTDialogVariant(variant); setIsLSTDialogOpen(true); @@ -418,7 +425,7 @@ const AssetsList = () => { )} {poolFilter !== "stable" && poolFilter !== "lst" && ( - +
diff --git a/apps/marginfi-v2-ui/src/styles/globals.css b/apps/marginfi-v2-ui/src/styles/globals.css index 72feecca31..7642522b12 100644 --- a/apps/marginfi-v2-ui/src/styles/globals.css +++ b/apps/marginfi-v2-ui/src/styles/globals.css @@ -27,7 +27,7 @@ --muted-highlight: 203 15% 15%; --muted-foreground: 240 5% 64.9%; - --accent: 240 4% 16%; + --accent: 210 10% 15%; --accent-highlight: 240 4% 21%; --accent-foreground: 0 0% 98%; From 14e612436e43fd59b41c2c6e3bfc59c769a056b2 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Fri, 24 Nov 2023 20:54:16 -0500 Subject: [PATCH 41/93] feat: style inline user positions --- .../desktop/AssetsList/AssetRow/AssetRow.tsx | 30 ++++++++++++++----- apps/marginfi-v2-ui/src/styles/globals.css | 2 +- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx index 87096c9944..c52448999d 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx @@ -17,7 +17,7 @@ import { MrgnTooltip } from "~/components/common/MrgnTooltip"; import { AssetRowInputBox, AssetRowAction, LSTDialogVariants } from "~/components/common/AssetList"; import { useAssetItemData } from "~/hooks/useAssetItemData"; import { useWalletContext } from "~/hooks/useWalletContext"; -import { closeBalance, executeLendingAction, MarginfiActionParams } from "~/utils"; +import { closeBalance, executeLendingAction, MarginfiActionParams, cn } from "~/utils"; export const EMISSION_MINT_INFO_MAP = new Map([ [ @@ -501,15 +501,29 @@ const AssetRow: FC<{ >

Your position details

-
-
{userPosition.position.isLending ? "Lending" : "Borrowing"}
-
{userPosition.position.amount}
-
USD Value
-
{userPosition.position.usdValue}
+
+
{userPosition.position.isLending ? "Lending" : "Borrowing"}
+
+ {userPosition.position.amount < 0.01 && "< "} + {numeralFormatter(userPosition.position.amount)} +
+
USD Value
+
0 && + "pr-4 border-accent-foreground/50 border-r" + )} + > + {usdFormatter.format(userPosition.position.usdValue)} +
{userPosition.position.liquidationPrice && userPosition.position.liquidationPrice > 0 && ( <> -
Liq price
-
{userPosition.position.liquidationPrice}
+
Liq price
+
+ {usdFormatter.format(userPosition.position.liquidationPrice)} +
)}
diff --git a/apps/marginfi-v2-ui/src/styles/globals.css b/apps/marginfi-v2-ui/src/styles/globals.css index 7642522b12..9ce4222bb2 100644 --- a/apps/marginfi-v2-ui/src/styles/globals.css +++ b/apps/marginfi-v2-ui/src/styles/globals.css @@ -29,7 +29,7 @@ --accent: 210 10% 15%; --accent-highlight: 240 4% 21%; - --accent-foreground: 0 0% 98%; + --accent-foreground: 213 5% 50%; --destructive: #e06d6f1a; --destructive-foreground: 359 65% 65%; From f4de64431070cad157794de2431efed20faa37c8 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Fri, 24 Nov 2023 20:56:55 -0500 Subject: [PATCH 42/93] chore: override text focus color in select ui component --- .../src/components/ui/select.tsx | 59 +++++++------------ 1 file changed, 21 insertions(+), 38 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/ui/select.tsx b/apps/marginfi-v2-ui/src/components/ui/select.tsx index 3165147fc5..2ed74db199 100644 --- a/apps/marginfi-v2-ui/src/components/ui/select.tsx +++ b/apps/marginfi-v2-ui/src/components/ui/select.tsx @@ -1,14 +1,14 @@ -import * as React from "react" -import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons" -import * as SelectPrimitive from "@radix-ui/react-select" +import * as React from "react"; +import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons"; +import * as SelectPrimitive from "@radix-ui/react-select"; -import { cn } from "~/utils/themeUtils" +import { cn } from "~/utils/themeUtils"; -const Select = SelectPrimitive.Root +const Select = SelectPrimitive.Root; -const SelectGroup = SelectPrimitive.Group +const SelectGroup = SelectPrimitive.Group; -const SelectValue = SelectPrimitive.Value +const SelectValue = SelectPrimitive.Value; const SelectTrigger = React.forwardRef< React.ElementRef, @@ -27,8 +27,8 @@ const SelectTrigger = React.forwardRef< -)) -SelectTrigger.displayName = SelectPrimitive.Trigger.displayName +)); +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; const SelectContent = React.forwardRef< React.ElementRef, @@ -57,20 +57,16 @@ const SelectContent = React.forwardRef< -)) -SelectContent.displayName = SelectPrimitive.Content.displayName +)); +SelectContent.displayName = SelectPrimitive.Content.displayName; const SelectLabel = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) -SelectLabel.displayName = SelectPrimitive.Label.displayName + +)); +SelectLabel.displayName = SelectPrimitive.Label.displayName; const SelectItem = React.forwardRef< React.ElementRef, @@ -79,7 +75,7 @@ const SelectItem = React.forwardRef< {children} -)) -SelectItem.displayName = SelectPrimitive.Item.displayName +)); +SelectItem.displayName = SelectPrimitive.Item.displayName; const SelectSeparator = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) -SelectSeparator.displayName = SelectPrimitive.Separator.displayName + +)); +SelectSeparator.displayName = SelectPrimitive.Separator.displayName; -export { - Select, - SelectGroup, - SelectValue, - SelectTrigger, - SelectContent, - SelectLabel, - SelectItem, - SelectSeparator, -} +export { Select, SelectGroup, SelectValue, SelectTrigger, SelectContent, SelectLabel, SelectItem, SelectSeparator }; From fa14b003ca44c2aba0719e0a79e7c6043f3bee84 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Fri, 24 Nov 2023 21:44:54 -0500 Subject: [PATCH 43/93] feat: add token symbol to inline position / add liq price back to lending pos --- .../desktop/AssetsList/AssetRow/AssetRow.tsx | 63 ++++++++++--------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx index c52448999d..dffe7b694a 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx @@ -505,7 +505,7 @@ const AssetRow: FC<{
{userPosition.position.isLending ? "Lending" : "Borrowing"}
{userPosition.position.amount < 0.01 && "< "} - {numeralFormatter(userPosition.position.amount)} + {numeralFormatter(userPosition.position.amount) + " " + bank.meta.tokenSymbol}
USD Value
( - - -
- {bankMetadata.tokenLogoUri && ( - - )} -
{bankMetadata.tokenSymbol}
-
-
- - - - - - - - - - - - - {}} disabled={true} /> - - -
- {isInLendingMode ? "Supply" : "Borrow"} -
-
-
+ <> + + +
+ {bankMetadata.tokenLogoUri && ( + + )} +
{bankMetadata.tokenSymbol}
+
+
+ + - + - + - + - + - + + {}} disabled={true} /> + + +
+ {isInLendingMode ? "Supply" : "Borrow"} +
+
+
+ + ); export { AssetRow, LoadingAsset }; From 18b81b5c81898a22866437374949b1aa8931a582 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Fri, 24 Nov 2023 21:48:10 -0500 Subject: [PATCH 44/93] feat: remove user positions and update user positions filter --- .../desktop/AssetsList/AssetRow/AssetRow.tsx | 1 + .../desktop/AssetsList/AssetsList.tsx | 926 +++++++++--------- 2 files changed, 460 insertions(+), 467 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx index dffe7b694a..6a194a6d93 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx @@ -218,6 +218,7 @@ const AssetRow: FC<{ <> (await import("~/components/desktop/UserPositions")).UserPositions, { - ssr: false, -}); - const AssetsList = () => { - // const { selectedAccount, nativeSolBalance } = useStore(); - const { connected, walletAddress } = useWalletContext(); + const { connected } = useWalletContext(); const [isStoreInitialized, extendedBankInfos, nativeSolBalance, selectedAccount] = useMrgnlendStore((state) => [ state.initialized, state.extendedBankInfos, @@ -183,480 +178,477 @@ const AssetsList = () => { return ( <> - <> - - - {!isFilteredUserPositions && ( -
- - - {poolFilter !== "isolated" && ( -
- - - -
- Global pool -
-
- + +
+ + + {poolFilter !== "isolated" && ( +
+ + + +
+ Global pool +
+
+ +
+ Price + + + Realtime prices + + + Powered by Pyth and Switchboard. + + + } + placement="top" > -
- Price - - - Realtime prices - - - Powered by Pyth and Switchboard. - - - } - placement="top" - > - - -
- - +
+
+
+ +
+ {isInLendingMode ? "APY" : "APR"} + + + {isInLendingMode ? "APY" : "APR"} + + + {isInLendingMode + ? "What you'll earn on deposits over a year. This includes compounding. All marginfi deposits are compounded hourly." + : "What you'll pay for your borrows, or the price of a loan. This does not include compounding. All marginfi borrows are compounded hourly."} + + + } + placement="top" > -
- {isInLendingMode ? "APY" : "APR"} - - - {isInLendingMode ? "APY" : "APR"} - - - {isInLendingMode - ? "What you'll earn on deposits over a year. This includes compounding. All marginfi deposits are compounded hourly." - : "What you'll pay for your borrows, or the price of a loan. This does not include compounding. All marginfi borrows are compounded hourly."} - - - } - placement="top" - > - - -
- - +
+
+
+ +
+ {isInLendingMode ? "Weight" : "LTV"} + + + {isInLendingMode ? "Weight" : "LTV"} + + + {isInLendingMode + ? "How much your assets count for collateral, relative to their USD value. The higher the weight, the more collateral you can borrow against it." + : "How much you can borrow against your free collateral. The higher the LTV, the more you can borrow against your free collateral."} + + + } + placement="top" > -
- {isInLendingMode ? "Weight" : "LTV"} - - - {isInLendingMode ? "Weight" : "LTV"} - - - {isInLendingMode - ? "How much your assets count for collateral, relative to their USD value. The higher the weight, the more collateral you can borrow against it." - : "How much you can borrow against your free collateral. The higher the LTV, the more you can borrow against your free collateral."} - - - } - placement="top" - > - - -
- - +
+
+
+ +
+ {isInLendingMode ? "Deposits" : "Available"} + + + {isInLendingMode ? "Total deposits" : "Total available"} + + + {isInLendingMode + ? "Total marginfi deposits for each asset. Everything is denominated in native tokens." + : "The amount of tokens available to borrow for each asset. Calculated as the minimum of the asset's borrow limit and available liquidity that has not yet been borrowed."} + + + } + placement="top" > -
- {isInLendingMode ? "Deposits" : "Available"} - - - {isInLendingMode ? "Total deposits" : "Total available"} - - - {isInLendingMode - ? "Total marginfi deposits for each asset. Everything is denominated in native tokens." - : "The amount of tokens available to borrow for each asset. Calculated as the minimum of the asset's borrow limit and available liquidity that has not yet been borrowed."} - - - } - placement="top" - > - - -
- - - {/*******************************/} - {/* [START]: ZOOM-BASED COLUMNS */} - {/*******************************/} - - {lendZoomLevel < 2 && ( - +
+
+
+ + {/*******************************/} + {/* [START]: ZOOM-BASED COLUMNS */} + {/*******************************/} + + {lendZoomLevel < 2 && ( + +
+ Global limit + + + {isInLendingMode ? "Global deposit cap" : "Global borrow cap"} + + Each marginfi pool has global deposit and borrow limits, also known as caps. This is the + total amount that all users combined can deposit or borrow of a given token. + + } + placement="top" > -
- Global limit - - - {isInLendingMode ? "Global deposit cap" : "Global borrow cap"} - - Each marginfi pool has global deposit and borrow limits, also known as caps. This is - the total amount that all users combined can deposit or borrow of a given token. - - } - placement="top" - > - - -
- - )} - - {lendZoomLevel < 3 && ( - +
+
+
+ )} + + {lendZoomLevel < 3 && ( + +
+ Utilization + + + Pool utilization + + What percentage of supplied tokens have been borrowed. This helps determine interest + rates. This is not based on the global pool limits, which can limit utilization. + + } + placement="top" > -
- Utilization - - - Pool utilization - - What percentage of supplied tokens have been borrowed. This helps determine interest - rates. This is not based on the global pool limits, which can limit utilization. - - } - placement="top" - > - - -
- - )} - - {/*******************************/} - {/* [END]: ZOOM-BASED COLUMNS */} - {/*******************************/} - - +
+
+
+ )} + + {/*******************************/} + {/* [END]: ZOOM-BASED COLUMNS */} + {/*******************************/} + + +
Wallet Amt.
+
+ + +
+
+ + + {globalBanks + .filter((b) => !b.info.state.isIsolated) + .map((bank, i) => { + if (poolFilter === "stable" && !STABLECOINS.includes(bank.meta.tokenSymbol)) return null; + if (poolFilter === "lst" && !LSTS.includes(bank.meta.tokenSymbol)) return null; + + // check to see if bank is in open positions + const userPosition = activeBankInfos.filter( + (activeBankInfo) => activeBankInfo.meta.tokenSymbol === bank.meta.tokenSymbol + ); + + if (isFilteredUserPositions && !userPosition.length) return null; + + return isStoreInitialized ? ( + 0 ? userPosition[0] : undefined} + showLSTDialog={(variant: LSTDialogVariants, onClose?: () => void) => { + setLSTDialogVariant(variant); + setIsLSTDialogOpen(true); + if (onClose) { + setLSTDialogCallback(() => onClose); + } + }} + /> + ) : ( + + ); + })} + +
+ )} + {poolFilter !== "stable" && poolFilter !== "lst" && ( + + + + +
+ + Isolated pools + + + + Isolated pools are risky ⚠️ + + Assets in isolated pools cannot be used as collateral. When you borrow an isolated asset, + you cannot borrow other assets. Isolated pools should be considered particularly risky. As + always, remember that marginfi is a decentralized protocol and all deposited funds are at + risk. + + } + placement="top" > -
Wallet Amt.
- - - - - - - - {globalBanks - .filter((b) => !b.info.state.isIsolated) - .map((bank, i) => { - if (poolFilter === "stable" && !STABLECOINS.includes(bank.meta.tokenSymbol)) return null; - if (poolFilter === "lst" && !LSTS.includes(bank.meta.tokenSymbol)) return null; - - // check to see if bank is in open positions - const userPosition = activeBankInfos.filter( - (activeBankInfo) => activeBankInfo.meta.tokenSymbol === bank.meta.tokenSymbol - ); - - return isStoreInitialized ? ( - 0 ? userPosition[0] : undefined} - showLSTDialog={(variant: LSTDialogVariants, onClose?: () => void) => { - setLSTDialogVariant(variant); - setIsLSTDialogOpen(true); - if (onClose) { - setLSTDialogCallback(() => onClose); - } - }} - /> - ) : ( - - ); - })} - -
- )} - {poolFilter !== "stable" && poolFilter !== "lst" && ( - - - - -
- - Isolated pools - - - - Isolated pools are risky ⚠️ - - Assets in isolated pools cannot be used as collateral. When you borrow an isolated - asset, you cannot borrow other assets. Isolated pools should be considered - particularly risky. As always, remember that marginfi is a decentralized protocol and - all deposited funds are at risk. - - } - placement="top" - > - - -
-
- + + + + +
+ Price + + + Realtime prices + + + Powered by Pyth and Switchboard. + + + } + placement="top" > -
- Price - - - Realtime prices - - - Powered by Pyth and Switchboard. - - - } - placement="top" - > - - -
- - +
+
+
+ +
+ {isInLendingMode ? "APY" : "APR"} + + + {isInLendingMode ? "APY" : "APR"} + + + {isInLendingMode + ? "What you'll earn on deposits over a year. This includes compounding. All marginfi deposits are compounded hourly." + : "What you'll pay for your borrows, or the price of a loan. This does not include compounding. All marginfi borrows are compounded hourly."} + + + } + placement="top" > -
- {isInLendingMode ? "APY" : "APR"} - - - {isInLendingMode ? "APY" : "APR"} - - - {isInLendingMode - ? "What you'll earn on deposits over a year. This includes compounding. All marginfi deposits are compounded hourly." - : "What you'll pay for your borrows, or the price of a loan. This does not include compounding. All marginfi borrows are compounded hourly."} - - - } - placement="top" - > - - -
- - +
+
+
+ +
+ {isInLendingMode ? "Weight" : "LTV"} + + + {isInLendingMode ? "Weight" : "LTV"} + + + {isInLendingMode + ? "How much your assets count for collateral, relative to their USD value. The higher the weight, the more collateral you can borrow against it." + : "How much you can borrow against your free collateral. The higher the LTV, the more you can borrow against your free collateral."} + + + } + placement="top" > -
- {isInLendingMode ? "Weight" : "LTV"} - - - {isInLendingMode ? "Weight" : "LTV"} - - - {isInLendingMode - ? "How much your assets count for collateral, relative to their USD value. The higher the weight, the more collateral you can borrow against it." - : "How much you can borrow against your free collateral. The higher the LTV, the more you can borrow against your free collateral."} - - - } - placement="top" - > - - -
- - +
+
+
+ +
+ {isInLendingMode ? "Deposits" : "Available"} + + + {isInLendingMode ? "Total deposits" : "Total available"} + + + {isInLendingMode + ? "Total marginfi deposits for each asset. Everything is denominated in native tokens." + : "The amount of tokens available to borrow for each asset. Calculated as the minimum of the asset's borrow limit and available liquidity that has not yet been borrowed."} + + + } + placement="top" > -
- {isInLendingMode ? "Deposits" : "Available"} - - - {isInLendingMode ? "Total deposits" : "Total available"} - - - {isInLendingMode - ? "Total marginfi deposits for each asset. Everything is denominated in native tokens." - : "The amount of tokens available to borrow for each asset. Calculated as the minimum of the asset's borrow limit and available liquidity that has not yet been borrowed."} - - - } - placement="top" - > - - -
- - - {/*******************************/} - {/* [START]: ZOOM-BASED COLUMNS */} - {/*******************************/} - - {lendZoomLevel < 2 && ( - +
+
+
+ + {/*******************************/} + {/* [START]: ZOOM-BASED COLUMNS */} + {/*******************************/} + + {lendZoomLevel < 2 && ( + +
+ Global limit + + + {isInLendingMode ? "Global deposit cap" : "Global borrow cap"} + + Each marginfi pool has global deposit and borrow limits, also known as caps. This is the + total amount that all users combined can deposit or borrow of a given token. + + } + placement="top" > -
- Global limit - - - {isInLendingMode ? "Global deposit cap" : "Global borrow cap"} - - Each marginfi pool has global deposit and borrow limits, also known as caps. This is - the total amount that all users combined can deposit or borrow of a given token. - - } - placement="top" - > - - -
- - )} - - {lendZoomLevel < 3 && ( - +
+
+
+ )} + + {lendZoomLevel < 3 && ( + +
+ Utilization + + + Pool utilization + + What percentage of supplied tokens have been borrowed. This helps determine interest + rates. This is not based on the global pool limits, which can limit utilization. + + } + placement="top" > -
- Utilization - - - Pool utilization - - What percentage of supplied tokens have been borrowed. This helps determine interest - rates. This is not based on the global pool limits, which can limit utilization. - - } - placement="top" - > - - -
- - )} - - {/*******************************/} - {/* [END]: ZOOM-BASED COLUMNS */} - {/*******************************/} - - -
Wallet Amt.
-
- - - - - - {isolatedBanks - .filter((b) => b.info.state.isIsolated) - .map((bank) => - isStoreInitialized ? ( - - ) : ( - - ) - )} - -
- )} - - -
- )} - {walletAddress && } - + info + +
+ + )} + + {/*******************************/} + {/* [END]: ZOOM-BASED COLUMNS */} + {/*******************************/} + + +
Wallet Amt.
+
+ + + + + + {isolatedBanks + .filter((b) => b.info.state.isIsolated) + .map((bank) => + isStoreInitialized ? ( + + ) : ( + + ) + )} + + + )} + + +
Date: Fri, 24 Nov 2023 21:57:30 -0500 Subject: [PATCH 45/93] feat: mobile inline positions --- .../AssetCard/AssetCardPosition.tsx | 57 ++++++++----------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/AssetCard/AssetCardPosition.tsx b/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/AssetCard/AssetCardPosition.tsx index 3c38831552..4989f28478 100644 --- a/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/AssetCard/AssetCardPosition.tsx +++ b/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/AssetCard/AssetCardPosition.tsx @@ -1,42 +1,31 @@ -import React, { FC, useState } from "react"; +import React from "react"; -import { usdFormatter } from "@mrgnlabs/mrgn-common"; +import { usdFormatter, numeralFormatter } from "@mrgnlabs/mrgn-common"; import { ActiveBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; -import ExpandLessIcon from "@mui/icons-material/ExpandLess"; -import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; - -export const AssetCardPosition: FC<{ - activeBank: ActiveBankInfo; -}> = ({ activeBank }) => { - const [isCollapsed, setIsCollapsed] = useState(true); +import { cn } from "~/utils"; +export const AssetCardPosition = ({ activeBank }: { activeBank: ActiveBankInfo }) => { return ( -
-
setIsCollapsed(!isCollapsed)}> -
Your position details
- {isCollapsed ? : } -
- {!isCollapsed && ( -
-
-
- {(activeBank as ActiveBankInfo).position.isLending ? "Lending" : "Borrowing"} -
-
- {(activeBank as ActiveBankInfo).position.amount.toFixed(activeBank.info.state.mintDecimals) + - " " + - activeBank.meta.tokenSymbol} -
-
-
-
USD value
-
- {usdFormatter.format((activeBank as ActiveBankInfo).position.usdValue)} -
-
-
- )} +
+

Your position details

+
+
{activeBank.position.isLending ? "Lending" : "Borrowing"}
+
+ {activeBank.position.amount < 0.01 && "< "} + {numeralFormatter(activeBank.position.amount) + " " + activeBank.meta.tokenSymbol} +
+
USD Value
+
{usdFormatter.format(activeBank.position.usdValue)}
+ {activeBank.position.liquidationPrice && activeBank.position.liquidationPrice > 0 && ( + <> +
Liq price
+
+ {usdFormatter.format(activeBank.position.liquidationPrice)} +
+ + )} +
); }; From f92d3a2b3f8ff24f7742f1f464d7920c4b95720b Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Fri, 24 Nov 2023 22:02:46 -0500 Subject: [PATCH 46/93] feat: more padding around asset row buttons --- .../src/components/desktop/AssetsList/AssetRow/AssetRow.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx index 6a194a6d93..f6bd1b8d6a 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx @@ -467,7 +467,7 @@ const AssetRow: FC<{ - + -
+

Your position details

{userPosition.position.isLending ? "Lending" : "Borrowing"}
From e3b13c85b5ff7cd37658242c8572af0f6fc3bb31 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Fri, 24 Nov 2023 22:23:39 -0500 Subject: [PATCH 47/93] chore: various inline position fixes / improvements --- .../desktop/AssetsList/AssetRow/AssetRow.tsx | 21 ++++++++++++++++--- .../AssetCard/AssetCardPosition.tsx | 6 +++--- apps/marginfi-v2-ui/src/hooks/useIsMobile.ts | 2 +- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx index f6bd1b8d6a..53a517e3fa 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx @@ -17,6 +17,7 @@ import { MrgnTooltip } from "~/components/common/MrgnTooltip"; import { AssetRowInputBox, AssetRowAction, LSTDialogVariants } from "~/components/common/AssetList"; import { useAssetItemData } from "~/hooks/useAssetItemData"; import { useWalletContext } from "~/hooks/useWalletContext"; +import { useIsMobile } from "~/hooks/useIsMobile"; import { closeBalance, executeLendingAction, MarginfiActionParams, cn } from "~/utils"; export const EMISSION_MINT_INFO_MAP = new Map([ @@ -66,6 +67,20 @@ const AssetRow: FC<{ const { rateAP, assetWeight, isBankFilled, isBankHigh, bankCap } = useAssetItemData({ bank, isInLendingMode }); const [hasLSTDialogShown, setHasLSTDialogShown] = useState([]); const { walletContextState } = useWalletContext(); + const isMobile = useIsMobile(); + + const userPositionColSpan = useMemo(() => { + if (isMobile) { + return 6; + } + if (lendZoomLevel === 3) { + return 9; + } + if (lendZoomLevel === 2) { + return 10; + } + return 11; + }, [isMobile, lendZoomLevel]); const assetPrice = useMemo( () => @@ -219,7 +234,7 @@ const AssetRow: FC<{ {userPosition.position.liquidationPrice && userPosition.position.liquidationPrice > 0 && ( <> -
Liq price
+
Liquidation price
{usdFormatter.format(userPosition.position.liquidationPrice)}
diff --git a/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/AssetCard/AssetCardPosition.tsx b/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/AssetCard/AssetCardPosition.tsx index 4989f28478..a53e333e29 100644 --- a/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/AssetCard/AssetCardPosition.tsx +++ b/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/AssetCard/AssetCardPosition.tsx @@ -7,9 +7,9 @@ import { cn } from "~/utils"; export const AssetCardPosition = ({ activeBank }: { activeBank: ActiveBankInfo }) => { return ( -
+

Your position details

-
+
{activeBank.position.isLending ? "Lending" : "Borrowing"}
{activeBank.position.amount < 0.01 && "< "} @@ -19,7 +19,7 @@ export const AssetCardPosition = ({ activeBank }: { activeBank: ActiveBankInfo }
{usdFormatter.format(activeBank.position.usdValue)}
{activeBank.position.liquidationPrice && activeBank.position.liquidationPrice > 0 && ( <> -
Liq price
+
Liquidation price
{usdFormatter.format(activeBank.position.liquidationPrice)}
diff --git a/apps/marginfi-v2-ui/src/hooks/useIsMobile.ts b/apps/marginfi-v2-ui/src/hooks/useIsMobile.ts index f4ec3b5849..b3710743cf 100644 --- a/apps/marginfi-v2-ui/src/hooks/useIsMobile.ts +++ b/apps/marginfi-v2-ui/src/hooks/useIsMobile.ts @@ -6,7 +6,7 @@ export const useIsMobile = (): boolean => { useLayoutEffect(() => { const updateSize = (): void => { - setIsMobile(window.innerWidth < 768); + setIsMobile(window.innerWidth < 1024); }; window.addEventListener("resize", debounce(updateSize, 250)); updateSize(); From a29a7fa482f186fd125b5fe2ce5bb9ad7ab73bcb Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Mon, 27 Nov 2023 09:33:04 -0500 Subject: [PATCH 48/93] feat: higlight inline positions with poor health --- .../desktop/AssetsList/AssetRow/AssetRow.tsx | 47 ++++++++++++++++--- .../desktop/AssetsList/AssetsList.tsx | 3 -- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx index 53a517e3fa..15917462b7 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx @@ -19,6 +19,7 @@ import { useAssetItemData } from "~/hooks/useAssetItemData"; import { useWalletContext } from "~/hooks/useWalletContext"; import { useIsMobile } from "~/hooks/useIsMobile"; import { closeBalance, executeLendingAction, MarginfiActionParams, cn } from "~/utils"; +import { IconAlertTriangle } from "~/components/ui/icons"; export const EMISSION_MINT_INFO_MAP = new Map([ [ @@ -69,6 +70,17 @@ const AssetRow: FC<{ const { walletContextState } = useWalletContext(); const isMobile = useIsMobile(); + const isUserPositionPoorHealth = useMemo(() => { + if (!userPosition || !userPosition.position.liquidationPrice) { + return false; + } + + const alertRange = userPosition.position.liquidationPrice * 0.05; + const lowerBound = userPosition.position.liquidationPrice - alertRange; + + return userPosition.position.isLending ? bank.info.state.price <= lowerBound : bank.info.state.price >= lowerBound; + }, [userPosition]); + const userPositionColSpan = useMemo(() => { if (isMobile) { return 6; @@ -515,13 +527,19 @@ const AssetRow: FC<{ fontWeight: 300, }} > -
+

Your position details

{userPosition.position.isLending ? "Lending" : "Borrowing"}
-
- {userPosition.position.amount < 0.01 && "< "} - {numeralFormatter(userPosition.position.amount) + " " + bank.meta.tokenSymbol} +
+ {userPosition.position.amount < 0.01 && "< 0.00"} + {userPosition.position.amount >= 0.01 && + numeralFormatter(userPosition.position.amount) + " " + bank.meta.tokenSymbol} + {userPosition.position.amount < 0.01 && ( + {userPosition.position.amount}} placement="top"> + info + + )}
USD Value
{userPosition.position.liquidationPrice && userPosition.position.liquidationPrice > 0 && ( <> -
Liquidation price
-
+
+ {isUserPositionPoorHealth && ( + + + + )} + Liquidation price +
+
{usdFormatter.format(userPosition.position.liquidationPrice)}
diff --git a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx index 65ef1bea51..f042948fe2 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx @@ -110,9 +110,6 @@ const AssetsList = () => { [activeBankInfos] ); - console.log("lendPositions", lendPositions); - console.log("borrowPositions", borrowPositions); - // Enter hotkey mode useHotkeys( "meta + k", From c19e347868076cac9a63e26ea5ac499777c01bd9 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Mon, 27 Nov 2023 10:05:25 -0500 Subject: [PATCH 49/93] fix: liq alert range logic --- .../desktop/AssetsList/AssetRow/AssetRow.tsx | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx index 15917462b7..076b74e2a5 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx @@ -75,10 +75,24 @@ const AssetRow: FC<{ return false; } - const alertRange = userPosition.position.liquidationPrice * 0.05; - const lowerBound = userPosition.position.liquidationPrice - alertRange; - - return userPosition.position.isLending ? bank.info.state.price <= lowerBound : bank.info.state.price >= lowerBound; + const alertRange = bank.info.state.price * 0.05; + const lowerBound = bank.info.state.price - alertRange; + const upperBound = bank.info.state.price + alertRange; + + console.log( + bank.meta.tokenName, + bank.info.state.price, + alertRange, + lowerBound, + upperBound, + userPosition.position.liquidationPrice + ); + + if (userPosition.position.isLending) { + return userPosition.position.liquidationPrice >= lowerBound; + } else { + return userPosition.position.liquidationPrice <= upperBound; + } }, [userPosition]); const userPositionColSpan = useMemo(() => { @@ -552,7 +566,7 @@ const AssetRow: FC<{ > {usdFormatter.format(userPosition.position.usdValue)} - {userPosition.position.liquidationPrice && userPosition.position.liquidationPrice > 0 && ( + {userPosition.position.liquidationPrice && ( <>
Date: Mon, 27 Nov 2023 10:26:50 -0500 Subject: [PATCH 50/93] chore: simplify alert logic --- .../desktop/AssetsList/AssetRow/AssetRow.tsx | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx index 076b74e2a5..1e541251f8 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx @@ -75,24 +75,7 @@ const AssetRow: FC<{ return false; } - const alertRange = bank.info.state.price * 0.05; - const lowerBound = bank.info.state.price - alertRange; - const upperBound = bank.info.state.price + alertRange; - - console.log( - bank.meta.tokenName, - bank.info.state.price, - alertRange, - lowerBound, - upperBound, - userPosition.position.liquidationPrice - ); - - if (userPosition.position.isLending) { - return userPosition.position.liquidationPrice >= lowerBound; - } else { - return userPosition.position.liquidationPrice <= upperBound; - } + return bank.info.state.price < userPosition.position.liquidationPrice * (1 + 0.05); }, [userPosition]); const userPositionColSpan = useMemo(() => { @@ -546,7 +529,7 @@ const AssetRow: FC<{
{userPosition.position.isLending ? "Lending" : "Borrowing"}
- {userPosition.position.amount < 0.01 && "< 0.00"} + {userPosition.position.amount < 0.01 && "< 0.01"} {userPosition.position.amount >= 0.01 && numeralFormatter(userPosition.position.amount) + " " + bank.meta.tokenSymbol} {userPosition.position.amount < 0.01 && ( From 20f9fac59e8458a5fd0dea689bbc72019a6d42a6 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Mon, 27 Nov 2023 11:03:28 -0500 Subject: [PATCH 51/93] feat: mobile liquidation price alert states --- .../desktop/AssetsList/AssetRow/AssetRow.tsx | 2 +- .../desktop/AssetsList/AssetsList.tsx | 8 -------- .../AssetCard/AssetCardPosition.tsx | 17 +++++++++++++++-- .../MobileAssetsList/MobileAssetsList.tsx | 1 + 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx index 1e541251f8..a0a2751fd5 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx @@ -549,7 +549,7 @@ const AssetRow: FC<{ > {usdFormatter.format(userPosition.position.usdValue)}
- {userPosition.position.liquidationPrice && ( + {userPosition.position.liquidationPrice && userPosition.position.liquidationPrice > 0 && ( <>
{ [extendedBankInfos] ) as ActiveBankInfo[]; - const { lendPositions, borrowPositions } = React.useMemo( - () => ({ - lendPositions: activeBankInfos.filter((bankInfo) => bankInfo.position.isLending), - borrowPositions: activeBankInfos.filter((bankInfo) => !bankInfo.position.isLending), - }), - [activeBankInfos] - ); - // Enter hotkey mode useHotkeys( "meta + k", diff --git a/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/AssetCard/AssetCardPosition.tsx b/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/AssetCard/AssetCardPosition.tsx index a53e333e29..cb8351abdc 100644 --- a/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/AssetCard/AssetCardPosition.tsx +++ b/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/AssetCard/AssetCardPosition.tsx @@ -5,9 +5,22 @@ import { ActiveBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; import { cn } from "~/utils"; -export const AssetCardPosition = ({ activeBank }: { activeBank: ActiveBankInfo }) => { +type AssetCardPositionProps = { + activeBank: ActiveBankInfo; + userPosition?: ActiveBankInfo; +}; + +export const AssetCardPosition = ({ activeBank, userPosition }: AssetCardPositionProps) => { + const isUserPositionPoorHealth = React.useMemo(() => { + if (!activeBank.position.amount || !activeBank.position.liquidationPrice) { + return false; + } + + return activeBank.info.state.price < activeBank.position.liquidationPrice * (1 + 0.05); + }, [userPosition]); + return ( -
+

Your position details

{activeBank.position.isLending ? "Lending" : "Borrowing"}
diff --git a/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/MobileAssetsList.tsx b/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/MobileAssetsList.tsx index 5fb1c72c0e..c72d287334 100644 --- a/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/MobileAssetsList.tsx +++ b/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/MobileAssetsList.tsx @@ -101,6 +101,7 @@ export const MobileAssetsList = () => { {globalBanks.map((bank) => { if (poolFilter === "stable" && !STABLECOINS.includes(bank.meta.tokenSymbol)) return null; if (poolFilter === "lst" && !LSTS.includes(bank.meta.tokenSymbol)) return null; + return ( Date: Mon, 27 Nov 2023 12:12:13 -0500 Subject: [PATCH 52/93] chore: inline pos details various fixes / improvements --- .../desktop/AssetsList/AssetRow/AssetRow.tsx | 160 ++++++++++-------- .../desktop/AssetsList/AssetsList.tsx | 17 +- .../MobileAssetsList/AssetCard/AssetCard.tsx | 17 +- .../MobileAssetsList/MobileAssetsList.tsx | 17 +- 4 files changed, 129 insertions(+), 82 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx index a0a2751fd5..ec2a941344 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx @@ -2,7 +2,7 @@ import React, { FC, useCallback, useEffect, useMemo, useState } from "react"; import clsx from "clsx"; import Image from "next/image"; import { TableCell, TableRow, Tooltip, Typography } from "@mui/material"; -import { useMrgnlendStore, useUserProfileStore } from "~/store"; +import { useMrgnlendStore, useUserProfileStore, useUiStore } from "~/store"; import Badge from "@mui/material/Badge"; import { WSOL_MINT, numeralFormatter, percentFormatter, usdFormatter } from "@mrgnlabs/mrgn-common"; import { @@ -20,6 +20,7 @@ import { useWalletContext } from "~/hooks/useWalletContext"; import { useIsMobile } from "~/hooks/useIsMobile"; import { closeBalance, executeLendingAction, MarginfiActionParams, cn } from "~/utils"; import { IconAlertTriangle } from "~/components/ui/icons"; +import { LendingModes } from "~/types"; export const EMISSION_MINT_INFO_MAP = new Map([ [ @@ -48,7 +49,7 @@ const AssetRow: FC<{ hasHotkey: boolean; showHotkeyBadges?: boolean; badgeContent?: string; - userPosition?: ActiveBankInfo; + activeBank: ActiveBankInfo; showLSTDialog?: (variant: LSTDialogVariants, callback?: () => void) => void; }> = ({ bank, @@ -59,24 +60,38 @@ const AssetRow: FC<{ hasHotkey, showHotkeyBadges, badgeContent, - userPosition, + activeBank, showLSTDialog, }) => { const [lendZoomLevel, denominationUSD] = useUserProfileStore((state) => [state.lendZoomLevel, state.denominationUSD]); const setIsRefreshingStore = useMrgnlendStore((state) => state.setIsRefreshingStore); const [mfiClient, fetchMrgnlendState] = useMrgnlendStore((state) => [state.marginfiClient, state.fetchMrgnlendState]); + const [lendingMode, isFilteredUserPositions] = useUiStore((state) => [ + state.lendingMode, + state.isFilteredUserPositions, + ]); const { rateAP, assetWeight, isBankFilled, isBankHigh, bankCap } = useAssetItemData({ bank, isInLendingMode }); const [hasLSTDialogShown, setHasLSTDialogShown] = useState([]); const { walletContextState } = useWalletContext(); const isMobile = useIsMobile(); const isUserPositionPoorHealth = useMemo(() => { - if (!userPosition || !userPosition.position.liquidationPrice) { + if (!activeBank || !activeBank.position.liquidationPrice) { return false; } - return bank.info.state.price < userPosition.position.liquidationPrice * (1 + 0.05); - }, [userPosition]); + const alertRange = 0.05; + + if (activeBank.position.isLending) { + return ( + bank.info.state.price < activeBank.position.liquidationPrice + activeBank.position.liquidationPrice * alertRange + ); + } else { + return ( + bank.info.state.price > activeBank.position.liquidationPrice - activeBank.position.liquidationPrice * alertRange + ); + } + }, [activeBank]); const userPositionColSpan = useMemo(() => { if (isMobile) { @@ -242,7 +257,7 @@ const AssetRow: FC<{ <> - {userPosition && ( - - -
-

Your position details

-
-
{userPosition.position.isLending ? "Lending" : "Borrowing"}
-
- {userPosition.position.amount < 0.01 && "< 0.01"} - {userPosition.position.amount >= 0.01 && - numeralFormatter(userPosition.position.amount) + " " + bank.meta.tokenSymbol} - {userPosition.position.amount < 0.01 && ( - {userPosition.position.amount}} placement="top"> - info - - )} -
-
USD Value
-
0 && - "pr-4 border-accent-foreground/50 border-r" + +
+

Your position details

+
+
{activeBank.position.isLending ? "Lending" : "Borrowing"}
+
+ {activeBank.position.amount < 0.01 && "< 0.01"} + {activeBank.position.amount >= 0.01 && + numeralFormatter(activeBank.position.amount) + " " + bank.meta.tokenSymbol} + {activeBank.position.amount < 0.01 && ( + {activeBank.position.amount}} placement="top"> + info + + )} +
+
USD Value
+
0 && + "pr-4 border-accent-foreground/50 border-r" + )} + > + {usdFormatter.format(activeBank.position.usdValue)} +
+ {activeBank.position.liquidationPrice && activeBank.position.liquidationPrice > 0 && ( + <> +
+ {isUserPositionPoorHealth && ( + + + + )} + Liquidation price +
+
+ {usdFormatter.format(activeBank.position.liquidationPrice)} +
+ )} - > - {usdFormatter.format(userPosition.position.usdValue)} -
- {userPosition.position.liquidationPrice && userPosition.position.liquidationPrice > 0 && ( - <> -
- {isUserPositionPoorHealth && ( - - - - )} - Liquidation price -
-
- {usdFormatter.format(userPosition.position.liquidationPrice)} -
- - )} -
-
-
-
- )} +
+
+ + + )} ); diff --git a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx index eb81ceeb4e..1aad0a62da 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx @@ -388,7 +388,7 @@ const AssetsList = () => { hasHotkey={true} showHotkeyBadges={showBadges} badgeContent={`${i + 1}`} - userPosition={userPosition.length > 0 ? userPosition[0] : undefined} + activeBank={userPosition[0]} showLSTDialog={(variant: LSTDialogVariants, onClose?: () => void) => { setLSTDialogVariant(variant); setIsLSTDialogOpen(true); @@ -612,8 +612,14 @@ const AssetsList = () => { {isolatedBanks .filter((b) => b.info.state.isIsolated) - .map((bank) => - isStoreInitialized ? ( + .map((bank) => { + const activeBank = activeBankInfos.filter( + (activeBankInfo) => activeBankInfo.meta.tokenSymbol === bank.meta.tokenSymbol + ); + + if (isFilteredUserPositions && !activeBank.length) return null; + + return isStoreInitialized ? ( { isInLendingMode={isInLendingMode} isConnected={connected} marginfiAccount={selectedAccount} + activeBank={activeBank[0]} inputRefs={inputRefs} hasHotkey={false} /> @@ -630,8 +637,8 @@ const AssetsList = () => { isInLendingMode={isInLendingMode} bankMetadata={bank.meta} /> - ) - )} + ); + })} )} diff --git a/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/AssetCard/AssetCard.tsx b/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/AssetCard/AssetCard.tsx index 05f051e752..0e4121ce6f 100644 --- a/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/AssetCard/AssetCard.tsx +++ b/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/AssetCard/AssetCard.tsx @@ -1,8 +1,8 @@ import React, { FC, useCallback, useMemo, useState } from "react"; import { WSOL_MINT } from "@mrgnlabs/mrgn-common"; -import { ExtendedBankInfo, ActionType, getCurrentAction } from "@mrgnlabs/marginfi-v2-ui-state"; +import { ExtendedBankInfo, ActiveBankInfo, ActionType, getCurrentAction } from "@mrgnlabs/marginfi-v2-ui-state"; import { MarginfiAccountWrapper } from "@mrgnlabs/marginfi-client-v2"; -import { useMrgnlendStore } from "~/store"; +import { useMrgnlendStore, useUiStore } from "~/store"; import { executeLendingAction, closeBalance, MarginfiActionParams } from "~/utils"; import { useAssetItemData } from "~/hooks/useAssetItemData"; import { useWalletContext } from "~/hooks/useWalletContext"; @@ -11,19 +11,25 @@ import { AssetCardStats } from "./AssetCardStats"; import { AssetCardActions } from "./AssetCardActions"; import { AssetCardPosition } from "./AssetCardPosition"; import { AssetCardHeader } from "./AssetCardHeader"; +import { LendingModes } from "~/types"; export const AssetCard: FC<{ bank: ExtendedBankInfo; + activeBank: ActiveBankInfo; nativeSolBalance: number; isInLendingMode: boolean; isConnected: boolean; marginfiAccount: MarginfiAccountWrapper | null; inputRefs?: React.MutableRefObject>; showLSTDialog?: (variant: LSTDialogVariants, callback?: () => void) => void; -}> = ({ bank, nativeSolBalance, isInLendingMode, marginfiAccount, inputRefs, showLSTDialog }) => { +}> = ({ bank, activeBank, nativeSolBalance, isInLendingMode, marginfiAccount, inputRefs, showLSTDialog }) => { const { rateAP, assetWeight, isBankFilled, isBankHigh, bankCap } = useAssetItemData({ bank, isInLendingMode }); const [mfiClient, fetchMrgnlendState] = useMrgnlendStore((state) => [state.marginfiClient, state.fetchMrgnlendState]); const setIsRefreshingStore = useMrgnlendStore((state) => state.setIsRefreshingStore); + const [lendingMode, isFilteredUserPositions] = useUiStore((state) => [ + state.lendingMode, + state.isFilteredUserPositions, + ]); const [hasLSTDialogShown, setHasLSTDialogShown] = useState([]); const { walletContextState } = useWalletContext(); @@ -169,7 +175,10 @@ export const AssetCard: FC<{ isBankHigh={isBankHigh} bankCap={bankCap} /> - {bank.isActive && } + {activeBank?.position && + (isFilteredUserPositions || activeBank?.position.isLending === (lendingMode === LendingModes.LEND)) && ( + + )} { const [lstDialogVariant, setLSTDialogVariant] = React.useState(null); const [lstDialogCallback, setLSTDialogCallback] = React.useState<(() => void) | null>(null); + const activeBankInfos = React.useMemo( + () => extendedBankInfos.filter((balance) => balance.isActive), + [extendedBankInfos] + ) as ActiveBankInfo[]; + const sortBanks = React.useCallback( (banks: ExtendedBankInfo[]) => { if (sortOption.field === "APY") { @@ -102,6 +107,10 @@ export const MobileAssetsList = () => { if (poolFilter === "stable" && !STABLECOINS.includes(bank.meta.tokenSymbol)) return null; if (poolFilter === "lst" && !LSTS.includes(bank.meta.tokenSymbol)) return null; + const activeBank = activeBankInfos.filter( + (activeBankInfo) => activeBankInfo.meta.tokenSymbol === bank.meta.tokenSymbol + ); + return ( { isConnected={connected} marginfiAccount={selectedAccount} inputRefs={inputRefs} + activeBank={activeBank[0]} showLSTDialog={(variant: LSTDialogVariants, onClose?: () => void) => { setLSTDialogVariant(variant); setIsLSTDialogOpen(true); @@ -167,6 +177,10 @@ export const MobileAssetsList = () => { isolatedBanks.length > 0 ? (
{isolatedBanks.map((bank, i) => { + const activeBank = activeBankInfos.filter( + (activeBankInfo) => activeBankInfo.meta.tokenSymbol === bank.meta.tokenSymbol + ); + return ( { isConnected={connected} marginfiAccount={selectedAccount} inputRefs={inputRefs} + activeBank={activeBank[0]} /> ); })} From 2dd254911722d3072b770545d142b3ef6c7b26d3 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Mon, 27 Nov 2023 12:27:06 -0500 Subject: [PATCH 53/93] chore: inline pos details checkpoint --- .../desktop/AssetsList/AssetRow/AssetRow.tsx | 25 +++++++-- .../MobileAssetsList/AssetCard/AssetCard.tsx | 2 +- .../AssetCard/AssetCardPosition.tsx | 55 ++++++++++++++++--- 3 files changed, 67 insertions(+), 15 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx index ec2a941344..b04df409c3 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx @@ -84,11 +84,13 @@ const AssetRow: FC<{ if (activeBank.position.isLending) { return ( - bank.info.state.price < activeBank.position.liquidationPrice + activeBank.position.liquidationPrice * alertRange + activeBank.info.state.price < + activeBank.position.liquidationPrice + activeBank.position.liquidationPrice * alertRange ); } else { return ( - bank.info.state.price > activeBank.position.liquidationPrice - activeBank.position.liquidationPrice * alertRange + activeBank.info.state.price > + activeBank.position.liquidationPrice - activeBank.position.liquidationPrice * alertRange ); } }, [activeBank]); @@ -541,7 +543,10 @@ const AssetRow: FC<{ }} >
-

Your position details

+

+ Your {isFilteredUserPositions ? (activeBank.position.isLending ? "lending " : "borrowing ") : ""}{" "} + position details +

{activeBank.position.isLending ? "Lending" : "Borrowing"}
@@ -557,13 +562,19 @@ const AssetRow: FC<{
USD Value
0 && "pr-4 border-accent-foreground/50 border-r" )} > - {usdFormatter.format(activeBank.position.usdValue)} + {activeBank.position.usdValue < 0.01 && "< $0.01"} + {activeBank.position.usdValue >= 0.01 && usdFormatter.format(activeBank.position.usdValue)} + {activeBank.position.usdValue < 0.01 && ( + ${activeBank.position.usdValue}} placement="top"> + info + + )}
{activeBank.position.liquidationPrice && activeBank.position.liquidationPrice > 0 && ( <> @@ -586,7 +597,9 @@ const AssetRow: FC<{ isUserPositionPoorHealth && "text-destructive-foreground" )} > - {usdFormatter.format(activeBank.position.liquidationPrice)} + {activeBank.position.liquidationPrice > 0.01 + ? usdFormatter.format(activeBank.position.liquidationPrice) + : `$${activeBank.position.liquidationPrice.toExponential(2)}`} )} diff --git a/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/AssetCard/AssetCard.tsx b/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/AssetCard/AssetCard.tsx index 0e4121ce6f..cb121670f7 100644 --- a/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/AssetCard/AssetCard.tsx +++ b/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/AssetCard/AssetCard.tsx @@ -15,7 +15,7 @@ import { LendingModes } from "~/types"; export const AssetCard: FC<{ bank: ExtendedBankInfo; - activeBank: ActiveBankInfo; + activeBank?: ActiveBankInfo; nativeSolBalance: number; isInLendingMode: boolean; isConnected: boolean; diff --git a/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/AssetCard/AssetCardPosition.tsx b/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/AssetCard/AssetCardPosition.tsx index cb8351abdc..9062c0cba2 100644 --- a/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/AssetCard/AssetCardPosition.tsx +++ b/apps/marginfi-v2-ui/src/components/mobile/MobileAssetsList/AssetCard/AssetCardPosition.tsx @@ -3,25 +3,50 @@ import React from "react"; import { usdFormatter, numeralFormatter } from "@mrgnlabs/mrgn-common"; import { ActiveBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; +import { useUiStore } from "~/store"; + +import { MrgnTooltip } from "~/components/common"; + +import { IconAlertTriangle } from "~/components/ui/icons"; + import { cn } from "~/utils"; type AssetCardPositionProps = { activeBank: ActiveBankInfo; - userPosition?: ActiveBankInfo; }; -export const AssetCardPosition = ({ activeBank, userPosition }: AssetCardPositionProps) => { +export const AssetCardPosition = ({ activeBank }: AssetCardPositionProps) => { + const [lendingMode, isFilteredUserPositions] = useUiStore((state) => [ + state.lendingMode, + state.isFilteredUserPositions, + ]); + const isUserPositionPoorHealth = React.useMemo(() => { - if (!activeBank.position.amount || !activeBank.position.liquidationPrice) { + if (!activeBank || !activeBank.position.liquidationPrice) { return false; } - return activeBank.info.state.price < activeBank.position.liquidationPrice * (1 + 0.05); - }, [userPosition]); + const alertRange = 0.2; + + if (activeBank.position.isLending) { + return ( + activeBank.info.state.price < + activeBank.position.liquidationPrice + activeBank.position.liquidationPrice * alertRange + ); + } else { + return ( + activeBank.info.state.price > + activeBank.position.liquidationPrice - activeBank.position.liquidationPrice * alertRange + ); + } + }, [activeBank]); return (
-

Your position details

+

+ Your {isFilteredUserPositions ? (activeBank.position.isLending ? "lending " : "borrowing ") : ""} position + details +

{activeBank.position.isLending ? "Lending" : "Borrowing"}
@@ -32,9 +57,23 @@ export const AssetCardPosition = ({ activeBank, userPosition }: AssetCardPositio
{usdFormatter.format(activeBank.position.usdValue)}
{activeBank.position.liquidationPrice && activeBank.position.liquidationPrice > 0 && ( <> -
Liquidation price
+
+ {isUserPositionPoorHealth && ( + + + + )} + Liquidation price +
- {usdFormatter.format(activeBank.position.liquidationPrice)} + {activeBank.position.liquidationPrice > 0.01 + ? usdFormatter.format(activeBank.position.liquidationPrice) + : `$${activeBank.position.liquidationPrice.toExponential(2)}`}
)} From a639337799a3564ff680dbedfb2c3b356fc02178 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Tue, 28 Nov 2023 10:09:34 -0500 Subject: [PATCH 54/93] feat: reorder asset filters --- .../src/components/common/AssetList/AssetListFilters.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.tsx b/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.tsx index e20301de8c..c5aa47282b 100644 --- a/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.tsx +++ b/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.tsx @@ -39,6 +39,7 @@ export const AssetListFilters = () => { return (
+
{
-
); }; From 920241b18329ad75872bfe079326f7d6b43f0246 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Tue, 28 Nov 2023 10:15:14 -0500 Subject: [PATCH 55/93] fix: reset pool filter when filtering user positions --- .../src/components/common/AssetList/AssetListFilters.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.tsx b/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.tsx index c5aa47282b..eceff73993 100644 --- a/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.tsx +++ b/apps/marginfi-v2-ui/src/components/common/AssetList/AssetListFilters.tsx @@ -59,7 +59,10 @@ export const AssetListFilters = () => { > setIsFilteredUserPositions(!isFilteredUserPositions)} + onChange={() => { + setIsFilteredUserPositions(!isFilteredUserPositions); + setPoolFilter(PoolTypes.ALL); + }} inputProps={{ "aria-label": "controlled" }} className={cn(!connected && "pointer-events-none")} /> From 43ab242f95c8244ad241769d6afb73026a3598d6 Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Tue, 28 Nov 2023 10:41:47 -0500 Subject: [PATCH 56/93] feat: add user positions component back at bottom of grid --- .../src/components/desktop/AssetsList/AssetsList.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx index 1aad0a62da..427f2c1beb 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetsList.tsx @@ -26,8 +26,12 @@ import { MrgnTooltip } from "~/components/common"; import { LendingModes } from "~/types"; +const UserPositions = dynamic(async () => (await import("~/components/desktop/UserPositions")).UserPositions, { + ssr: false, +}); + const AssetsList = () => { - const { connected } = useWalletContext(); + const { connected, walletAddress } = useWalletContext(); const [isStoreInitialized, extendedBankInfos, nativeSolBalance, selectedAccount] = useMrgnlendStore((state) => [ state.initialized, state.extendedBankInfos, @@ -646,6 +650,8 @@ const AssetsList = () => {
+ {walletAddress && } + Date: Mon, 27 Nov 2023 17:13:07 +0100 Subject: [PATCH 57/93] feat: improved toasts throughout the app --- .../common/AssetList/AssetRowInputBox.tsx | 4 ++-- .../Staking/StakingCard/StakingCard.tsx | 14 +++++++---- .../desktop/DesktopNavbar/DesktopNavbar.tsx | 2 -- .../src/components/desktop/Earn/index.tsx | 21 ++++------------- .../src/components/mobile/EmissionsBanner.tsx | 2 -- apps/marginfi-v2-ui/src/pages/bridge.tsx | 23 ++++--------------- apps/marginfi-v2-ui/src/utils/mrgnActions.ts | 7 +++++- 7 files changed, 27 insertions(+), 46 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/common/AssetList/AssetRowInputBox.tsx b/apps/marginfi-v2-ui/src/components/common/AssetList/AssetRowInputBox.tsx index c25e5d1329..a2e38e3403 100644 --- a/apps/marginfi-v2-ui/src/components/common/AssetList/AssetRowInputBox.tsx +++ b/apps/marginfi-v2-ui/src/components/common/AssetList/AssetRowInputBox.tsx @@ -1,7 +1,7 @@ import { InputAdornment, TextField } from "@mui/material"; import { FC, MouseEventHandler, useCallback } from "react"; import { NumberFormatValues, NumericFormat } from "react-number-format"; -import { toast } from "react-toastify"; +import { showErrorToast } from "~/utils/toastUtils"; interface AssetRowInputBox { value: number; @@ -28,7 +28,7 @@ const AssetRowInputBox: FC = ({ if (maxValue !== undefined) { setValue(maxValue); } else { - toast.error("Not implemented"); + showErrorToast("Not implemented"); } }, [maxValue, setValue]); diff --git a/apps/marginfi-v2-ui/src/components/common/Staking/StakingCard/StakingCard.tsx b/apps/marginfi-v2-ui/src/components/common/Staking/StakingCard/StakingCard.tsx index 967056c36a..fe7335fc86 100644 --- a/apps/marginfi-v2-ui/src/components/common/Staking/StakingCard/StakingCard.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Staking/StakingCard/StakingCard.tsx @@ -37,7 +37,6 @@ import { SwapMode, useJupiter } from "@jup-ag/react-hook"; import JSBI from "jsbi"; import { StakeData, usePrevious } from "~/utils"; import { createJupiterApiClient } from "@jup-ag/api"; -import { toast } from "react-toastify"; import { SettingsModal } from "./SettingsModal"; import { SettingsIcon } from "./SettingsIcon"; import { LST_MINT, TokenData, TokenDataMap } from "~/store/lstStore"; @@ -46,6 +45,7 @@ import { IconLoader } from "~/components/ui/icons"; import BN from "bn.js"; import debounce from "lodash.debounce"; import { Desktop, Mobile } from "~/mediaQueries"; +import { MultiStepToastHandle, showErrorToast } from "~/utils/toastUtils"; const QUOTE_EXPIRY_MS = 30_000; const DEFAULT_DEPOSIT_OPTION: DepositOption = { type: "native", amount: new BN(0), maxAmount: new BN(0) }; @@ -182,7 +182,7 @@ export const StakingCard: FC = () => { [depositOption, refresh, lastRefreshTimestamp] ); - const showErrotToast = useRef(debounce(() => toast.error("Failed to find route"), 250)); + const showErrotToast = useRef(debounce(() => showErrorToast("Failed to find route"), 250)); const prevError = usePrevious(error); useEffect(() => { @@ -267,6 +267,9 @@ export const StakingCard: FC = () => { setOngoingAction("minting"); + const multiStepToast = new MultiStepToastHandle("Stake", [{ label: "Minting LST" }]); + multiStepToast.start(); + try { if (depositOption.type === "stake") { const { instructions, signers } = await makeDepositStakeToStakePoolIx( @@ -293,6 +296,7 @@ export const StakingCard: FC = () => { } else if (depositOption.type === "token") { const quote = quoteResponseMeta?.original; if (!quote) { + multiStepToast.setFailed("Route not calculated yet"); console.error("Route not calculated yet"); return; } @@ -346,10 +350,10 @@ export const StakingCard: FC = () => { "confirmed" ); } else { - throw new Error("Invalid deposit option"); + multiStepToast.setFailed("Invalid deposit option"); } - toast.success("Minting complete"); + multiStepToast.setSuccessAndNext(); } catch (error: any) { if (error.logs) { console.log("------ Logs 👇 ------"); @@ -360,7 +364,7 @@ export const StakingCard: FC = () => { if (errorMsg) { errorMsg = errorMsg ? errorMsg : "Transaction failed!"; } - toast.error(errorMsg); + multiStepToast.setFailed(errorMsg); } finally { await Promise.all([refresh(), fetchLstState()]); setDepositOption((currentDepositOption) => diff --git a/apps/marginfi-v2-ui/src/components/desktop/DesktopNavbar/DesktopNavbar.tsx b/apps/marginfi-v2-ui/src/components/desktop/DesktopNavbar/DesktopNavbar.tsx index 338b4ba711..c0f566896d 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/DesktopNavbar/DesktopNavbar.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/DesktopNavbar/DesktopNavbar.tsx @@ -11,7 +11,6 @@ import { useWalletContext } from "~/hooks/useWalletContext"; import { Features, isActive } from "~/utils/featureGates"; import { PublicKey } from "@solana/web3.js"; import { useConnection } from "~/hooks/useConnection"; -import { toast } from "react-toastify"; import { EMISSION_MINT_INFO_MAP } from "../AssetsList/AssetRow"; import { collectRewardsBatch } from "~/utils"; import { IconMrgn } from "~/components/ui/icons"; @@ -301,7 +300,6 @@ const DesktopNavbar: FC = () => { onClick={async () => { if (!wallet || !selectedAccount || bankAddressesWithEmissions.length === 0) return; await collectRewardsBatch(connection, wallet, selectedAccount, bankAddressesWithEmissions); - toast.success("Withdrawal successful"); }} > collect rewards diff --git a/apps/marginfi-v2-ui/src/components/desktop/Earn/index.tsx b/apps/marginfi-v2-ui/src/components/desktop/Earn/index.tsx index cfc62dedb2..0a0351e601 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/Earn/index.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/Earn/index.tsx @@ -28,11 +28,11 @@ import { } from "@mrgnlabs/mrgn-common"; import { Bank, PriceBias } from "@mrgnlabs/marginfi-client-v2"; import { Countdown } from "~/components/desktop/Countdown"; -import { toast } from "react-toastify"; import BigNumber from "bignumber.js"; import { useWalletContext } from "~/hooks/useWalletContext"; import { useMrgnlendStore } from "~/store"; import { Desktop } from "~/mediaQueries"; +import { MultiStepToastHandle } from "~/utils/toastUtils"; const Earn = () => { const { wallet, isOverride, connected, walletAddress } = useWalletContext(); @@ -181,25 +181,14 @@ const Earn = () => { const closeDeposit = useCallback( async (deposit: Deposit) => { if (!lipAccount) return; - toast.loading(`Closing deposit`, { - toastId: "close-deposit", - }); + const multiStepToast = new MultiStepToastHandle("Close deposit", [{ label: "Closing deposit" }]); + multiStepToast.start(); try { await lipAccount.closePosition(deposit); - toast.update("close-deposit", { - render: `Closing deposit 👍`, - type: toast.TYPE.SUCCESS, - autoClose: 2000, - isLoading: false, - }); + multiStepToast.setSuccessAndNext(); } catch (e) { console.error(e); - toast.update("close-deposit", { - render: `Error closing deposit: ${e}`, - type: toast.TYPE.ERROR, - autoClose: 2000, - isLoading: false, - }); + multiStepToast.setFailed(`Error closing deposit: ${e}`); } setReloading(true); setLipAccount(await lipAccount.reloadAndClone()); diff --git a/apps/marginfi-v2-ui/src/components/mobile/EmissionsBanner.tsx b/apps/marginfi-v2-ui/src/components/mobile/EmissionsBanner.tsx index eb1e1da095..5adae414f8 100644 --- a/apps/marginfi-v2-ui/src/components/mobile/EmissionsBanner.tsx +++ b/apps/marginfi-v2-ui/src/components/mobile/EmissionsBanner.tsx @@ -5,7 +5,6 @@ import { useWalletContext } from "~/hooks/useWalletContext"; import { useMrgnlendStore } from "~/store"; import { collectRewardsBatch } from "~/utils"; import { EMISSION_MINT_INFO_MAP } from "../desktop/AssetsList/AssetRow"; -import { toast } from "react-toastify"; const EmissionsBanner: FC = () => { const { connection } = useConnection(); @@ -33,7 +32,6 @@ const EmissionsBanner: FC = () => { onClick={async () => { if (!wallet || !selectedAccount || bankAddressesWithEmissions.length === 0) return; await collectRewardsBatch(connection, wallet, selectedAccount, bankAddressesWithEmissions); - toast.success("Withdrawal successful"); }} >
Collect LM rewards
diff --git a/apps/marginfi-v2-ui/src/pages/bridge.tsx b/apps/marginfi-v2-ui/src/pages/bridge.tsx index 794095fa4e..92184bcd3c 100644 --- a/apps/marginfi-v2-ui/src/pages/bridge.tsx +++ b/apps/marginfi-v2-ui/src/pages/bridge.tsx @@ -2,7 +2,6 @@ import React from "react"; import Script from "next/script"; -import { toast } from "react-toastify"; import { useHotkeys } from "react-hotkeys-hook"; import config from "~/config"; @@ -11,6 +10,7 @@ import { useUserProfileStore, useUiStore } from "~/store"; import { Desktop } from "~/mediaQueries"; import { useWalletContext } from "~/hooks/useWalletContext"; import { PageHeader } from "~/components/common/PageHeader"; +import { MultiStepToastHandle } from "~/utils/toastUtils"; const tokens = [ "0x0000000000000000000000000000000000000000", // SOL @@ -111,6 +111,7 @@ const BridgePage = () => { }, [walletContextState, handleConnect, walletAddress]); const handleLoadMayanWidget = React.useCallback(() => { + const multiStepToast = new MultiStepToastHandle("Bridge", [{ label: `Cross-chain swap/bridge in progress` }]); const configIndex = isBridgeIn ? 0 : 1; const config = { ...configs[configIndex], @@ -123,27 +124,13 @@ const BridgePage = () => { }; window.MayanSwap.init("swap_widget", config); window.MayanSwap.setSwapInitiateListener((data) => { - toast.loading("Cross-chain swap/bridge in progress", { - toastId: data.hash, - }); + multiStepToast.start(); }); window.MayanSwap.setSwapCompleteListener((data) => { - toast.update(data.hash, { - render: "Cross-chain swap/bridge done", - toastId: data.hash, - type: toast.TYPE.SUCCESS, - autoClose: 5000, - isLoading: false, - }); + multiStepToast.setSuccessAndNext(); }); window.MayanSwap.setSwapRefundListener((data) => { - toast.update(data.hash, { - render: "Cross-chain swap/bridge refunded", - toastId: data.hash, - type: toast.TYPE.WARNING, - autoClose: 5000, - isLoading: false, - }); + multiStepToast.setFailed("Cross-chain swap/bridge refunded"); }); }, [handleConnect, isBridgeIn, walletAddress, walletContextState.disconnect, walletContextState.signTransaction]); diff --git a/apps/marginfi-v2-ui/src/utils/mrgnActions.ts b/apps/marginfi-v2-ui/src/utils/mrgnActions.ts index e885095a19..b74a8e463c 100644 --- a/apps/marginfi-v2-ui/src/utils/mrgnActions.ts +++ b/apps/marginfi-v2-ui/src/utils/mrgnActions.ts @@ -219,10 +219,14 @@ export async function collectRewardsBatch( marginfiAccount: MarginfiAccountWrapper, bankAddresses: PublicKey[] ) { + const multiStepToast = new MultiStepToastHandle("Collect rewards", [{ label: "Collecting rewards" }]); + multiStepToast.start(); + try { const tx = new Transaction(); const ixs = []; const signers = []; + for (const bankAddress of bankAddresses) { const ix = await marginfiAccount.makeWithdrawEmissionsIx(bankAddress); ixs.push(...ix.instructions); @@ -230,9 +234,10 @@ export async function collectRewardsBatch( } tx.add(...ixs); await processTransaction(connection, wallet, tx); + multiStepToast.setSuccessAndNext(); } catch (error: any) { const msg = extractErrorString(error); - showErrorToast(msg); + multiStepToast.setFailed(msg); console.log(`Error while collecting rewards: ${msg}`); console.log(error); return; From 3b0e2294a7b97f21298523ef96c2610ffc3ffbef Mon Sep 17 00:00:00 2001 From: Kobe Leenders Date: Mon, 27 Nov 2023 19:56:06 +0100 Subject: [PATCH 58/93] feat: added reduce only label --- .../desktop/AssetsList/AssetRow/AssetRow.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx index b04df409c3..c3fabd1b85 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx @@ -39,6 +39,8 @@ export const EMISSION_MINT_INFO_MAP = new Map (bank?.meta?.tokenSymbol ? REDUCE_ONLY_BANKS.includes(bank.meta.tokenSymbol) : false), + [bank.meta.tokenSymbol] + ); + const isUserPositionPoorHealth = useMemo(() => { if (!activeBank || !activeBank.position.liquidationPrice) { return false; @@ -390,7 +397,7 @@ const AssetRow: FC<{ title={ - {isBankHigh && (isBankFilled ? "Limit Reached" : "Approaching Limit")} + {isReduceOnly ? "Reduce Only" : isBankHigh && (isBankFilled ? "Limit Reached" : "Approaching Limit")} {`${bank.meta.tokenSymbol} ${isInLendingMode ? "deposits" : "borrows"} are at ${percentFormatter.format( (isInLendingMode ? bank.info.state.totalDeposits : bank.info.state.totalBorrows) / bankCap @@ -405,14 +412,14 @@ const AssetRow: FC<{ className={``} > {denominationUSD ? usdFormatter.format( From 9f0297bec22c8cfc353edb0e0e71eabd0b8aa13b Mon Sep 17 00:00:00 2001 From: Kobe Leenders Date: Mon, 27 Nov 2023 23:43:50 +0100 Subject: [PATCH 59/93] fix: reduce label change --- .../desktop/AssetsList/AssetRow/AssetRow.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx index c3fabd1b85..57e36331b8 100644 --- a/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx +++ b/apps/marginfi-v2-ui/src/components/desktop/AssetsList/AssetRow/AssetRow.tsx @@ -399,9 +399,14 @@ const AssetRow: FC<{ {isReduceOnly ? "Reduce Only" : isBankHigh && (isBankFilled ? "Limit Reached" : "Approaching Limit")} - {`${bank.meta.tokenSymbol} ${isInLendingMode ? "deposits" : "borrows"} are at ${percentFormatter.format( - (isInLendingMode ? bank.info.state.totalDeposits : bank.info.state.totalBorrows) / bankCap - )} capacity.`} + + {isReduceOnly + ? "stSOL is being discontinued." + : `${bank.meta.tokenSymbol} ${ + isInLendingMode ? "deposits" : "borrows" + } are at ${percentFormatter.format( + (isInLendingMode ? bank.info.state.totalDeposits : bank.info.state.totalBorrows) / bankCap + )} capacity.`}
Learn more. From a6e20be0d3219d385070a5aac40045c9165df843 Mon Sep 17 00:00:00 2001 From: Kobe Leenders Date: Fri, 24 Nov 2023 15:40:29 +0100 Subject: [PATCH 60/93] feat: store login provider --- .../components/common/Wallet/WalletButton.tsx | 69 +++++++++++++++++-- .../src/hooks/useWalletContext.tsx | 23 ++++++- 2 files changed, 85 insertions(+), 7 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/common/Wallet/WalletButton.tsx b/apps/marginfi-v2-ui/src/components/common/Wallet/WalletButton.tsx index 783aa6ce0d..c046eab3d8 100644 --- a/apps/marginfi-v2-ui/src/components/common/Wallet/WalletButton.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Wallet/WalletButton.tsx @@ -1,21 +1,78 @@ -import React from "react"; +import React, { useCallback, useMemo } from "react"; +import Image from "next/image"; +import { useWallet } from "@solana/wallet-adapter-react"; import { useUiStore } from "~/store"; -import { useWalletContext } from "~/hooks/useWalletContext"; +import { WalletInfo, Web3AuthProvider, useWalletContext } from "~/hooks/useWalletContext"; import { Wallet } from "~/components/common/Wallet"; - +import { IconChevronDown, IconBrandGoogle, IconBrandX, IconBrandApple, IconMrgn } from "~/components/ui/icons"; import { Button } from "~/components/ui/button"; +const web3AuthIconMap: { [key in Web3AuthProvider]: { icon: JSX.Element } } = { + google: { + icon: , + }, + twitter: { + icon: , + }, + apple: { + icon: , + }, + email_passwordless: { + icon: , + }, +}; + export const WalletButton = () => { - const { connected } = useWalletContext(); + const { select, wallets } = useWallet(); + const { connected, loginWeb3Auth } = useWalletContext(); const [setIsWalletAuthDialogOpen] = useUiStore((state) => [state.setIsWalletAuthDialogOpen]); + const walletInfo = useMemo(() => JSON.parse(localStorage.getItem("walletInfo")) as WalletInfo | null, [connected]); + + const WalletIcon = useMemo(() => { + if (walletInfo?.icon) { + return () => wallet_icon; + } else { + return () => web3AuthIconMap[walletInfo?.name]?.icon ?? ; + } + }, [walletInfo]); + + const handleWalletConnect = useCallback(() => { + try { + if (!walletInfo) throw new Error("No local storage"); + if (walletInfo.web3Auth) { + if (walletInfo.name === "email_passwordless") { + loginWeb3Auth("email_passwordless", { login_hint: walletInfo.email }); + } else { + loginWeb3Auth(walletInfo.name); + } + } else { + select(walletInfo.name as any); + } + } catch (error) { + setIsWalletAuthDialogOpen(true); + } + }, [walletInfo]); + return ( <> {!connected ? ( - ) : ( diff --git a/apps/marginfi-v2-ui/src/hooks/useWalletContext.tsx b/apps/marginfi-v2-ui/src/hooks/useWalletContext.tsx index 3c703e7351..56e91e5a47 100644 --- a/apps/marginfi-v2-ui/src/hooks/useWalletContext.tsx +++ b/apps/marginfi-v2-ui/src/hooks/useWalletContext.tsx @@ -58,6 +58,12 @@ type WalletContextProps = { type Web3AuthSocialProvider = "google" | "twitter" | "apple"; type Web3AuthProvider = "email_passwordless" | Web3AuthSocialProvider; +type WalletInfo = { + name: string; + web3Auth: boolean; + icon?: string; + email?: string; +}; const web3AuthChainConfig = { chainNamespace: CHAIN_NAMESPACES.SOLANA, @@ -129,6 +135,14 @@ const WalletProvider = ({ children }: { children: React.ReactNode }) => { if (web3Auth?.connected && web3AuthWalletData) { return makeweb3AuthWalletContextState(web3AuthWalletData); } else { + if (walletContextStateDefault.wallet) { + const walletInfo: WalletInfo = { + name: walletContextStateDefault.wallet.adapter.name, + icon: walletContextStateDefault.wallet.adapter.icon, + web3Auth: false, + }; + localStorage.setItem("walletInfo", JSON.stringify(walletInfo)); + } return walletContextStateDefault; } }, [web3Auth?.connected, web3AuthWalletData, walletContextStateDefault.connected]); @@ -272,6 +286,13 @@ const WalletProvider = ({ children }: { children: React.ReactNode }) => { loginProvider: provider, extraLoginOptions, }); + + const walletInfo: WalletInfo = { + name: provider!, + web3Auth: true, + email: extraLoginOptions.login_hint, + }; + localStorage.setItem("walletInfo", JSON.stringify(walletInfo)); } catch (error) { console.error(error); } @@ -360,5 +381,5 @@ const useWalletContext = () => { return context; }; -export type { WalletContextStateOverride, Web3AuthSocialProvider, Web3AuthProvider }; +export type { WalletContextStateOverride, Web3AuthSocialProvider, Web3AuthProvider, WalletInfo }; export { WalletProvider, useWalletContext }; From 2d20d8ce218212b754064b4fc5f8a35321f97737 Mon Sep 17 00:00:00 2001 From: Kobe Leenders Date: Fri, 24 Nov 2023 16:24:40 +0100 Subject: [PATCH 61/93] fix: build error --- .../components/common/Wallet/WalletButton.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/common/Wallet/WalletButton.tsx b/apps/marginfi-v2-ui/src/components/common/Wallet/WalletButton.tsx index c046eab3d8..47eb165420 100644 --- a/apps/marginfi-v2-ui/src/components/common/Wallet/WalletButton.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Wallet/WalletButton.tsx @@ -29,13 +29,21 @@ export const WalletButton = () => { const { connected, loginWeb3Auth } = useWalletContext(); const [setIsWalletAuthDialogOpen] = useUiStore((state) => [state.setIsWalletAuthDialogOpen]); - const walletInfo = useMemo(() => JSON.parse(localStorage.getItem("walletInfo")) as WalletInfo | null, [connected]); + const walletInfo = useMemo( + () => (connected ? null : (JSON.parse(localStorage.getItem("walletInfo") ?? "null") as WalletInfo | null)), + [connected] + ); const WalletIcon = useMemo(() => { if (walletInfo?.icon) { - return () => wallet_icon; + const iconSrc = walletInfo?.icon; + return function WalletIconComp() { + return wallet_icon; + }; } else { - return () => web3AuthIconMap[walletInfo?.name]?.icon ?? ; + return function WalletIconComp() { + return walletInfo ? web3AuthIconMap[walletInfo.name as Web3AuthProvider]?.icon || : ; + }; } }, [walletInfo]); @@ -54,7 +62,7 @@ export const WalletButton = () => { } catch (error) { setIsWalletAuthDialogOpen(true); } - }, [walletInfo]); + }, [walletInfo, setIsWalletAuthDialogOpen]); return ( <> From b540190fa4591057279bf987d6ba2a222a8d9e8d Mon Sep 17 00:00:00 2001 From: Adam Chambers Date: Fri, 24 Nov 2023 13:27:34 -0500 Subject: [PATCH 62/93] feat: minor wallet button styling adjustments --- .../components/common/Wallet/WalletButton.tsx | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/common/Wallet/WalletButton.tsx b/apps/marginfi-v2-ui/src/components/common/Wallet/WalletButton.tsx index 47eb165420..025dbf9bd1 100644 --- a/apps/marginfi-v2-ui/src/components/common/Wallet/WalletButton.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Wallet/WalletButton.tsx @@ -11,16 +11,16 @@ import { Button } from "~/components/ui/button"; const web3AuthIconMap: { [key in Web3AuthProvider]: { icon: JSX.Element } } = { google: { - icon: , + icon: , }, twitter: { - icon: , + icon: , }, apple: { - icon: , + icon: , }, email_passwordless: { - icon: , + icon: , }, }; @@ -38,11 +38,15 @@ export const WalletButton = () => { if (walletInfo?.icon) { const iconSrc = walletInfo?.icon; return function WalletIconComp() { - return wallet_icon; + return wallet_icon; }; } else { return function WalletIconComp() { - return walletInfo ? web3AuthIconMap[walletInfo.name as Web3AuthProvider]?.icon || : ; + return walletInfo ? ( + web3AuthIconMap[walletInfo.name as Web3AuthProvider]?.icon || + ) : ( + + ); }; } }, [walletInfo]); @@ -67,17 +71,17 @@ export const WalletButton = () => { return ( <> {!connected ? ( - - ) : ( - - )} + {walletInfo && ( +
setIsWalletAuthDialogOpen(true)} + className="pl-2 border-l border-border inline-flex items-center" + > + +
+ )} +
+ + ) : ( + + ))} ); }; diff --git a/apps/marginfi-v2-ui/src/hooks/useWalletContext.tsx b/apps/marginfi-v2-ui/src/hooks/useWalletContext.tsx index a5d78e785c..ac9750bad0 100644 --- a/apps/marginfi-v2-ui/src/hooks/useWalletContext.tsx +++ b/apps/marginfi-v2-ui/src/hooks/useWalletContext.tsx @@ -205,10 +205,7 @@ const WalletProvider = ({ children }: { children: React.ReactNode }) => { // and override signTransaction methods with web3auth sdk const makeweb3AuthWalletData = React.useCallback( async (web3AuthProvider: IProvider) => { - if (!web3Auth) { - setIsLoading(false); - return; - } + if (!web3Auth) return; const solanaWallet = new SolanaWallet(web3AuthProvider); const accounts = await solanaWallet.requestAccounts(); @@ -248,7 +245,6 @@ const WalletProvider = ({ children }: { children: React.ReactNode }) => { return signedMessage; }, }); - setIsLoading(false); }, [web3Auth] ); @@ -331,6 +327,7 @@ const WalletProvider = ({ children }: { children: React.ReactNode }) => { if (!web3Auth?.connected || !web3Auth?.provider || web3AuthWalletData) return; setIsLoading(true); makeweb3AuthWalletData(web3Auth.provider); + setIsLoading(false); }, [web3Auth?.connected, web3Auth?.provider, web3AuthWalletData]); // initialize web3auth sdk on page load @@ -361,6 +358,7 @@ const WalletProvider = ({ children }: { children: React.ReactNode }) => { setweb3Auth(web3AuthInstance); } catch (error) { console.error(error); + } finally { setIsLoading(false); } }; From dd10b703729af3bf5099c99cd847a21202ca4d8d Mon Sep 17 00:00:00 2001 From: Kobe Leenders Date: Tue, 28 Nov 2023 11:22:49 +0100 Subject: [PATCH 65/93] fix: flashing icon bug --- apps/marginfi-v2-ui/src/hooks/useWalletContext.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/marginfi-v2-ui/src/hooks/useWalletContext.tsx b/apps/marginfi-v2-ui/src/hooks/useWalletContext.tsx index ac9750bad0..ed760f5b43 100644 --- a/apps/marginfi-v2-ui/src/hooks/useWalletContext.tsx +++ b/apps/marginfi-v2-ui/src/hooks/useWalletContext.tsx @@ -370,7 +370,7 @@ const WalletProvider = ({ children }: { children: React.ReactNode }) => { Date: Tue, 28 Nov 2023 16:45:36 +0100 Subject: [PATCH 66/93] feat: optimized state fetching --- .../marginfi-v2-ui-state/src/lib/mrgnlend.ts | 65 ++++++++++++++++--- .../src/store/mrgnlendStore.ts | 27 ++++++-- 2 files changed, 79 insertions(+), 13 deletions(-) diff --git a/packages/marginfi-v2-ui-state/src/lib/mrgnlend.ts b/packages/marginfi-v2-ui-state/src/lib/mrgnlend.ts index 83cd083f75..7a62d79d6b 100644 --- a/packages/marginfi-v2-ui-state/src/lib/mrgnlend.ts +++ b/packages/marginfi-v2-ui-state/src/lib/mrgnlend.ts @@ -158,15 +158,14 @@ export async function fetchBirdeyePrices(mints: PublicKey[], apiKey?: string): P throw new Error("Failed to fetch price"); } -export async function fetchEmissionsPriceMap( - banks: Bank[], - connection: Connection, +export async function makeExtendedBankEmission( + banks: ExtendedBankInfo[], + extendedBankMetadatas: ExtendedBankMetadata[], + tokenMap: TokenPriceMap, apiKey?: string -): Promise { - const banksWithEmissions = banks.filter((bank) => !bank.emissionsMint.equals(PublicKey.default)); - const emissionsMints = banksWithEmissions.map((bank) => bank.emissionsMint); - - let birdeyePrices = emissionsMints.map((m) => new BigNumber(0)); +): Promise<[ExtendedBankInfo[], ExtendedBankMetadata[]]> { + const emissionsMints = Object.keys(tokenMap).map((key) => new PublicKey(key)); + let birdeyePrices = emissionsMints.map(() => new BigNumber(0)); try { birdeyePrices = await fetchBirdeyePrices(emissionsMints, apiKey); @@ -174,12 +173,60 @@ export async function fetchEmissionsPriceMap( console.log("Failed to fetch emissions prices from Birdeye", err); } + emissionsMints.map((mint, idx) => { + tokenMap[mint.toBase58()] = { ...tokenMap[mint.toBase58()], price: birdeyePrices[idx] }; + }); + + const updatedBanks = banks.map((bank) => { + const rawBank = bank.info.rawBank; + const emissionTokenData = tokenMap[rawBank.emissionsMint.toBase58()]; + let emissionsRate: number = 0; + let emissions = Emissions.Inactive; + if ((rawBank.emissionsActiveLending || rawBank.emissionsActiveBorrowing) && emissionTokenData) { + const emissionsRateAmount = new BigNumber(nativeToUi(rawBank.emissionsRate, emissionTokenData.decimals)); + const emissionsRateValue = emissionsRateAmount.times(emissionTokenData.price); + const emissionsRateAdditionalyApy = emissionsRateValue.div(bank.info.oraclePrice.price); + + emissionsRate = emissionsRateAdditionalyApy.toNumber(); + + if (rawBank.emissionsActiveBorrowing) { + emissions = Emissions.Borrowing; + } else if (rawBank.emissionsActiveLending) { + emissions = Emissions.Lending; + } + + bank.info.state = { + ...bank.info.state, + emissionsRate, + emissions, + }; + } + return bank; + }); + + const sortedExtendedBankInfos = updatedBanks.sort( + (a, b) => b.info.state.totalDeposits * b.info.state.price - a.info.state.totalDeposits * a.info.state.price + ); + + const sortedExtendedBankMetadatas = extendedBankMetadatas.sort((am, bm) => { + const a = sortedExtendedBankInfos.find((a) => a.address.equals(am.address))!; + const b = sortedExtendedBankInfos.find((b) => b.address.equals(bm.address))!; + return b.info.state.totalDeposits * b.info.state.price - a.info.state.totalDeposits * a.info.state.price; + }); + + return [sortedExtendedBankInfos, sortedExtendedBankMetadatas]; +} + +export async function makeEmissionsPriceMap(banks: Bank[], connection: Connection): Promise { + const banksWithEmissions = banks.filter((bank) => !bank.emissionsMint.equals(PublicKey.default)); + const emissionsMints = banksWithEmissions.map((bank) => bank.emissionsMint); + const mintAis = await connection.getMultipleAccountsInfo(emissionsMints); const mint = mintAis.map((ai) => MintLayout.decode(ai!.data)); const emissionsPrices = banksWithEmissions.map((bank, i) => ({ mint: bank.emissionsMint, - price: birdeyePrices[i], + price: new BigNumber(0), decimals: mint[0].decimals, })); diff --git a/packages/marginfi-v2-ui-state/src/store/mrgnlendStore.ts b/packages/marginfi-v2-ui-state/src/store/mrgnlendStore.ts index 3ee7b7cf0a..1c3649cfe4 100644 --- a/packages/marginfi-v2-ui-state/src/store/mrgnlendStore.ts +++ b/packages/marginfi-v2-ui-state/src/store/mrgnlendStore.ts @@ -12,7 +12,7 @@ import { Bank, OraclePrice } from "@mrgnlabs/marginfi-client-v2"; import { Connection, PublicKey } from "@solana/web3.js"; import { DEFAULT_ACCOUNT_SUMMARY, - fetchEmissionsPriceMap, + makeEmissionsPriceMap, computeAccountSummary, fetchTokenAccounts, makeExtendedBankInfo, @@ -21,6 +21,7 @@ import { TokenAccountMap, ExtendedBankMetadata, makeExtendedBankMetadata, + makeExtendedBankEmission, } from "../lib"; import { getPointsSummary } from "../lib/points"; import { create, StateCreator } from "zustand"; @@ -131,7 +132,7 @@ const stateCreator: StateCreator = (set, get) => ({ const banks = [...marginfiClient.banks.values()]; const birdEyeApiKey = args?.birdEyeApiKey ?? get().birdEyeApiKey; - const priceMap = await fetchEmissionsPriceMap(banks, connection, birdEyeApiKey); + const priceMap = await makeEmissionsPriceMap(banks, connection); let nativeSolBalance: number = 0; let tokenAccountMap: TokenAccountMap; @@ -252,7 +253,7 @@ const stateCreator: StateCreator = (set, get) => ({ accountSummary = computeAccountSummary(selectedAccount, extendedBankInfos); } - const pointSummary = await getPointsSummary(); + const pointsTotal = get().protocolStats.pointsTotal; set({ initialized: true, @@ -268,13 +269,31 @@ const stateCreator: StateCreator = (set, get) => ({ deposits, borrows, tvl: deposits - borrows, - pointsTotal: pointSummary.points_total, + pointsTotal: pointsTotal, }, selectedAccount, nativeSolBalance, accountSummary, birdEyeApiKey, }); + + const pointSummary = await getPointsSummary(); + + set({ + protocolStats: { deposits, borrows, tvl: deposits - borrows, pointsTotal: pointSummary.points_total }, + }); + + const [sortedExtendedBankEmission, sortedExtendedBankMetadatasEmission] = await makeExtendedBankEmission( + sortedExtendedBankInfos, + sortedExtendedBankMetadatas, + priceMap, + birdEyeApiKey + ); + + set({ + extendedBankInfos: sortedExtendedBankEmission, + extendedBankMetadatas: sortedExtendedBankMetadatasEmission, + }); } catch (err) { console.error("error refreshing state: ", err); set({ isRefreshingStore: false }); From 2ede608ba1ae5bf58ebfca8bc500833678fe2482 Mon Sep 17 00:00:00 2001 From: Kobe Leenders Date: Wed, 29 Nov 2023 16:10:16 +0100 Subject: [PATCH 67/93] fix: sign in button offset --- .../src/components/common/Wallet/WalletButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/marginfi-v2-ui/src/components/common/Wallet/WalletButton.tsx b/apps/marginfi-v2-ui/src/components/common/Wallet/WalletButton.tsx index e76442830b..c2e35d24dd 100644 --- a/apps/marginfi-v2-ui/src/components/common/Wallet/WalletButton.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Wallet/WalletButton.tsx @@ -72,7 +72,7 @@ export const WalletButton = () => { <> {!isLoading && (!connected ? ( -