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 176020c286..60e5172c8f 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 @@ -33,8 +33,8 @@ import { VersionedTransaction, } from "@solana/web3.js"; import { useConnection } from "~/hooks/useConnection"; +import { useAnalytics } from "~/hooks/useAnalytics"; import { SwapMode, useJupiter } from "@jup-ag/react-hook"; -import posthog from "posthog-js"; import JSBI from "jsbi"; import { StakeData, usePrevious } from "~/utils"; import { createJupiterApiClient } from "@jup-ag/api"; @@ -73,6 +73,7 @@ export const StakingCard: FC = () => { const router = useRouter(); const { connection } = useConnection(); const { connected, wallet, walletAddress } = useWalletContext(); + const { capture } = useAnalytics(); const [setIsWalletAuthDialogOpen] = useUiStore((state) => [state.setIsWalletAuthDialogOpen]); const [ @@ -368,7 +369,7 @@ export const StakingCard: FC = () => { multiStepToast.setFailed(errorMsg); } finally { await Promise.all([refresh(), fetchLstState()]); - posthog.capture("user_stake", { + capture("user_stake", { amount: depositAmountUi, }); setDepositOption((currentDepositOption) => diff --git a/apps/marginfi-v2-ui/src/components/common/Wallet/Wallet.tsx b/apps/marginfi-v2-ui/src/components/common/Wallet/Wallet.tsx index cf0aabd8e6..0abe19997f 100644 --- a/apps/marginfi-v2-ui/src/components/common/Wallet/Wallet.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Wallet/Wallet.tsx @@ -3,7 +3,6 @@ import React from "react"; import { LAMPORTS_PER_SOL, GetProgramAccountsFilter, PublicKey } from "@solana/web3.js"; import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; import { CopyToClipboard } from "react-copy-to-clipboard"; -import posthog from "posthog-js"; import { shortenAddress, usdFormatter, numeralFormatter } from "@mrgnlabs/mrgn-common"; @@ -11,6 +10,7 @@ import { useMrgnlendStore, useUiStore } from "~/store"; import { useConnection } from "~/hooks/useConnection"; import { useWalletContext } from "~/hooks/useWalletContext"; import { useIsMobile } from "~/hooks/useIsMobile"; +import { useAnalytics } from "~/hooks/useAnalytics"; import { MrgnTooltip } from "~/components/common/MrgnTooltip"; import { @@ -33,6 +33,7 @@ export const Wallet = () => { const { connection } = useConnection(); const { wallet, connected, logout, pfp, requestPrivateKey, web3AuthPk, web3AuthConncected } = useWalletContext(); const isMobile = useIsMobile(); + const { setPersonProperties } = useAnalytics(); const [isWalletAddressCopied, setIsWalletAddressCopied] = React.useState(false); const [isFundingAddressCopied, setIsFundingAddressCopied] = React.useState(false); @@ -88,7 +89,7 @@ export const Wallet = () => { tokens: (tokens || []) as Token[], }); - posthog.setPersonProperties({ + setPersonProperties({ walletAddress: wallet?.publicKey.toString(), tokens: tokens.map((token) => ({ name: token?.name, diff --git a/apps/marginfi-v2-ui/src/hooks/useAnalytics.ts b/apps/marginfi-v2-ui/src/hooks/useAnalytics.ts new file mode 100644 index 0000000000..8dbad40e66 --- /dev/null +++ b/apps/marginfi-v2-ui/src/hooks/useAnalytics.ts @@ -0,0 +1,33 @@ +import posthog from "posthog-js"; + +export const useAnalytics = (): { + init: () => void; + capture: (event: string, properties?: Record) => void; + identify: (userId: string, properties?: Record) => void; + setPersonProperties: (properties: Record) => void; +} => { + const _checkEnv = () => + !process.env.NEXT_PUBLIC_POSTHOG_API_KEY || process.env.NEXT_PUBLIC_MARGINFI_ENVIRONMENT !== "production"; + + const init = () => { + if (_checkEnv()) return; + posthog.init(process.env.NEXT_PUBLIC_POSTHOG_API_KEY!, { api_host: "https://app.posthog.com" }); + }; + + const capture = (event: string, properties?: Record) => { + if (_checkEnv()) return; + posthog.capture(event, properties); + }; + + const identify = (userId: string, properties?: Record) => { + if (_checkEnv()) return; + posthog.identify(userId, properties); + }; + + const setPersonProperties = (properties: Record) => { + if (_checkEnv()) return; + posthog.setPersonProperties(properties); + }; + + return { init, capture, identify, setPersonProperties }; +}; diff --git a/apps/marginfi-v2-ui/src/pages/_app.tsx b/apps/marginfi-v2-ui/src/pages/_app.tsx index 18ec4596a4..aba9781905 100644 --- a/apps/marginfi-v2-ui/src/pages/_app.tsx +++ b/apps/marginfi-v2-ui/src/pages/_app.tsx @@ -10,7 +10,6 @@ import { WalletModalProvider } from "@solana/wallet-adapter-react-ui"; import { init, push } from "@socialgouv/matomo-next"; import { ToastContainer } from "react-toastify"; import { Analytics } from "@vercel/analytics/react"; -import posthog from "posthog-js"; import config from "~/config"; import { WALLET_ADAPTERS } from "~/config/wallets"; @@ -19,6 +18,7 @@ import { useLstStore } from "./stake"; import { Desktop, Mobile } from "~/mediaQueries"; import { WalletProvider as MrgnWalletProvider } from "~/hooks/useWalletContext"; import { ConnectionProvider } from "~/hooks/useConnection"; +import { useAnalytics } from "~/hooks/useAnalytics"; import { MobileNavbar } from "~/components/mobile/MobileNavbar"; import { Tutorial } from "~/components/common/Tutorial"; @@ -55,6 +55,8 @@ const MyApp = ({ Component, pageProps }: AppProps) => { state.isRefreshingStore, ]); + const { init: initAnalytics } = useAnalytics(); + // enable matomo heartbeat React.useEffect(() => { if (process.env.NEXT_PUBLIC_MARGINFI_ENVIRONMENT === "alpha") { @@ -79,7 +81,7 @@ const MyApp = ({ Component, pageProps }: AppProps) => { React.useEffect(() => { setReady(true); - posthog.init(process.env.NEXT_PUBLIC_POSTHOG_API_KEY!, { api_host: "https://app.posthog.com" }); + initAnalytics(); }, []); // if account set in query param then store inn local storage and remove from url 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 5fc0897da3..a29937b8b9 100644 --- a/apps/marginfi-v2-ui/src/pages/api/user/login.ts +++ b/apps/marginfi-v2-ui/src/pages/api/user/login.ts @@ -16,7 +16,7 @@ import { STATUS_OK, firebaseApi, } from "@mrgnlabs/marginfi-v2-ui-state"; -import posthog from "posthog-js"; +import { useAnalytics } from "~/hooks/useAnalytics"; initFirebaseIfNeeded(); @@ -26,6 +26,7 @@ export interface LoginRequest { export default async function handler(req: NextApiRequest, res: any) { const { walletAddress } = req.body; + const { capture, identify } = useAnalytics(); /* signing logic let signer; @@ -57,11 +58,12 @@ export default async function handler(req: NextApiRequest, res: an return res.status(STATUS_NOT_FOUND).json({ error: "User not found" }); } else { await logLoginAttempt(walletAddress, userResult.uid, "", true); - posthog.capture("user_login", { + + capture("user_login", { publicKey: walletAddress, uuid: userResult.uid, }); - posthog.identify(userResult.uid, { + identify(userResult.uid, { publicKey: walletAddress, }); } 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 c5cc202d43..72d3dc0ab4 100644 --- a/apps/marginfi-v2-ui/src/pages/api/user/signup.ts +++ b/apps/marginfi-v2-ui/src/pages/api/user/signup.ts @@ -15,7 +15,7 @@ import { STATUS_OK, firebaseApi, } from "@mrgnlabs/marginfi-v2-ui-state"; -import posthog from "posthog-js"; +import { useAnalytics } from "~/hooks/useAnalytics"; initFirebaseIfNeeded(); @@ -27,6 +27,8 @@ export interface SignupRequest { export default async function handler(req: NextApiRequest, res: any) { const { walletAddress, payload } = req.body; + const { capture, identify } = useAnalytics(); + Sentry.setContext("signup_args", { walletAddress, }); @@ -73,11 +75,11 @@ export default async function handler(req: NextApiRequest, res: a } await logSignupAttempt(walletAddress, payload.uuid, "", true); - posthog.capture("user_login", { + capture("user_login", { publicKey: walletAddress, uuid: payload.uuid, }); - posthog.identify(payload.uuid, { + identify(payload.uuid, { publicKey: walletAddress, }); diff --git a/apps/marginfi-v2-ui/src/utils/mrgnActions.ts b/apps/marginfi-v2-ui/src/utils/mrgnActions.ts index 3923e3a391..c8a5851c16 100644 --- a/apps/marginfi-v2-ui/src/utils/mrgnActions.ts +++ b/apps/marginfi-v2-ui/src/utils/mrgnActions.ts @@ -4,8 +4,8 @@ import { isWholePosition } from "./mrgnUtils"; import { Connection, PublicKey, Transaction } from "@solana/web3.js"; import { Wallet, processTransaction } from "@mrgnlabs/mrgn-common"; import { WalletContextState } from "@solana/wallet-adapter-react"; -import posthog from "posthog-js"; import { WalletContextStateOverride } from "~/hooks/useWalletContext"; +import { useAnalytics } from "~/hooks/useAnalytics"; import { MultiStepToastHandle, showErrorToast } from "./toastUtils"; export type MarginfiActionParams = { @@ -79,6 +79,8 @@ async function createAccountAndDeposit({ return; } + const { capture } = useAnalytics(); + const multiStepToast = new MultiStepToastHandle("Initial deposit", [ { label: "Creating account" }, { label: `Depositing ${amount} ${bank.meta.tokenSymbol}` }, @@ -101,7 +103,7 @@ async function createAccountAndDeposit({ try { await marginfiAccount.deposit(amount, bank.address); multiStepToast.setSuccessAndNext(); - posthog.capture("user_deposit", { + capture("user_deposit", { amount, bankAddress: bank.address.toBase58(), tokenSymbol: bank.meta.tokenSymbol, @@ -124,6 +126,8 @@ export async function deposit({ bank: ExtendedBankInfo; amount: number; }) { + const { capture } = useAnalytics(); + const multiStepToast = new MultiStepToastHandle("Deposit", [ { label: `Depositing ${amount} ${bank.meta.tokenSymbol}` }, ]); @@ -132,7 +136,7 @@ export async function deposit({ try { await marginfiAccount.deposit(amount, bank.address); multiStepToast.setSuccessAndNext(); - posthog.capture("user_deposit", { + capture("user_deposit", { amount, bankAddress: bank.address.toBase58(), tokenSymbol: bank.meta.tokenSymbol, @@ -155,6 +159,8 @@ export async function borrow({ bank: ExtendedBankInfo; amount: number; }) { + const { capture } = useAnalytics(); + const multiStepToast = new MultiStepToastHandle("Borrow", [ { label: `Borrowing ${amount} ${bank.meta.tokenSymbol}` }, ]); @@ -163,7 +169,7 @@ export async function borrow({ try { await marginfiAccount.borrow(amount, bank.address); multiStepToast.setSuccessAndNext(); - posthog.capture("user_borrow", { + capture("user_borrow", { amount, bankAddress: bank.address.toBase58(), tokenSymbol: bank.meta.tokenSymbol, @@ -186,6 +192,8 @@ export async function withdraw({ bank: ExtendedBankInfo; amount: number; }) { + const { capture } = useAnalytics(); + const multiStepToast = new MultiStepToastHandle("Withdrawal", [ { label: `Withdrawing ${amount} ${bank.meta.tokenSymbol}` }, ]); @@ -194,7 +202,7 @@ export async function withdraw({ try { await marginfiAccount.withdraw(amount, bank.address, bank.isActive && isWholePosition(bank, amount)); multiStepToast.setSuccessAndNext(); - posthog.capture("user_withdraw", { + capture("user_withdraw", { amount, bankAddress: bank.address.toBase58(), tokenSymbol: bank.meta.tokenSymbol, @@ -217,6 +225,8 @@ export async function repay({ bank: ExtendedBankInfo; amount: number; }) { + const { capture } = useAnalytics(); + const multiStepToast = new MultiStepToastHandle("Repayment", [ { label: `Repaying ${amount} ${bank.meta.tokenSymbol}` }, ]); @@ -225,7 +235,7 @@ export async function repay({ try { await marginfiAccount.repay(amount, bank.address, bank.isActive && isWholePosition(bank, amount)); multiStepToast.setSuccessAndNext(); - posthog.capture("user_repay", { + capture("user_repay", { amount, bankAddress: bank.address.toBase58(), tokenSymbol: bank.meta.tokenSymbol, @@ -245,6 +255,7 @@ export async function collectRewardsBatch( marginfiAccount: MarginfiAccountWrapper, bankAddresses: PublicKey[] ) { + const { capture } = useAnalytics(); const multiStepToast = new MultiStepToastHandle("Collect rewards", [{ label: "Collecting rewards" }]); multiStepToast.start(); @@ -261,7 +272,7 @@ export async function collectRewardsBatch( tx.add(...ixs); await processTransaction(connection, wallet, tx); multiStepToast.setSuccessAndNext(); - posthog.capture("user_collect_rewards", { + capture("user_collect_rewards", { bankAddresses, }); } catch (error: any) { @@ -289,6 +300,8 @@ export const closeBalance = async ({ return; } + const { capture } = useAnalytics(); + const multiStepToast = new MultiStepToastHandle("Closing balance", [ { label: `Closing ${bank.position.isLending ? "lending" : "borrow"} balance for ${bank.meta.tokenSymbol}` }, ]); @@ -301,7 +314,7 @@ export const closeBalance = async ({ await marginfiAccount.repay(0, bank.address, true); } multiStepToast.setSuccessAndNext(); - posthog.capture("user_close_balance", { + capture("user_close_balance", { positionType: bank.position.isLending ? "lending" : "borrow", bankAddress: bank.address.toBase58(), tokenSymbol: bank.meta.tokenSymbol,