diff --git a/components/governance/ListToken/ListToken.tsx b/components/governance/ListToken/ListToken.tsx index 38e04758..f090fbe7 100644 --- a/components/governance/ListToken/ListToken.tsx +++ b/components/governance/ListToken/ListToken.tsx @@ -344,6 +344,7 @@ const ListToken = ({ goBack }: { goBack: () => void }) => { undefined, // mangoAccount onlyDirect ? 'JUPITER_DIRECT' : 'JUPITER', connection, + undefined, ) }, [wallet.publicKey, connection], diff --git a/components/swap/SwapReviewRouteInfo.tsx b/components/swap/SwapReviewRouteInfo.tsx index 54ae3b92..f7933ea1 100644 --- a/components/swap/SwapReviewRouteInfo.tsx +++ b/components/swap/SwapReviewRouteInfo.tsx @@ -65,6 +65,7 @@ import { import { isTokenInsured } from '@components/DepositForm' import UninsuredNotification from '@components/shared/UninsuredNotification' import { sendTxAndConfirm } from 'utils/governance/tools' +import useAnalytics from 'hooks/useAnalytics' type JupiterRouteInfoProps = { amountIn: Decimal @@ -302,6 +303,7 @@ const SwapReviewRouteInfo = ({ INITIAL_SOUND_SETTINGS, ) const focusRef = useRef(null) + const { sendAnalytics } = useAnalytics() const [refetchRoutePercentage, setRefetchRoutePercentage] = useState(0) @@ -430,7 +432,7 @@ const SwapReviewRouteInfo = ({ ) return setSubmitting(true) - + let tx = '' const [ixs, alts] = // selectedRoute.routerName === 'Mango' // ? await prepareMangoRouterInstructions( @@ -452,7 +454,15 @@ const SwapReviewRouteInfo = ({ ) try { - const { signature: tx, slot } = await client.marginTrade({ + sendAnalytics( + { + inputMintPk: inputBank.mint, + amountIn: amountIn.toNumber(), + outputMintPk: outputBank.mint, + }, + 'swapping', + ) + const { signature, slot } = await client.marginTrade({ group, mangoAccount, inputMintPk: inputBank.mint, @@ -462,6 +472,7 @@ const SwapReviewRouteInfo = ({ userDefinedAlts: alts, flashLoanType: { swap: {} }, }) + tx = signature set((s) => { s.successAnimation.swap = true s.swap.amountIn = '' @@ -470,10 +481,16 @@ const SwapReviewRouteInfo = ({ if (soundSettings['swap-success']) { successSound.play() } + sendAnalytics( + { + tx: `${tx}`, + }, + 'swapSuccess', + ) notify({ title: 'Transaction confirmed', type: 'success', - txid: tx, + txid: signature, noSound: true, }) actions.fetchGroup() @@ -483,6 +500,13 @@ const SwapReviewRouteInfo = ({ onSuccess() } } catch (e) { + sendAnalytics( + { + e: `${e}`, + tx: `${tx}`, + }, + 'onSwapError', + ) console.error('onSwap error: ', e) sentry.captureException(e) if (isMangoError(e)) { @@ -547,6 +571,7 @@ const SwapReviewRouteInfo = ({ } } }, [ + sendAnalytics, selectedRoute, wallet.publicKey, slippage, diff --git a/components/swap/useQuoteRoutes.ts b/components/swap/useQuoteRoutes.ts index 05d80313..ffd3581b 100644 --- a/components/swap/useQuoteRoutes.ts +++ b/components/swap/useQuoteRoutes.ts @@ -11,6 +11,7 @@ import { MangoAccount, toUiDecimals } from '@blockworks-foundation/mango-v4' import { findRaydiumPoolInfo, getSwapTransaction } from 'utils/swap/raydium' import mangoStore from '@store/mangoStore' import { fetchJupiterTransaction } from './SwapReviewRouteInfo' +import useAnalytics from 'hooks/useAnalytics' type SwapModes = 'ExactIn' | 'ExactOut' @@ -58,6 +59,7 @@ const fetchJupiterRoute = async ( maxAccounts = 64, connection: Connection, wallet: string, + sendAnalytics?: (data: object, tag: string) => Promise, ) => { return new Promise<{ bestRoute: JupiterV6RouteInfo }>( // eslint-disable-next-line no-async-promise-executor @@ -88,6 +90,15 @@ const fetchJupiterRoute = async ( const response = await fetch( `${JUPITER_V6_QUOTE_API_MAINNET}/quote?${paramsString}`, ) + if (sendAnalytics) { + sendAnalytics( + { + url: `${JUPITER_V6_QUOTE_API_MAINNET}/quote?${paramsString}`, + }, + 'fetchJupiterRoute', + ) + } + const res: JupiterV6RouteInfo = await response.json() if (res.error) { throw res.error @@ -111,6 +122,14 @@ const fetchJupiterRoute = async ( bestRoute: res, }) } catch (e) { + if (sendAnalytics) { + sendAnalytics( + { + error: `${e}`, + }, + 'fetchJupiterRouteError', + ) + } console.log('jupiter route error:', e) reject(e) } @@ -126,11 +145,24 @@ const fetchRaydiumRoute = async ( connection: Connection, wallet: string, isInWalletSwap: boolean, + sendAnalytics?: (data: object, tag: string) => Promise, ) => { return new Promise<{ bestRoute: JupiterV6RouteInfo }>( // eslint-disable-next-line no-async-promise-executor async (resolve, reject) => { try { + if (sendAnalytics) { + sendAnalytics( + { + inputMint, + outputMint, + amount, + slippage, + }, + 'fetchRaydiumRoute', + ) + } + if (!inputMint || !outputMint) return const poolKeys = await findRaydiumPoolInfo( @@ -154,6 +186,14 @@ const fetchRaydiumRoute = async ( throw 'No route found' } } catch (e) { + if (sendAnalytics) { + sendAnalytics( + { + error: `${e}`, + }, + 'raydiumRouteError', + ) + } console.log('raydium route error:', e) reject(e) } @@ -167,6 +207,7 @@ const fetchMangoRoute = async ( amount = 0, slippage = 50, swapMode = 'ExactIn', + sendAnalytics?: (data: object, tag: string) => Promise, ) => { return new Promise<{ bestRoute: JupiterV6RouteInfo }>( // eslint-disable-next-line no-async-promise-executor @@ -183,7 +224,14 @@ const fetchMangoRoute = async ( const response = await fetch( `${MANGO_ROUTER_API_URL}/quote?${paramsString}`, ) - + if (sendAnalytics) { + sendAnalytics( + { + url: `${MANGO_ROUTER_API_URL}/quote?${paramsString}`, + }, + 'fetchMangoRoute', + ) + } if (response.status === 500) { throw 'No route found' } @@ -196,6 +244,14 @@ const fetchMangoRoute = async ( reject('No route found') } } catch (e) { + if (sendAnalytics) { + sendAnalytics( + { + error: `${e}`, + }, + 'mangoRouteError', + ) + } console.log('mango router error:', e) reject(e) } @@ -213,6 +269,7 @@ export async function handleGetRoutes( mangoAccount: MangoAccount | undefined, routingMode: MultiRoutingMode | RaydiumRoutingMode, connection: Connection, + sendAnalytics: ((data: object, tag: string) => Promise) | undefined, inputTokenDecimals: number, ): Promise<{ bestRoute: JupiterV6RouteInfo }> @@ -226,6 +283,7 @@ export async function handleGetRoutes( mangoAccount: MangoAccount | undefined, routingMode: JupiterRoutingMode | MangoRoutingMode, connection: Connection, + sendAnalytics: ((data: object, tag: string) => Promise) | undefined, ): Promise<{ bestRoute: JupiterV6RouteInfo }> export async function handleGetRoutes( @@ -238,6 +296,7 @@ export async function handleGetRoutes( mangoAccount: MangoAccount | undefined, routingMode: RaydiumRoutingMode, connection: Connection, + sendAnalytics: ((data: object, tag: string) => Promise) | undefined, inputTokenDecimals: number, ): Promise<{ bestRoute: JupiterV6RouteInfo }> @@ -251,9 +310,25 @@ export async function handleGetRoutes( mangoAccount: MangoAccount | undefined, routingMode: RoutingMode = 'ALL', connection: Connection, + sendAnalytics: ((data: object, tag: string) => Promise) | undefined, inputTokenDecimals?: number, ) { try { + if (sendAnalytics) { + sendAnalytics( + { + inputMint, + outputMint, + amount, + slippage, + swapMode, + wallet, + routingMode, + }, + 'handleGetRoutes', + ) + } + wallet ||= PublicKey.default.toBase58() let maxAccounts: number @@ -282,6 +357,7 @@ export async function handleGetRoutes( connection, wallet, !mangoAccount, + sendAnalytics, ) routes.push(raydiumRoute) } @@ -300,6 +376,7 @@ export async function handleGetRoutes( maxAccounts, connection, wallet, + sendAnalytics, ) routes.push(jupiterDirectRoute) @@ -316,6 +393,7 @@ export async function handleGetRoutes( maxAccounts, connection, wallet, + sendAnalytics, ) routes.push(jupiterRoute) } @@ -327,6 +405,7 @@ export async function handleGetRoutes( amount, slippage, swapMode, + sendAnalytics, ) routes.push(mangoRoute) } @@ -354,6 +433,14 @@ export async function handleGetRoutes( : null, } } catch (e) { + if (sendAnalytics) { + sendAnalytics( + { + error: `${e}`, + }, + 'noRouteFoundError', + ) + } return { bestRoute: null, } @@ -372,6 +459,7 @@ const useQuoteRoutes = ({ enabled, }: useQuoteRoutesPropTypes) => { const connection = mangoStore((s) => s.connection) + const { sendAnalytics } = useAnalytics() const { inputTokenInfo, outputTokenInfo } = useJupiterSwapData() const decimals = useMemo(() => { return swapMode === 'ExactIn' @@ -419,6 +507,7 @@ const useQuoteRoutes = ({ mangoAccount, routingMode, connection, + sendAnalytics, decimals, ) } else { @@ -432,6 +521,7 @@ const useQuoteRoutes = ({ mangoAccount, routingMode, connection, + sendAnalytics, ) } }, diff --git a/components/trade/TradingViewChart.tsx b/components/trade/TradingViewChart.tsx index 9becb66c..ecd65b87 100644 --- a/components/trade/TradingViewChart.tsx +++ b/components/trade/TradingViewChart.tsx @@ -49,6 +49,7 @@ import { findSerum3MarketPkInOpenOrders } from './OpenOrders' import { Transition } from '@headlessui/react' import useThemeWrapper from 'hooks/useThemeWrapper' import { handleCancelTriggerOrder } from '@components/swap/SwapTriggerOrders' +import useAnalytics from 'hooks/useAnalytics' export interface ChartContainerProps { container: ChartingLibraryWidgetOptions['container'] @@ -93,6 +94,8 @@ const TradingViewChart = () => { const [orderToModify, setOrderToModify] = useState( null, ) + + const { sendAnalytics } = useAnalytics() const [modifiedPrice, setModifiedPrice] = useState('') const [showOrderLinesLocalStorage, toggleShowOrderLinesLocalStorage] = useLocalStorageState(SHOW_ORDER_LINES_KEY, true) @@ -174,6 +177,10 @@ const TradingViewChart = () => { } }, [chartReady, selectedMarketName, showOrderLinesLocalStorage]) + useEffect(() => { + sendAnalytics({ selectedMarketName: selectedMarketName }, 'chart_page') + }, [selectedMarketName, sendAnalytics]) + useEffect(() => { if (showOrderLines !== showOrderLinesLocalStorage) { toggleShowOrderLinesLocalStorage(showOrderLines) diff --git a/hooks/useAnalytics.ts b/hooks/useAnalytics.ts new file mode 100644 index 00000000..51e0b2e8 --- /dev/null +++ b/hooks/useAnalytics.ts @@ -0,0 +1,54 @@ +import useMangoAccount from './useMangoAccount' +import { useWallet } from '@solana/wallet-adapter-react' +import { WHITE_LIST_API } from 'utils/constants' +import useMangoGroup from './useMangoGroup' +import { PublicKey } from '@metaplex-foundation/js' +import { useCallback } from 'react' + +export default function useAnalytics() { + const { group } = useMangoGroup() + const { mangoAccountAddress, mangoAccount } = useMangoAccount() + const { publicKey } = useWallet() + const analyticsTokenBank = group?.getFirstBankByMint( + new PublicKey('EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm'), + ) + const val = + mangoAccount && analyticsTokenBank + ? mangoAccount.getTokenBalanceUi(analyticsTokenBank) * + analyticsTokenBank.uiPrice + : 0 + const sendAnalytics = useCallback( + async (data: object, tag: string) => { + if ( + publicKey?.toBase58() && + tag && + data && + mangoAccountAddress && + val >= 10000 + ) { + const enchantedData = JSON.stringify({ + mangoAccountAddress: mangoAccountAddress, + ...data, + }) + + await fetch(`${WHITE_LIST_API}analytics/add`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + wallet: publicKey.toBase58(), + data: enchantedData, + tag: tag, + }), + }) + } + }, + [val, mangoAccountAddress, publicKey], + ) + + return { + sendAnalytics, + } +}