diff --git a/apps/marginfi-v2-ui/package.json b/apps/marginfi-v2-ui/package.json index eec62dec24..4f42be0b9b 100644 --- a/apps/marginfi-v2-ui/package.json +++ b/apps/marginfi-v2-ui/package.json @@ -23,11 +23,11 @@ "@next/bundle-analyzer": "^13.4.19", "@next/font": "13.1.1", "@socialgouv/matomo-next": "^1.4.0", - "@solana/wallet-adapter-base": "^0.9.23", - "@solana/wallet-adapter-react": "^0.15.34", - "@solana/wallet-adapter-react-ui": "^0.9.33", - "@solana/wallet-adapter-wallets": "^0.19.20", - "@solana/web3.js": "^1.78.4", + "@solana/wallet-adapter-base": "^0.9.20", + "@solana/wallet-adapter-react": "^0.15.28", + "@solana/wallet-adapter-react-ui": "^0.9.27", + "@solana/wallet-adapter-wallets": "^0.19.10", + "@solana/web3.js": "^1.73.0", "@vercel/analytics": "^1.0.0", "bignumber.js": "^9.1.1", "bn.js": "^5.2.1", diff --git a/apps/marginfi-v2-ui/src/components/AccountSummary/AccountSummary.tsx b/apps/marginfi-v2-ui/src/components/AccountSummary/AccountSummary.tsx index 4f5ae664f0..ade5b63e08 100644 --- a/apps/marginfi-v2-ui/src/components/AccountSummary/AccountSummary.tsx +++ b/apps/marginfi-v2-ui/src/components/AccountSummary/AccountSummary.tsx @@ -1,8 +1,8 @@ -import { useWallet } from "@solana/wallet-adapter-react"; import React, { FC } from "react"; import { UserStats } from "./UserStats"; import { useMrgnlendStore } from "~/store"; import dynamic from "next/dynamic"; +import { useWalletContext } from "../useWalletContext"; const GlobalStats = dynamic(async () => (await import("./GlobalStats")).GlobalStats, { ssr: false }); @@ -13,7 +13,7 @@ const AccountSummary: FC = () => { state.protocolStats, state.selectedAccount, ]); - const wallet = useWallet(); + const { connected } = useWalletContext(); return (
@@ -27,7 +27,7 @@ const AccountSummary: FC = () => {
- {wallet.connected && ( + {connected && ( { + const { connected, openWalletSelector } = useWalletContext(); const [lendZoomLevel, denominationUSD] = useUserProfileStore((state) => [state.lendZoomLevel, state.denominationUSD]); const setIsRefreshingStore = useMrgnlendStore((state) => state.setIsRefreshingStore); const [mfiClient, fetchMrgnlendState] = useMrgnlendStore((state) => [state.marginfiClient, state.fetchMrgnlendState]); @@ -74,9 +76,14 @@ const AssetRow: FC<{ () => bank.isActive && uiToNative(bank.position.amount, bank.info.state.mintDecimals).isZero(), [bank] ); - const currentAction = useMemo(() => getCurrentAction(isInLendingMode, bank), [isInLendingMode, bank]); + const currentAction: ActionType | "Connect" = useMemo( + () => (connected ? getCurrentAction(isInLendingMode, bank) : "Connect"), + [connected, isInLendingMode, bank] + ); const maxAmount = useMemo(() => { switch (currentAction) { + case "Connect": + return 0; case ActionType.Deposit: return bank.userInfo.maxDeposit; case ActionType.Withdraw: @@ -303,7 +310,16 @@ const AssetRow: FC<{ console.log("Error while reloading state"); console.log(error); } - }, [bank, borrowOrLendAmount, currentAction, marginfiAccount, mfiClient, nativeSolBalance, fetchMrgnlendState, setIsRefreshingStore]); + }, [ + bank, + borrowOrLendAmount, + currentAction, + marginfiAccount, + mfiClient, + nativeSolBalance, + fetchMrgnlendState, + setIsRefreshingStore, + ]); return ( @@ -641,7 +657,7 @@ const AssetRow: FC<{ maxValue={maxAmount} maxDecimals={bank.info.state.mintDecimals} inputRefs={inputRefs} - disabled={isDust} + disabled={isDust || currentAction === "Connect"} /> @@ -654,11 +670,15 @@ const AssetRow: FC<{
{isDust ? "Close" : currentAction} diff --git a/apps/marginfi-v2-ui/src/components/AssetsList/AssetRow/AssetRowAction.tsx b/apps/marginfi-v2-ui/src/components/AssetsList/AssetRow/AssetRowAction.tsx index aa2afe83e8..780a7c063a 100644 --- a/apps/marginfi-v2-ui/src/components/AssetsList/AssetRow/AssetRowAction.tsx +++ b/apps/marginfi-v2-ui/src/components/AssetsList/AssetRow/AssetRowAction.tsx @@ -1,37 +1,27 @@ import { Button, ButtonProps } from "@mui/material"; -import { useWallet } from "@solana/wallet-adapter-react"; import { FC, ReactNode } from "react"; -import dynamic from "next/dynamic"; - -const WalletMultiButtonDynamic = dynamic( - async () => (await import("@solana/wallet-adapter-react-ui")).WalletMultiButton, - { ssr: false } -); interface AssetRowActionProps extends ButtonProps { children?: ReactNode; bgColor?: string; } -const AssetRowAction: FC = ({ children, disabled, bgColor, ...otherProps }) => { - const wallet = useWallet(); - - return ; -}; +const AssetRowAction: FC = ({ children, disabled, bgColor, ...otherProps }) => ( + +); export { AssetRowAction }; diff --git a/apps/marginfi-v2-ui/src/components/AssetsList/AssetsList.tsx b/apps/marginfi-v2-ui/src/components/AssetsList/AssetsList.tsx index 6c9e4467f6..051d270f71 100644 --- a/apps/marginfi-v2-ui/src/components/AssetsList/AssetsList.tsx +++ b/apps/marginfi-v2-ui/src/components/AssetsList/AssetsList.tsx @@ -1,6 +1,5 @@ import Image from "next/image"; import React, { FC, useEffect, useRef, useState } from "react"; -import { useWallet } from "@solana/wallet-adapter-react"; import { Card, Table, TableHead, TableBody, TableContainer, TableCell } from "@mui/material"; import { styled } from "@mui/material/styles"; import Tooltip, { TooltipProps, tooltipClasses } from "@mui/material/Tooltip"; @@ -10,6 +9,7 @@ import AssetRow from "./AssetRow"; import { useMrgnlendStore, useUserProfileStore } from "~/store"; import { useHotkeys } from "react-hotkeys-hook"; import { LoadingAsset } from "./AssetRow/AssetRow"; +import { useWalletContext } from "../useWalletContext"; const HtmlTooltip = styled(({ className, ...props }: TooltipProps) => ( @@ -25,7 +25,7 @@ const HtmlTooltip = styled(({ className, ...props }: TooltipProps) => ( const AssetsList: FC = () => { // const { selectedAccount, nativeSolBalance } = useStore(); - const wallet = useWallet(); + const { connected } = useWalletContext(); const [isStoreInitialized, sortedBanks, nativeSolBalance, selectedAccount] = useMrgnlendStore((state) => [ state.initialized, state.extendedBankInfos, @@ -305,7 +305,7 @@ const AssetsList: FC = () => { nativeSolBalance={nativeSolBalance} bank={bank} isInLendingMode={isInLendingMode} - isConnected={wallet.connected} + isConnected={connected} marginfiAccount={selectedAccount} inputRefs={inputRefs} hasHotkey={true} @@ -351,7 +351,7 @@ const AssetsList: FC = () => { nativeSolBalance={nativeSolBalance} bank={bank} isInLendingMode={isInLendingMode} - isConnected={wallet.connected} + isConnected={connected} marginfiAccount={selectedAccount} inputRefs={inputRefs} hasHotkey={false} diff --git a/apps/marginfi-v2-ui/src/components/CampaignWizard.tsx b/apps/marginfi-v2-ui/src/components/CampaignWizard.tsx index 1534305ec8..ee3cc497d7 100644 --- a/apps/marginfi-v2-ui/src/components/CampaignWizard.tsx +++ b/apps/marginfi-v2-ui/src/components/CampaignWizard.tsx @@ -13,7 +13,6 @@ import { getAssociatedTokenAddressSync, } from "@mrgnlabs/mrgn-common"; import { useLipClient } from "~/context"; -import { useWallet } from "@solana/wallet-adapter-react"; import { MenuItem, Select, TextField } from "@mui/material"; import { Bank } from "@mrgnlabs/marginfi-client-v2"; import Image from "next/image"; @@ -21,6 +20,7 @@ import { NumberFormatValues, NumericFormat } from "react-number-format"; import { useMrgnlendStore } from "~/store"; import { computeGuaranteedApy } from "@mrgnlabs/lip-client"; import { EarnAction } from "./Earn"; +import { useWalletContext } from "./useWalletContext"; interface CampaignWizardInputBox { value: number; @@ -87,7 +87,7 @@ const CampaignWizard: FC = () => { const [depositCapacity, setDepositCapacity] = useState(0); const [campaignBank, setCampaignBank] = useState(null); - const wallet = useWallet(); + const walletContext = useWalletContext(); const [mfiClient] = useMrgnlendStore((state) => [state.marginfiClient]); const { lipClient, reload: reloadLipClient } = useLipClient(); @@ -273,7 +273,7 @@ const CampaignWizard: FC = () => { setValue={(value) => setGuaranteedApy(value / 100)} loadingSafetyCheck={() => {}} maxDecimals={2} - disabled={!wallet.connected} + disabled={!walletContext.connected} />
@@ -284,7 +284,7 @@ const CampaignWizard: FC = () => { setValue={setLockupPeriodInDays} loadingSafetyCheck={() => {}} maxDecimals={4} - disabled={!wallet.connected} + disabled={!walletContext.connected} />
@@ -295,7 +295,7 @@ const CampaignWizard: FC = () => { setValue={setDepositCapacity} loadingSafetyCheck={() => {}} maxDecimals={3} - disabled={!wallet.connected} + disabled={!walletContext.connected} />
diff --git a/apps/marginfi-v2-ui/src/components/Earn/index.tsx b/apps/marginfi-v2-ui/src/components/Earn/index.tsx index 5cfad096c4..6d80b532f9 100644 --- a/apps/marginfi-v2-ui/src/components/Earn/index.tsx +++ b/apps/marginfi-v2-ui/src/components/Earn/index.tsx @@ -31,12 +31,11 @@ import { Bank, PriceBias } from "@mrgnlabs/marginfi-client-v2"; import { Countdown } from "~/components/Countdown"; import { toast } from "react-toastify"; import BigNumber from "bignumber.js"; -import { useWalletWithOverride } from "~/components/useWalletWithOverride"; +import { useWalletContext } from "~/components/useWalletContext"; import { useMrgnlendStore } from "~/store"; const Earn = () => { - const walletContext = useWallet(); - const { wallet, isOverride } = useWalletWithOverride(); + const { wallet, isOverride, connected, walletAddress } = useWalletContext(); const { connection } = useConnection(); const { lipClient } = useLipClient(); @@ -128,31 +127,31 @@ const Earn = () => { useEffect(() => { (async function () { setInitialFetchDone(true); - if (!mfiClient || !lipClient || !walletContext.publicKey) return; - const lipAccount = await LipAccount.fetch(walletContext.publicKey, lipClient, mfiClient); + if (!mfiClient || !lipClient || !walletAddress) return; + const lipAccount = await LipAccount.fetch(walletAddress, lipClient, mfiClient); setLipAccount(lipAccount); })(); - }, [lipClient, mfiClient, walletContext.publicKey]); + }, [lipClient, mfiClient, walletAddress]); useEffect(() => { - if (walletContext.connected) { + if (connected) { setProgressPercent(50); } else { setProgressPercent(0); } - }, [walletContext.connected]); + }, [connected]); useEffect(() => { if (amount > 0) { setProgressPercent(100); } else { - if (walletContext.connected) { + if (connected) { setProgressPercent(50); } else { setProgressPercent(0); } } - }, [amount, walletContext.connected]); + }, [amount, connected]); const depositAction = useCallback(async () => { if (!lipAccount || !lipClient || !selectedCampaign || amount === 0 || whitelistedCampaignsWithMeta.length === 0) @@ -216,7 +215,7 @@ const Earn = () => {
- {walletContext.connected && ( + {connected && (
Your total deposits: @@ -275,7 +274,7 @@ const Earn = () => { maxValue={maxDepositAmount} loadingSafetyCheck={loadingSafetyCheck} maxDecimals={2} - disabled={!walletContext.connected} + disabled={!connected} />
@@ -625,21 +624,21 @@ interface EarnActionProps extends ButtonProps { } export const EarnAction: FC = ({ children, spinning, disabled, ...otherProps }) => { - const walletContext = useWallet(); + const { connected } = useWalletContext(); - return walletContext.connected ? ( + return connected ? ( diff --git a/apps/marginfi-v2-ui/src/components/Navbar/AirdropZone.tsx b/apps/marginfi-v2-ui/src/components/Navbar/AirdropZone.tsx index b87ff1e074..0600bf1d28 100644 --- a/apps/marginfi-v2-ui/src/components/Navbar/AirdropZone.tsx +++ b/apps/marginfi-v2-ui/src/components/Navbar/AirdropZone.tsx @@ -10,6 +10,7 @@ import { getAssociatedTokenAddressSync, shortenAddress, } from "@mrgnlabs/mrgn-common"; +import { useWalletContext } from "../useWalletContext"; const SOL_AMOUNT = 2 * 10 ** 9; @@ -23,7 +24,7 @@ const USDC_FAUCET = new PublicKey("3ThaREisq3etoy9cvdzRgKypHsa8iTjMxj19AjETA1Fy" const AirdropZone: FC = () => { const [isOpen, setIsOpen] = useState(false); - const wallet = useWallet(); + const { walletAddress, walletContextState } = useWalletContext(); const { connection } = useConnection(); const open = useCallback(async () => setIsOpen(true), []); @@ -31,31 +32,31 @@ const AirdropZone: FC = () => { const airdropToken = useCallback( async (amount: number, mint: PublicKey, faucet: PublicKey) => { - if (faucet && wallet.publicKey) { - const ataAddress = getAssociatedTokenAddressSync(mint, wallet.publicKey!); + if (faucet && walletAddress) { + const ataAddress = getAssociatedTokenAddressSync(mint, walletAddress!); const ixs = []; - const solBalance = await connection.getBalance(wallet.publicKey); + const solBalance = await connection.getBalance(walletAddress); if (solBalance < 0.05) { - await connection.requestAirdrop(wallet.publicKey, 100000000); + await connection.requestAirdrop(walletAddress, 100000000); } const ataAi = await connection.getAccountInfo(ataAddress); if (!ataAi) { - ixs.push(createAssociatedTokenAccountInstruction(wallet.publicKey, ataAddress, wallet.publicKey, mint)); + ixs.push(createAssociatedTokenAccountInstruction(walletAddress, ataAddress, walletAddress, mint)); } ixs.push(makeAirdropCollateralIx(amount, mint, ataAddress, faucet)); const tx = new Transaction(); tx.add(...ixs); - await wallet.sendTransaction(tx, connection, { + await walletContextState.sendTransaction(tx, connection, { skipPreflight: true, }); } }, - [connection, wallet] + [connection, walletAddress, walletContextState] ); - if (!wallet?.publicKey) return null; + if (!walletAddress) return null; return (
@@ -72,7 +73,7 @@ const AirdropZone: FC = () => { try { await airdropToken(USDC_AMOUNT, USDC_MINT, USDC_FAUCET); toast.update(toastId, { - render: `Airdropped ${USDC_AMOUNT} USDC to ${shortenAddress(wallet.publicKey!)}`, + render: `Airdropped ${USDC_AMOUNT} USDC to ${shortenAddress(walletAddress!)}`, type: toast.TYPE.SUCCESS, autoClose: 5000, isLoading: false, @@ -95,7 +96,7 @@ const AirdropZone: FC = () => { try { await airdropToken(NOTSOL_AMOUNT, NOTSOL_MINT, NOTSOL_FAUCET); toast.update(toastId, { - render: `Airdropped ${NOTSOL_AMOUNT} notSOL to ${shortenAddress(wallet.publicKey!)}`, + render: `Airdropped ${NOTSOL_AMOUNT} notSOL to ${shortenAddress(walletAddress!)}`, type: toast.TYPE.SUCCESS, autoClose: 5000, toastId, @@ -118,9 +119,9 @@ const AirdropZone: FC = () => { onClick={async () => { const toastId = toast.loading(`Airdropping ${SOL_AMOUNT} SOL`); try { - await connection.requestAirdrop(wallet.publicKey!, SOL_AMOUNT); + await connection.requestAirdrop(walletAddress!, SOL_AMOUNT); toast.update(toastId, { - render: `Airdropped ${SOL_AMOUNT} SOL to ${shortenAddress(wallet.publicKey!)}`, + render: `Airdropped ${SOL_AMOUNT} SOL to ${shortenAddress(walletAddress!)}`, type: toast.TYPE.SUCCESS, autoClose: 5000, toastId, diff --git a/apps/marginfi-v2-ui/src/components/Navbar/Navbar.tsx b/apps/marginfi-v2-ui/src/components/Navbar/Navbar.tsx index 3c1f63ceb4..25495a6d27 100644 --- a/apps/marginfi-v2-ui/src/components/Navbar/Navbar.tsx +++ b/apps/marginfi-v2-ui/src/components/Navbar/Navbar.tsx @@ -3,7 +3,6 @@ import Link from "next/link"; import Image from "next/image"; import AirdropZone from "./AirdropZone"; import { WalletButton } from "./WalletButton"; -import { useWallet } from "@solana/wallet-adapter-react"; import { useHotkeys } from "react-hotkeys-hook"; import { useMrgnlendStore, useUserProfileStore } from "~/store"; @@ -13,12 +12,13 @@ import { HotkeysEvent } from "react-hotkeys-hook/dist/types"; import { Badge } from "@mui/material"; import { useFirebaseAccount } from "../useFirebaseAccount"; import { groupedNumberFormatterDyn, numeralFormatter } from "@mrgnlabs/mrgn-common"; +import { useWalletContext } from "../useWalletContext"; // @todo implement second pretty navbar row const Navbar: FC = () => { useFirebaseAccount(); - const wallet = useWallet(); + const { connected, walletAddress } = useWalletContext(); const router = useRouter(); const [accountSummary, selectedAccount, extendedBankInfos] = useMrgnlendStore((state) => [ state.accountSummary, @@ -37,10 +37,9 @@ const Navbar: FC = () => { const [currentRoute, setCurrentRoute] = useState(router.pathname); useEffect(() => { - const walletAddress = wallet.publicKey?.toBase58(); if (!walletAddress) return; - fetchPoints(walletAddress).catch(console.error); - }, [fetchPoints, wallet.publicKey]); + fetchPoints(walletAddress.toBase58()).catch(console.error); + }, [fetchPoints, walletAddress]); useEffect(() => { setCurrentRoute(router.pathname); @@ -215,7 +214,7 @@ const Navbar: FC = () => { omni - {process.env.NEXT_PUBLIC_MARGINFI_FEATURES_AIRDROP === "true" && wallet.connected && } + {process.env.NEXT_PUBLIC_MARGINFI_FEATURES_AIRDROP === "true" && connected && }
{
- {wallet.connected && currentFirebaseUser + {connected && currentFirebaseUser ? `${groupedNumberFormatterDyn.format(Math.round(userPointsData.totalPoints))} points` : "P...P...POINTS!"} diff --git a/apps/marginfi-v2-ui/src/components/Navbar/WalletButton.tsx b/apps/marginfi-v2-ui/src/components/Navbar/WalletButton.tsx index 601e5d4436..dfbd690866 100644 --- a/apps/marginfi-v2-ui/src/components/Navbar/WalletButton.tsx +++ b/apps/marginfi-v2-ui/src/components/Navbar/WalletButton.tsx @@ -1,6 +1,6 @@ import dynamic from "next/dynamic"; import { FC } from "react"; -import { useWallet } from "@solana/wallet-adapter-react"; +import { useWalletContext } from "../useWalletContext"; const WalletMultiButtonDynamic = dynamic( async () => (await import("@solana/wallet-adapter-react-ui")).WalletMultiButton, @@ -8,14 +8,12 @@ const WalletMultiButtonDynamic = dynamic( ); const WalletButton: FC = () => { - const wallet = useWallet(); + const { connected } = useWalletContext(); return (
- - {!wallet.connected && "CONNECT"} + + {!connected &&
CONNECT
}
); diff --git a/apps/marginfi-v2-ui/src/components/useFirebaseAccount.tsx b/apps/marginfi-v2-ui/src/components/useFirebaseAccount.tsx index a63e5d9b0e..73055004fd 100644 --- a/apps/marginfi-v2-ui/src/components/useFirebaseAccount.tsx +++ b/apps/marginfi-v2-ui/src/components/useFirebaseAccount.tsx @@ -1,12 +1,12 @@ import { firebaseApi } from "@mrgnlabs/marginfi-v2-ui-state"; -import { useWallet } from "@solana/wallet-adapter-react"; import { onAuthStateChanged } from "firebase/auth"; import { useEffect } from "react"; import { toast } from "react-toastify"; import { useUserProfileStore } from "~/store"; +import { useWalletContext } from "./useWalletContext"; const useFirebaseAccount = () => { - const wallet = useWallet(); + const {connected, walletAddress} = useWalletContext(); const [checkForFirebaseUser, setFirebaseUser, signoutFirebaseUser, fetchPoints, resetPoints] = useUserProfileStore( (state) => [ @@ -34,16 +34,16 @@ const useFirebaseAccount = () => { // Wallet connection side effect (auto-login attempt) useEffect(() => { - if (!wallet.publicKey) return; - checkForFirebaseUser(wallet.publicKey.toBase58()); - }, [wallet.publicKey, checkForFirebaseUser]); + if (!walletAddress) return; + checkForFirebaseUser(walletAddress.toBase58()); + }, [walletAddress, checkForFirebaseUser]); // Wallet disconnection/change side effect (auto-logout) useEffect(() => { - signoutFirebaseUser(wallet.connected, wallet.publicKey?.toBase58()).catch((error) => + signoutFirebaseUser(connected, walletAddress?.toBase58()).catch((error) => toast.error(`Error signing out: ${error}`) ), - [wallet]; + [connected, walletAddress]; }); }; diff --git a/apps/marginfi-v2-ui/src/components/useWalletWithOverride.tsx b/apps/marginfi-v2-ui/src/components/useWalletContext.tsx similarity index 52% rename from apps/marginfi-v2-ui/src/components/useWalletWithOverride.tsx rename to apps/marginfi-v2-ui/src/components/useWalletContext.tsx index 6c8e6de9df..00401ca5b2 100644 --- a/apps/marginfi-v2-ui/src/components/useWalletWithOverride.tsx +++ b/apps/marginfi-v2-ui/src/components/useWalletContext.tsx @@ -1,13 +1,20 @@ import { Wallet } from "@mrgnlabs/mrgn-common"; -import { useAnchorWallet } from "@solana/wallet-adapter-react"; +import { useAnchorWallet, useWallet } from "@solana/wallet-adapter-react"; +import { useWalletModal } from "@solana/wallet-adapter-react-ui"; import { PublicKey } from "@solana/web3.js"; import { useRouter } from "next/router"; -import { useMemo } from "react"; +import { useCallback, useMemo } from "react"; -const useWalletWithOverride = () => { +const useWalletContext = () => { + const walletContextState = useWallet(); + const walletModal = useWalletModal(); const anchorWallet = useAnchorWallet(); const { query } = useRouter(); + const openWalletSelector = useCallback(() => { + walletModal.setVisible(true); + }, [walletModal]); + const { wallet, isOverride }: { wallet: Wallet | undefined; isOverride: boolean } = useMemo(() => { const override = query?.wallet as string; if (anchorWallet && override) { @@ -22,7 +29,7 @@ const useWalletWithOverride = () => { return { wallet: anchorWallet, isOverride: false }; }, [anchorWallet, query]); - return { wallet, isOverride }; + return { wallet, walletAddress: wallet?.publicKey, isOverride, connected: walletContextState.connected, openWalletSelector, walletContextState }; }; -export { useWalletWithOverride }; +export { useWalletContext }; diff --git a/apps/marginfi-v2-ui/src/pages/bridge.tsx b/apps/marginfi-v2-ui/src/pages/bridge.tsx index 639d5d9029..371ae523a9 100644 --- a/apps/marginfi-v2-ui/src/pages/bridge.tsx +++ b/apps/marginfi-v2-ui/src/pages/bridge.tsx @@ -1,15 +1,14 @@ "use client"; import { useState, useEffect, useCallback } from "react"; -import { useWallet } from "@solana/wallet-adapter-react"; import config from "~/config"; import Script from "next/script"; -import { useWalletModal } from "@solana/wallet-adapter-react-ui"; import { toast } from "react-toastify"; import { useHotkeys } from "react-hotkeys-hook"; import { PageHeaderBridge } from "~/components/PageHeader"; import { MayanWidgetColors, MayanWidgetConfigType } from "~/types"; import { useUserProfileStore } from "~/store"; +import { useWalletContext } from "~/components/useWalletContext"; const tokens = [ "0x0000000000000000000000000000000000000000", // SOL @@ -65,8 +64,7 @@ const configs: MayanWidgetConfigType[] = [ }, ]; const BridgePage = () => { - const { publicKey, signTransaction, connect, disconnect, wallet } = useWallet(); - const { setVisible, visible } = useWalletModal(); + const { walletAddress, walletContextState, openWalletSelector } = useWalletContext(); const [isBridgeIn, setIsBridgeIn] = useState(true); const setShowBadges = useUserProfileStore((state) => state.setShowBadges); @@ -85,36 +83,36 @@ const BridgePage = () => { const handleConnect = useCallback(async () => { try { - if (!wallet) { - setVisible(!visible); + if (!walletContextState.wallet) { + openWalletSelector(); } else { - await connect(); + await walletContextState.connect(); } } catch (err) { console.error(err); } - }, [connect, setVisible, visible, wallet]); + }, [walletContextState, openWalletSelector]); useEffect(() => { if (typeof window !== "undefined" && typeof window.MayanSwap !== "undefined") { window.MayanSwap.updateSolanaWallet({ - signTransaction, - publicKey: publicKey ? publicKey.toString() : null, + signTransaction: walletContextState.signTransaction, + publicKey: walletAddress ? walletAddress.toString() : null, onClickOnConnect: handleConnect, - onClickOnDisconnect: disconnect, + onClickOnDisconnect: walletContextState.disconnect, }); } - }, [disconnect, handleConnect, publicKey, signTransaction]); + }, [walletContextState, handleConnect, walletAddress]); const handleLoadMayanWidget = () => { const configIndex = isBridgeIn ? 0 : 1; const config = { ...configs[configIndex], solanaWallet: { - publicKey: publicKey ? publicKey.toString() : null, - signTransaction, + publicKey: walletAddress ? walletAddress.toString() : null, + signTransaction: walletContextState.signTransaction, onClickOnConnect: handleConnect, - onClickOnDisconnect: disconnect, + onClickOnDisconnect: walletContextState.disconnect, }, }; window.MayanSwap.init("swap_widget", config); @@ -148,10 +146,10 @@ const BridgePage = () => { const config = { ...configs[newConfigIndex], solanaWallet: { - publicKey: publicKey ? publicKey.toString() : null, - signTransaction, + publicKey: walletAddress ? walletAddress.toString() : null, + signTransaction: walletContextState.signTransaction, onClickOnConnect: handleConnect, - onClickOnDisconnect: disconnect, + onClickOnDisconnect: walletContextState.disconnect, }, }; if (window.MayanSwap) { @@ -160,7 +158,7 @@ const BridgePage = () => { return; } setIsBridgeIn((prevState) => !prevState); - }, [disconnect, handleConnect, isBridgeIn, publicKey, signTransaction]); + }, [handleConnect, isBridgeIn, walletAddress, walletContextState]); useEffect(() => { handleUpdateConfig; diff --git a/apps/marginfi-v2-ui/src/pages/index.tsx b/apps/marginfi-v2-ui/src/pages/index.tsx index 7492ddf375..f88c00f3a0 100644 --- a/apps/marginfi-v2-ui/src/pages/index.tsx +++ b/apps/marginfi-v2-ui/src/pages/index.tsx @@ -1,13 +1,13 @@ import React, { useEffect } from "react"; -import { useConnection, useWallet } from "@solana/wallet-adapter-react"; import { Banner } from "~/components"; import { PageHeader } from "~/components/PageHeader"; -import { useWalletWithOverride } from "~/components/useWalletWithOverride"; +import { useWalletContext } from "~/components/useWalletContext"; import { shortenAddress } from "@mrgnlabs/mrgn-common"; import config from "~/config/marginfi"; import { useMrgnlendStore } from "../store"; import dynamic from "next/dynamic"; import { OverlaySpinner } from "~/components/OverlaySpinner"; +import { useConnection } from "@solana/wallet-adapter-react"; const AccountSummary = dynamic(async () => (await import("~/components/AccountSummary")).AccountSummary, { ssr: false, @@ -16,19 +16,21 @@ const AssetsList = dynamic(async () => (await import("~/components/AssetsList")) const UserPositions = dynamic(async () => (await import("~/components/UserPositions")).UserPositions, { ssr: false }); const Home = () => { - const walletContext = useWallet(); - const { wallet, isOverride } = useWalletWithOverride(); + const { walletAddress, wallet, isOverride } = useWalletContext(); const { connection } = useConnection(); - const fetchMrgnlendState = useMrgnlendStore((state) => state.fetchMrgnlendState); - const setIsRefreshingStore = useMrgnlendStore((state) => state.setIsRefreshingStore); - const marginfiAccountCount = useMrgnlendStore((state) => state.marginfiAccountCount); - const selectedAccount = useMrgnlendStore((state) => state.selectedAccount); + const [fetchMrgnlendState, setIsRefreshingStore, marginfiAccountCount, selectedAccount, userDataFetched, resetUserData] = useMrgnlendStore((state) => [state.fetchMrgnlendState, state.setIsRefreshingStore, state.marginfiAccountCount, state.selectedAccount, state.userDataFetched, state.resetUserData]); const [isStoreInitialized, isRefreshingStore] = useMrgnlendStore((state) => [ state.initialized, state.isRefreshingStore, ]); + useEffect(() => { + if (!walletAddress && userDataFetched) { + resetUserData(); + } + }, [walletAddress, userDataFetched, resetUserData]); + useEffect(() => { setIsRefreshingStore(true); fetchMrgnlendState({ marginfiConfig: config.mfiConfig, connection, wallet, isOverride }).catch(console.error); @@ -48,18 +50,18 @@ const Home = () => { suppressHydrationWarning={true} className="flex flex-col h-full justify-start content-start pt-[64px] sm:pt-[16px] w-4/5 max-w-7xl gap-4" > - {walletContext.publicKey && + {walletAddress && wallet && selectedAccount && - !selectedAccount.authority.equals(walletContext.publicKey) && ( + !selectedAccount.authority.equals(walletAddress) && ( )} - {walletContext.connected && marginfiAccountCount > 1 && ( + {walletAddress && marginfiAccountCount > 1 && ( )} @@ -67,7 +69,7 @@ const Home = () => {
- {walletContext.connected && } + {walletAddress && }
diff --git a/apps/marginfi-v2-ui/src/pages/lip.tsx b/apps/marginfi-v2-ui/src/pages/lip.tsx index b58b185ee2..85dd283c6a 100644 --- a/apps/marginfi-v2-ui/src/pages/lip.tsx +++ b/apps/marginfi-v2-ui/src/pages/lip.tsx @@ -1,15 +1,15 @@ -import { useWallet } from "@solana/wallet-adapter-react"; import { PageHeader } from "~/components/PageHeader"; import { CampaignWizard } from "~/components/CampaignWizard"; import { LipClientProvider } from "~/context"; +import { useWalletContext } from "~/components/useWalletContext"; const LIP = () => { - const wallet = useWallet(); + const { connected } = useWalletContext(); return ( - {wallet.connected && } + {connected && } ); }; diff --git a/apps/marginfi-v2-ui/src/pages/points.tsx b/apps/marginfi-v2-ui/src/pages/points.tsx index 701db8efc0..5b55d2ef3c 100644 --- a/apps/marginfi-v2-ui/src/pages/points.tsx +++ b/apps/marginfi-v2-ui/src/pages/points.tsx @@ -16,7 +16,7 @@ import { Checkbox, CircularProgress, } from "@mui/material"; -import { useConnection, useWallet } from "@solana/wallet-adapter-react"; +import { useConnection } from "@solana/wallet-adapter-react"; import { FC, useEffect, useState } from "react"; import { PageHeader } from "~/components/PageHeader"; import FileCopyIcon from "@mui/icons-material/FileCopy"; @@ -31,6 +31,7 @@ import { toast } from "react-toastify"; import { useUserProfileStore } from "~/store"; import { LeaderboardRow, fetchLeaderboardData, firebaseApi } from "@mrgnlabs/marginfi-v2-ui-state"; import { numeralFormatter, groupedNumberFormatterDyn } from "@mrgnlabs/mrgn-common"; +import { useWalletContext } from "~/components/useWalletContext"; const HtmlTooltip = styled(({ className, ...props }: TooltipProps) => ( @@ -45,7 +46,7 @@ const HtmlTooltip = styled(({ className, ...props }: TooltipProps) => ( })); const Points: FC = () => { - const wallet = useWallet(); + const { connected, walletAddress } = useWalletContext(); const { query: routerQuery } = useRouter(); const [currentFirebaseUser, hasUser, userPointsData] = useUserProfileStore((state) => [ state.currentFirebaseUser, @@ -60,13 +61,13 @@ const Points: FC = () => { useEffect(() => { fetchLeaderboardData().then(setLeaderboardData); // TODO: cache leaderboard and avoid call - }, [wallet.connected, wallet.publicKey]); // Dependency array to re-fetch when these variables change + }, [connected, walletAddress]); // Dependency array to re-fetch when these variables change return ( <>
- {!wallet.connected ? ( + {!connected ? ( ) : currentFirebaseUser ? ( <> @@ -442,7 +443,7 @@ const CheckingUser: FC = () => ( const Signup: FC<{ referralCode?: string }> = ({ referralCode }) => { const { connection } = useConnection(); - const wallet = useWallet(); + const { walletContextState, connected } = useWalletContext(); const [manualCode, setManualCode] = useState(""); const [useAuthTx, setUseAuthTx] = useState(false); const [useManualCode, setUseManualCode] = useState(false); @@ -462,13 +463,13 @@ const Signup: FC<{ referralCode?: string }> = ({ referralCode }) => { toast.info("Logging in..."); const blockhashInfo = await connection.getLatestBlockhash(); try { - await firebaseApi.signup(wallet, useAuthTx ? "tx" : "memo", blockhashInfo, finalReferralCode); + await firebaseApi.signup(walletContextState, useAuthTx ? "tx" : "memo", blockhashInfo, finalReferralCode); // localStorage.setItem("authData", JSON.stringify(signedAuthData)); toast.success("Signed up successfully"); } catch (signupError: any) { toast.error(signupError.message); } - }, [connection, finalReferralCode, useAuthTx, wallet]); + }, [connection, finalReferralCode, useAuthTx, walletContextState]); return ( @@ -483,7 +484,7 @@ const Signup: FC<{ referralCode?: string }> = ({ referralCode }) => { Optionally enter a referral code below.
- {wallet.connected ? ( + {connected ? (
= ({ referralCode }) => { const Login: FC = () => { const { connection } = useConnection(); - const wallet = useWallet(); + const { walletContextState, connected } = useWalletContext(); const [useAuthTx, setUseAuthTx] = useState(false); const login = useCallback(async () => { toast.info("Logging in..."); const blockhashInfo = await connection.getLatestBlockhash(); try { - await firebaseApi.login(wallet, useAuthTx ? "tx" : "memo", blockhashInfo); + await firebaseApi.login(walletContextState, useAuthTx ? "tx" : "memo", blockhashInfo); // localStorage.setItem("authData", JSON.stringify(signedAuthData)); toast.success("Logged in successfully"); } catch (loginError: any) { toast.error(loginError.message); } - }, [connection, useAuthTx, wallet]); + }, [connection, useAuthTx, walletContextState]); return ( @@ -576,7 +577,7 @@ const Login: FC = () => { Login to your points account by signing a message.
- {wallet.connected ? ( + {connected ? (
{ - const { wallet } = useWallet(); + const { wallet } = useWalletContext(); useEffect(() => { if (wallet) { diff --git a/apps/marginfi-v2-ui/src/utils/OKXWalletAdapter.tsx b/apps/marginfi-v2-ui/src/utils/OKXWalletAdapter.tsx index 90733828e2..3343c483fe 100644 --- a/apps/marginfi-v2-ui/src/utils/OKXWalletAdapter.tsx +++ b/apps/marginfi-v2-ui/src/utils/OKXWalletAdapter.tsx @@ -6,15 +6,19 @@ import { WalletConnectionError, WalletDisconnectedError, WalletDisconnectionError, + WalletNotConnectedError, WalletNotReadyError, WalletPublicKeyError, WalletReadyState, + WalletSignMessageError, + WalletSignTransactionError, } from "@solana/wallet-adapter-base"; -import { PublicKey } from "@solana/web3.js"; +import { PublicKey, Transaction, TransactionVersion, VersionedTransaction } from "@solana/web3.js"; interface OKXWalletEvents { connect(...args: unknown[]): unknown; disconnect(...args: unknown[]): unknown; + accountChanged(address: PublicKey, ...args: unknown[]): unknown; } interface OKXWallet extends EventEmitter { @@ -24,6 +28,9 @@ interface OKXWallet extends EventEmitter { publicKey?: PublicKey; connect(): Promise; disconnect(): Promise; + signMessage(message: Uint8Array, encoding: string): Promise; + signTransaction(transaction: Transaction | VersionedTransaction): Promise; + signAllTransactions(transactions: (Transaction | VersionedTransaction)[]): Promise<(Transaction | VersionedTransaction)[]>; } interface OKXWindow extends Window { @@ -38,13 +45,12 @@ interface OKXWalletAdapterConfig {} const OKXWalletName = "OKX Wallet" as WalletName<"OKX Wallet">; -//@ts-ignore export class OKXWalletAdapter extends BaseMessageSignerWalletAdapter { name = OKXWalletName; url = "https://www.okx.com/web3"; icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAJDSURBVHgB7Zq9jtpAEMfHlhEgQLiioXEkoAGECwoKxMcTRHmC5E3IoyRPkPAEkI7unJYmTgEFTYwA8a3NTKScLnCHN6c9r1e3P2llWQy7M/s1Gv1twCP0ej37dDq9x+Zut1t3t9vZjDEHIiSRSPg4ZpDL5fxkMvn1cDh8m0wmfugfO53OoFQq/crn8wxfY9EymQyrVCqMfHvScZx1p9ls3pFxXBy/bKlUipGPrVbLuQqAfsCliq3zl0H84zwtjQrOw4Mt1W63P5LvBm2d+Xz+YzqdgkqUy+WgWCy+Mc/nc282m4FqLBYL+3g8fjDxenq72WxANZbLJeA13zDX67UDioL5ybXwafMYu64Ltn3bdDweQ5R97fd7GyhBQMipx4POeEDHIu2LfDdBIGGz+hJ9CQ1ABjoA2egAZPM6AgiCAEQhsi/C4jHyPA/6/f5NG3Ks2+3CYDC4aTccDrn6ojG54MnEvG00GoVmWLIRNZ7wTCwDHYBsdACy0QHIhiuRETxlICWpMMhGZHmqS8qH6JLyGegAZKMDkI0uKf8X4SWlaZo+Pp1bRrwlJU8ZKLIvUjKh0WiQ3sRUbNVq9c5Ebew7KEo2m/1p4jJ4qAmDaqDQBzj5XyiAT4VCQezJigAU+IDU+z8vJFnGWeC+bKQV/5VZ71FV6L7PA3gg3tXrdQ+DgLhC+75Wq3no69P3MC0NFQpx2lL04Ql9gHK1bRDjsSBIvScBnDTk1WrlGIZBorIDEYJj+rhdgnQ67VmWRe0zlplXl81vcyEt0rSoYDUAAAAASUVORK5CYII="; - readonly supportedTransactionVersions = null; + readonly supportedTransactionVersions: ReadonlySet = new Set(['legacy', 0]); private _connecting: boolean; private _wallet: OKXWallet | null; @@ -119,6 +125,7 @@ export class OKXWalletAdapter extends BaseMessageSignerWalletAdapter { } wallet.on("disconnect", this._disconnected); + wallet.on('accountChanged', this._accountChanged); this._wallet = wallet; this._publicKey = publicKey; @@ -135,7 +142,8 @@ export class OKXWalletAdapter extends BaseMessageSignerWalletAdapter { async disconnect(): Promise { const wallet = this._wallet; if (wallet) { - wallet.on("disconnect", this._disconnected); + wallet.off("disconnect", this._disconnected); + wallet.off('accountChanged', this._accountChanged); this._wallet = null; this._publicKey = null; @@ -150,10 +158,58 @@ export class OKXWalletAdapter extends BaseMessageSignerWalletAdapter { this.emit("disconnect"); } + async signTransaction(transaction: T): Promise { + try { + const wallet = this._wallet; + if (!wallet) throw new WalletNotConnectedError(); + + try { + return ((await wallet.signTransaction(transaction)) as T) || transaction; + } catch (error: any) { + throw new WalletSignTransactionError(error?.message, error); + } + } catch (error: any) { + this.emit('error', error); + throw error; + } +} + +async signAllTransactions(transactions: T[]): Promise { + try { + const wallet = this._wallet; + if (!wallet) throw new WalletNotConnectedError(); + + try { + return ((await wallet.signAllTransactions(transactions)) as T[]) || transactions; + } catch (error: any) { + throw new WalletSignTransactionError(error?.message, error); + } + } catch (error: any) { + this.emit('error', error); + throw error; + } +} + +async signMessage(message: Uint8Array): Promise { + try { + const wallet = this._wallet; + if (!wallet) throw new WalletNotConnectedError(); + + try { + return await wallet.signMessage(message, 'utf8'); + } catch (error: any) { + throw new WalletSignMessageError(error?.message, error); + } + } catch (error: any) { + this.emit('error', error); + throw error; + } +} + private _disconnected = () => { const wallet = this._wallet; if (wallet) { - wallet.on("disconnect", this._disconnected); + wallet.off("disconnect", this._disconnected); this._wallet = null; this._publicKey = null; @@ -162,4 +218,23 @@ export class OKXWalletAdapter extends BaseMessageSignerWalletAdapter { this.emit("disconnect"); } }; + + private _accountChanged = (newPublicKey?: PublicKey) => { + if (!newPublicKey) return; + + const publicKey = this._publicKey; + if (!publicKey) return; + + try { + newPublicKey = new PublicKey(newPublicKey.toBytes()); + } catch (error: any) { + this.emit('error', new WalletPublicKeyError(error?.message, error)); + return; + } + + if (publicKey.equals(newPublicKey)) return; + + this._publicKey = newPublicKey; + this.emit('connect', newPublicKey); +}; } diff --git a/apps/omni/src/pages/index.tsx b/apps/omni/src/pages/index.tsx index b6eed7a1d0..c1495abacc 100644 --- a/apps/omni/src/pages/index.tsx +++ b/apps/omni/src/pages/index.tsx @@ -35,9 +35,8 @@ const AiUI: FC = () => { const [failed, setFailed] = useState(false); const wallet = useWallet(); - const [marginfiClient, fetchMrgnlendState, selectedAccount, extendedBankInfos] = useMrgnlendStore((state) => [ + const [marginfiClient, selectedAccount, extendedBankInfos] = useMrgnlendStore((state) => [ state.marginfiClient, - state.fetchMrgnlendState, state.selectedAccount, state.extendedBankInfos, ]); diff --git a/packages/marginfi-v2-ui-state/src/store/mrgnlendStore.ts b/packages/marginfi-v2-ui-state/src/store/mrgnlendStore.ts index 63d4a98233..30fe207d28 100644 --- a/packages/marginfi-v2-ui-state/src/store/mrgnlendStore.ts +++ b/packages/marginfi-v2-ui-state/src/store/mrgnlendStore.ts @@ -35,6 +35,7 @@ interface ProtocolStats { interface MrgnlendState { // State initialized: boolean; + userDataFetched: boolean; isRefreshingStore: boolean; marginfiClient: MarginfiClient | null; bankMetadataMap: BankMetadataMap; @@ -55,6 +56,7 @@ interface MrgnlendState { isOverride?: boolean; }) => Promise; setIsRefreshingStore: (isRefreshingStore: boolean) => void; + resetUserData: () => void; } function createMrgnlendStore() { @@ -78,6 +80,7 @@ function createPersistentMrgnlendStore() { const stateCreator: StateCreator = (set, get) => ({ // State initialized: false, + userDataFetched: false, isRefreshingStore: false, marginfiClient: null, bankMetadataMap: {}, @@ -102,6 +105,8 @@ const stateCreator: StateCreator = (set, get) => ({ wallet?: Wallet; isOverride?: boolean; }) => { + let userDataFetched = false; + const connection = args?.connection ?? get().marginfiClient?.provider.connection; if (!connection) throw new Error("Connection not found"); @@ -138,6 +143,7 @@ const stateCreator: StateCreator = (set, get) => ({ tokenAccountMap = tokenData.tokenAccountMap; marginfiAccounts = marginfiAccountWrappers; selectedAccount = marginfiAccounts[0]; + userDataFetched = true; } const banksWithPriceAndToken = banks.map((bank) => { @@ -204,6 +210,7 @@ const stateCreator: StateCreator = (set, get) => ({ set({ initialized: true, + userDataFetched, isRefreshingStore: false, marginfiClient, bankMetadataMap, @@ -223,6 +230,31 @@ const stateCreator: StateCreator = (set, get) => ({ }); }, setIsRefreshingStore: (isRefreshingStore: boolean) => set({ isRefreshingStore }), + resetUserData: () => { + const extendedBankInfos = get().extendedBankInfos.map((extendedBankInfo) => ({ + ...extendedBankInfo, + userInfo: { + tokenAccount: { + created: false, + mint: extendedBankInfo.info.state.mint, + balance: 0, + }, + maxDeposit: 0, + maxRepay: 0, + maxWithdraw: 0, + maxBorrow: 0, + }, + })); + + set({ + userDataFetched: false, + selectedAccount: null, + nativeSolBalance: 0, + accountSummary: DEFAULT_ACCOUNT_SUMMARY, + extendedBankInfos, + marginfiClient: null, + }); + }, }); export { createMrgnlendStore, createPersistentMrgnlendStore }; diff --git a/yarn.lock b/yarn.lock index 4df56603bf..5e1d91c896 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6509,7 +6509,7 @@ dependencies: "@solana/wallet-adapter-base" "^0.9.23" -"@solana/wallet-adapter-react-ui@^0.9.27", "@solana/wallet-adapter-react-ui@^0.9.33": +"@solana/wallet-adapter-react-ui@^0.9.27": version "0.9.33" resolved "https://registry.yarnpkg.com/@solana/wallet-adapter-react-ui/-/wallet-adapter-react-ui-0.9.33.tgz#30ad1638f700a47386ff709d5e855fc792b32e25" integrity sha512-Wf2yAYpmzxWE3f8IMNed7wz+JALEwY2eIKcDtviLhvs/MXHw74fXA0mLzf5GB3EgHZq5zgWZI4kdYGTT8/yY1w== @@ -6653,7 +6653,7 @@ "@jnwng/walletconnect-solana" "^0.2.0" "@solana/wallet-adapter-base" "^0.9.23" -"@solana/wallet-adapter-wallets@^0.19.10", "@solana/wallet-adapter-wallets@^0.19.20": +"@solana/wallet-adapter-wallets@^0.19.10": version "0.19.20" resolved "https://registry.yarnpkg.com/@solana/wallet-adapter-wallets/-/wallet-adapter-wallets-0.19.20.tgz#25bbbbf2349f9c8ee169de1db34c74900f0e8a35" integrity sha512-bE7tS6UykCSiWmudo8DYe/mZV0HoyqI+XifIjPaY/BNSgXQ3u1cwbqvqcLURuixBRnOka7FE6VuT90PhRsosqA==