diff --git a/src/constants/analytics.ts b/src/constants/analytics.ts
index 9510070aa..51688374a 100644
--- a/src/constants/analytics.ts
+++ b/src/constants/analytics.ts
@@ -328,6 +328,25 @@ export const AnalyticsEvents = unionize(
assetSymbol?: string;
assetName?: string;
}>(),
+
+ // vaults
+ ClickViewVaultFromPositionCard: ofType<{}>(),
+ ClickViewVaultFromOverview: ofType<{}>(),
+
+ EnterValidVaultAmountForm: ofType<{}>(),
+ VaultFormPreviewStep: ofType<{ operation: 'DEPOSIT' | 'WITHDRAW'; amount: number }>(),
+ AttemptVaultOperation: ofType<{
+ operation: 'DEPOSIT' | 'WITHDRAW';
+ amount: number;
+ slippage: number | null | undefined;
+ }>(),
+ VaultOperationPreAborted: ofType<{ operation: 'DEPOSIT' | 'WITHDRAW'; amount: number }>(),
+ SuccessfulVaultOperation: ofType<{
+ operation: 'DEPOSIT' | 'WITHDRAW';
+ amount: number;
+ amountDiff: number | null | undefined;
+ }>(),
+ VaultOperationProtocolError: ofType<{ operation: 'DEPOSIT' | 'WITHDRAW' }>(),
},
{ tag: 'type' as const, value: 'payload' as const }
);
diff --git a/src/hooks/vaultsHooks.ts b/src/hooks/vaultsHooks.ts
index d979463ac..f630ef646 100644
--- a/src/hooks/vaultsHooks.ts
+++ b/src/hooks/vaultsHooks.ts
@@ -15,6 +15,7 @@ import {
VaultFormAction,
VaultFormData,
} from '@/constants/abacus';
+import { AnalyticsEvents } from '@/constants/analytics';
import { STRING_KEYS, StringGetterFunction } from '@/constants/localization';
import { timeUnits } from '@/constants/time';
@@ -22,6 +23,7 @@ import { selectSubaccountStateForVaults } from '@/state/accountCalculators';
import { getVaultForm, selectVaultFormStateExceptAmount } from '@/state/vaultSelectors';
import abacusStateManager from '@/lib/abacus';
+import { track } from '@/lib/analytics/analytics';
import { assertNever } from '@/lib/assertNever';
import { MustBigNumber } from '@/lib/numbers';
import { safeStringifyForAbacusParsing } from '@/lib/stringifyHelpers';
@@ -230,12 +232,20 @@ const VAULT_FORM_AMOUNT_DEBOUNCE_MS = 500;
const useVaultFormAmountDebounced = () => {
const amount = useAppSelector((state) => state.vaults.vaultForm.amount);
const debouncedAmount = useDebounce(amount, VAULT_FORM_AMOUNT_DEBOUNCE_MS);
+
+ useEffect(() => {
+ if (MustBigNumber(debouncedAmount).gt(0)) {
+ track(AnalyticsEvents.EnterValidVaultAmountForm());
+ }
+ }, [debouncedAmount]);
+
// if the user goes back to the beginning, use that value for calculations
// this fixes an issue where the validation logic would show an error for a second after submission
// when we reset the value
if (amount === '') {
return amount;
}
+
return debouncedAmount;
};
diff --git a/src/pages/portfolio/AccountOverviewSection.tsx b/src/pages/portfolio/AccountOverviewSection.tsx
index 03c1bd7ed..e2d515fde 100644
--- a/src/pages/portfolio/AccountOverviewSection.tsx
+++ b/src/pages/portfolio/AccountOverviewSection.tsx
@@ -4,6 +4,7 @@ import { shallowEqual } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import styled from 'styled-components';
+import { AnalyticsEvents } from '@/constants/analytics';
import { ButtonAction } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { AppRoute } from '@/constants/routes';
@@ -21,6 +22,7 @@ import { WithLabel } from '@/components/WithLabel';
import { getSubaccount } from '@/state/accountSelectors';
import { useAppSelector } from '@/state/appTypes';
+import { track } from '@/lib/analytics/analytics';
import { runIf } from '@/lib/do';
import { isTruthy } from '@/lib/isTruthy';
import { testFlags } from '@/lib/testFlags';
@@ -54,6 +56,7 @@ export const AccountOverviewSection = () => {
const totalValue = runIf(equity?.current, (e) => e + (vaultBalance ?? 0));
const handleViewVault = useCallback(() => {
+ track(AnalyticsEvents.ClickViewVaultFromOverview());
navigate(`${AppRoute.Vault}`, {
state: { from: AppRoute.Portfolio },
});
diff --git a/src/pages/portfolio/Positions.tsx b/src/pages/portfolio/Positions.tsx
index e8c37b4ff..475e59586 100644
--- a/src/pages/portfolio/Positions.tsx
+++ b/src/pages/portfolio/Positions.tsx
@@ -3,6 +3,7 @@ import { useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import styled from 'styled-components';
+import { AnalyticsEvents } from '@/constants/analytics';
import { STRING_KEYS } from '@/constants/localization';
import { AppRoute, PortfolioRoute } from '@/constants/routes';
@@ -19,6 +20,7 @@ import { PositionsTable, PositionsTableColumnKey } from '@/views/tables/Position
import { calculateShouldRenderActionsInPositionsTable } from '@/state/accountCalculators';
+import { track } from '@/lib/analytics/analytics';
import { isTruthy } from '@/lib/isTruthy';
import { MaybeUnopenedIsolatedPositionsPanel } from '../trade/UnopenedIsolatedPositions';
@@ -41,6 +43,7 @@ export const Positions = () => {
}, [navigate]);
const handleViewVault = useCallback(() => {
+ track(AnalyticsEvents.ClickViewVaultFromPositionCard());
navigate(`${AppRoute.Vault}`, {
state: { from: AppRoute.Portfolio },
});
diff --git a/src/pages/vaults/VaultDepositWithdrawForm.tsx b/src/pages/vaults/VaultDepositWithdrawForm.tsx
index 848992006..412ed5afd 100644
--- a/src/pages/vaults/VaultDepositWithdrawForm.tsx
+++ b/src/pages/vaults/VaultDepositWithdrawForm.tsx
@@ -6,6 +6,7 @@ import styled, { css } from 'styled-components';
import tw from 'twin.macro';
import { AlertType } from '@/constants/alerts';
+import { AnalyticsEvents } from '@/constants/analytics';
import { ButtonAction, ButtonShape, ButtonSize, ButtonType } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { QUANTUM_MULTIPLIER } from '@/constants/numbers';
@@ -52,6 +53,7 @@ import {
setVaultFormSlippageAck,
} from '@/state/vaults';
+import { track } from '@/lib/analytics/analytics';
import { dd } from '@/lib/analytics/datadog';
import { assertNever } from '@/lib/assertNever';
import { runFn } from '@/lib/do';
@@ -169,6 +171,13 @@ export const VaultDepositWithdrawForm = ({
if (isSubmitting) {
return;
}
+ track(
+ AnalyticsEvents.AttemptVaultOperation({
+ amount: MustBigNumber(amount).toNumber(),
+ operation,
+ slippage: validationResponse.summaryData.estimatedSlippage,
+ })
+ );
setIsSubmitting(true);
try {
const { submissionData } = validationResponse;
@@ -180,6 +189,12 @@ export const VaultDepositWithdrawForm = ({
title: stringGetter({ key: STRING_KEYS.MEGAVAULT_CANT_SUBMIT }),
body: stringGetter({ key: STRING_KEYS.MEGAVAULT_CANT_SUBMIT_BODY }),
});
+ track(
+ AnalyticsEvents.VaultOperationPreAborted({
+ amount: MustBigNumber(amount).toNumber(),
+ operation,
+ })
+ );
dd.error('Megavault deposit blocked, invalid validation response amount', {
deposit: submissionData?.deposit,
});
@@ -192,6 +207,13 @@ export const VaultDepositWithdrawForm = ({
await depositToMegavault(cachedAmount);
+ track(
+ AnalyticsEvents.SuccessfulVaultOperation({
+ amount: MustBigNumber(amount).toNumber(),
+ operation,
+ amountDiff: undefined,
+ })
+ );
notify({
title: stringGetter({ key: STRING_KEYS.MEGAVAULT_DEPOSIT_SUCCESSFUL }),
slotTitleLeft: <$SmallIcon iconName={IconName.CheckCircle} />,
@@ -218,6 +240,12 @@ export const VaultDepositWithdrawForm = ({
title: stringGetter({ key: STRING_KEYS.MEGAVAULT_CANT_SUBMIT }),
body: stringGetter({ key: STRING_KEYS.MEGAVAULT_CANT_SUBMIT_BODY }),
});
+ track(
+ AnalyticsEvents.VaultOperationPreAborted({
+ amount: MustBigNumber(amount).toNumber(),
+ operation,
+ })
+ );
dd.error('Megavault withdraw blocked, invalid validation response values', {
withdraw: submissionData?.withdraw,
});
@@ -228,16 +256,25 @@ export const VaultDepositWithdrawForm = ({
return;
}
+ const preEstimate = validationResponse.summaryData.estimatedAmountReceived;
const result = await withdrawFromMegavault(
submissionData?.withdraw?.shares,
submissionData?.withdraw?.minAmount
);
- const events = (result as IndexedTx).events;
+ const events = (result as IndexedTx)?.events;
const actualAmount = events
- .find((e) => e.type === 'withdraw_from_megavault')
+ ?.find((e) => e.type === 'withdraw_from_megavault')
?.attributes.find((a) => a.key === 'redeemed_quote_quantums')?.value;
-
+ const realAmountReceived = MustBigNumber(actualAmount).div(QUANTUM_MULTIPLIER).toNumber();
+
+ track(
+ AnalyticsEvents.SuccessfulVaultOperation({
+ amount: realAmountReceived,
+ operation,
+ amountDiff: Math.abs((preEstimate ?? 0) - (realAmountReceived ?? 0)),
+ })
+ );
notify({
slotTitleLeft: <$SmallIcon iconName={IconName.CheckCircle} />,
title: stringGetter({ key: STRING_KEYS.MEGAVAULT_WITHDRAWAL_SUCCESSFUL }),
@@ -251,7 +288,7 @@ export const VaultDepositWithdrawForm = ({
),
},
@@ -281,7 +318,11 @@ export const VaultDepositWithdrawForm = ({
: STRING_KEYS.MEGAVAULT_WITHDRAWAL_FAILED_BODY,
}),
});
-
+ track(
+ AnalyticsEvents.VaultOperationProtocolError({
+ operation,
+ })
+ );
dd.error('Megavault transaction failed', { ...validationResponse.submissionData }, e);
// eslint-disable-next-line no-console
console.error('Error submitting megavault transaction', e);
@@ -573,7 +614,15 @@ export const VaultDepositWithdrawForm = ({