Skip to content

Commit

Permalink
feat: add tracking to staking (#710)
Browse files Browse the repository at this point in the history
  • Loading branch information
rosepuppy authored Jun 24, 2024
1 parent 1f95a09 commit cb3fb25
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 136 deletions.
244 changes: 114 additions & 130 deletions src/constants/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,138 +119,122 @@ export enum AnalyticsEvent {

// Notification
NotificationAction = 'NotificationAction',

// Staking
StakeTransaction = 'StakeTransaction',
UnstakeTransaction = 'UnstakeTransaction',
StakeInput = 'StakeInput',
UnstakeInput = 'UnstakeInput',
ClaimTransaction = 'ClaimTransaction',
}

export type AnalyticsEventData<T extends AnalyticsEvent> =
// App
T extends AnalyticsEvent.AppStart
? {}
: T extends AnalyticsEvent.NetworkStatus
? {
status: (typeof AbacusApiStatus)['name'];
/** Last time indexer node was queried successfully */
lastSuccessfulIndexerRpcQuery?: number;
/** Time elapsed since indexer node was queried successfully */
elapsedTime?: number;
blockHeight?: number;
indexerBlockHeight?: number;
trailingBlocks?: number;
}
: // Navigation
T extends AnalyticsEvent.NavigatePage
? {
path: string;
}
: T extends AnalyticsEvent.NavigateDialog
? {
type: DialogTypes;
}
: T extends AnalyticsEvent.NavigateDialogClose
? {
type: DialogTypes;
}
: T extends AnalyticsEvent.NavigateExternal
? {
link: string;
}
: // Wallet
T extends AnalyticsEvent.ConnectWallet
? {
walletType: WalletType;
walletConnectionType: WalletConnectionType;
}
: T extends AnalyticsEvent.DisconnectWallet
? {}
: // Onboarding
T extends AnalyticsEvent.OnboardingStepChanged
? {
state: OnboardingState;
step?: OnboardingSteps;
}
: T extends AnalyticsEvent.OnboardingAccountDerived
? {
hasPreviousTransactions: boolean;
}
: // Transfers
T extends AnalyticsEvent.TransferFaucet
? {}
: T extends AnalyticsEvent.TransferFaucetConfirmed
? {
/** roundtrip time between user placing an order and confirmation from indexer (client → validator → indexer → client) */
roundtripMs: number;
/** URL/IP of node the order was sent to */
validatorUrl: string;
}
: T extends AnalyticsEvent.TransferDeposit
? {
chainId?: string;
tokenAddress?: string;
tokenSymbol?: string;
slippage?: number;
gasFee?: number;
bridgeFee?: number;
exchangeRate?: number;
estimatedRouteDuration?: number;
toAmount?: number;
toAmountMin?: number;
}
: T extends AnalyticsEvent.TransferWithdraw
? {
chainId?: string;
tokenAddress?: string;
tokenSymbol?: string;
slippage?: number;
gasFee?: number;
bridgeFee?: number;
exchangeRate?: number;
estimatedRouteDuration?: number;
toAmount?: number;
toAmountMin?: number;
}
: // Trading
T extends AnalyticsEvent.TradeOrderTypeSelected
? {
type: TradeTypes;
}
: T extends AnalyticsEvent.TradePlaceOrder
? HumanReadablePlaceOrderPayload & {
isClosePosition: boolean;
}
: T extends AnalyticsEvent.TradePlaceOrderConfirmed
? {
/** roundtrip time between user placing an order and confirmation from indexer (client → validator → indexer → client) */
roundtripMs: number;
/** URL/IP of node the order was sent to */
validatorUrl: string;
}
: T extends AnalyticsEvent.TradeCancelOrder
? {}
: T extends AnalyticsEvent.TradeCancelOrderConfirmed
? {
/** roundtrip time between user canceling an order and confirmation from indexer (client → validator → indexer → client) */
roundtripMs: number;
/** URL/IP of node the order was sent to */
validatorUrl: string;
}
: // Notifcation
T extends AnalyticsEvent.NotificationAction
? {
type: string;
id: string;
}
: T extends AnalyticsEvent.TransferNotification
? {
type: TransferNotificationTypes | undefined;
toAmount: number | undefined;
timeSpent:
| Record<string, number>
| number
| undefined;
txHash: string;
status: 'new' | 'success' | 'error';
triggeredAt: number | undefined;
}
: never;
type AnalyticsEventDataMap = {
[AnalyticsEvent.AppStart]: {};
[AnalyticsEvent.NetworkStatus]: {
status: (typeof AbacusApiStatus)['name'];
lastSuccessfulIndexerRpcQuery?: number;
elapsedTime?: number;
blockHeight?: number;
indexerBlockHeight?: number;
trailingBlocks?: number;
};
[AnalyticsEvent.NavigatePage]: { path: string };
[AnalyticsEvent.NavigateDialog]: { type: DialogTypes };
[AnalyticsEvent.NavigateDialogClose]: { type: DialogTypes };
[AnalyticsEvent.NavigateExternal]: { link: string };
[AnalyticsEvent.ConnectWallet]: {
walletType: WalletType;
walletConnectionType: WalletConnectionType;
};
[AnalyticsEvent.DisconnectWallet]: {};
[AnalyticsEvent.OnboardingStepChanged]: {
state: OnboardingState;
step?: OnboardingSteps;
};
[AnalyticsEvent.OnboardingAccountDerived]: {
hasPreviousTransactions: boolean;
};
[AnalyticsEvent.TransferFaucet]: {};
[AnalyticsEvent.TransferFaucetConfirmed]: {
roundtripMs: number;
validatorUrl: string;
};
[AnalyticsEvent.TransferDeposit]: {
chainId?: string;
tokenAddress?: string;
tokenSymbol?: string;
slippage?: number;
gasFee?: number;
bridgeFee?: number;
exchangeRate?: number;
estimatedRouteDuration?: number;
toAmount?: number;
toAmountMin?: number;
};
[AnalyticsEvent.TransferWithdraw]: {
chainId?: string;
tokenAddress?: string;
tokenSymbol?: string;
slippage?: number;
gasFee?: number;
bridgeFee?: number;
exchangeRate?: number;
estimatedRouteDuration?: number;
toAmount?: number;
toAmountMin?: number;
};
[AnalyticsEvent.TradeOrderTypeSelected]: { type: TradeTypes };
[AnalyticsEvent.TradePlaceOrder]: HumanReadablePlaceOrderPayload & {
isClosePosition: boolean;
};
[AnalyticsEvent.TradePlaceOrderConfirmed]: {
roundtripMs: number;
validatorUrl: string;
};
[AnalyticsEvent.TradeCancelOrder]: {};
[AnalyticsEvent.TradeCancelOrderConfirmed]: {
roundtripMs: number;
validatorUrl: string;
};
[AnalyticsEvent.NotificationAction]: {
type: string;
id: string;
};
[AnalyticsEvent.TransferNotification]: {
type: TransferNotificationTypes | undefined;
toAmount: number | undefined;
timeSpent: Record<string, number> | number | undefined;
txHash: string;
status: 'new' | 'success' | 'error';
triggeredAt: number | undefined;
};
[AnalyticsEvent.StakeTransaction]: {
txHash?: string;
amount?: number;
validatorAddress?: string;
};
[AnalyticsEvent.UnstakeTransaction]: {
txHash?: string;
amount?: number;
validatorAddresses?: string[];
};
[AnalyticsEvent.ClaimTransaction]: {
txHash?: string;
amount?: string;
};
[AnalyticsEvent.StakeInput]: {
amount?: number;
validatorAddress?: string;
};
[AnalyticsEvent.UnstakeInput]: {
amount?: number;
validatorAddress?: string;
};
};

export type AnalyticsEventData<T extends AnalyticsEvent> = T extends keyof AnalyticsEventDataMap
? AnalyticsEventDataMap[T]
: never;

export const DEFAULT_TRANSACTION_MEMO = 'dYdX Frontend (web)';
export const lastSuccessfulRestRequestByOrigin: Record<URL['origin'], number> = {};
Expand Down
13 changes: 11 additions & 2 deletions src/views/dialogs/StakingRewardDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import styled, { css } from 'styled-components';
import { formatUnits } from 'viem';

import { AlertType } from '@/constants/alerts';
import { AnalyticsEvent } from '@/constants/analytics';
import { STRING_KEYS } from '@/constants/localization';
import { NumberSign, SMALL_USD_DECIMALS } from '@/constants/numbers';

Expand All @@ -29,8 +30,10 @@ import { getSubaccountEquity } from '@/state/accountSelectors';
import { useAppSelector } from '@/state/appTypes';
import { getChartDotBackground } from '@/state/configsSelectors';

import { track } from '@/lib/analytics';
import { BigNumberish, MustBigNumber } from '@/lib/numbers';
import { log } from '@/lib/telemetry';
import { hashFromTx } from '@/lib/txUtils';

type ElementProps = {
validators: string[];
Expand Down Expand Up @@ -117,7 +120,13 @@ export const StakingRewardDialog = ({ validators, usdcRewards, setIsOpen }: Elem
try {
setIsLoading(true);
setError(undefined);
await withdrawReward(validators);
const tx = await withdrawReward(validators);
const txHash = hashFromTx(tx.hash);

track(AnalyticsEvent.ClaimTransaction, {
txHash,
amount: usdcRewards.toString(),
});
setIsOpen(false);
} catch (err) {
log('StakeRewardDialog/withdrawReward', err);
Expand All @@ -129,7 +138,7 @@ export const StakingRewardDialog = ({ validators, usdcRewards, setIsOpen }: Elem
} finally {
setIsLoading(false);
}
}, [validators, withdrawReward, setIsOpen]);
}, [validators, withdrawReward, setIsOpen, usdcRewards]);

return (
<$Dialog isOpen setIsOpen={setIsOpen} hasHeaderBlur={false}>
Expand Down
27 changes: 25 additions & 2 deletions src/views/forms/StakeForm/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { useCallback, useEffect, useState, type FormEvent } from 'react';
import { useCallback, useEffect, useMemo, useState, type FormEvent } from 'react';

import BigNumber from 'bignumber.js';
import { debounce } from 'lodash';
import { type NumberFormatValues } from 'react-number-format';
import styled from 'styled-components';
import { formatUnits } from 'viem';

import { AMOUNT_RESERVED_FOR_GAS_DYDX } from '@/constants/account';
import { AlertType } from '@/constants/alerts';
import { AnalyticsEvent } from '@/constants/analytics';
import { DialogTypes } from '@/constants/dialogs';
import { STRING_KEYS } from '@/constants/localization';
import { NumberSign } from '@/constants/numbers';
Expand All @@ -33,8 +35,10 @@ import { StakeButtonAndReceipt } from '@/views/forms/StakeForm/StakeButtonAndRec
import { useAppDispatch } from '@/state/appTypes';
import { forceOpenDialog } from '@/state/dialogs';

import { track } from '@/lib/analytics';
import { BigNumberish, MustBigNumber } from '@/lib/numbers';
import { log } from '@/lib/telemetry';
import { hashFromTx } from '@/lib/txUtils';

type StakeFormProps = {
onDone?: () => void;
Expand Down Expand Up @@ -105,8 +109,20 @@ export const StakeForm = ({ onDone, className }: StakeFormProps) => {
}
}, [getDelegateFee, amountBN, selectedValidator, isAmountValid, chainTokenDecimals]);

const debouncedChangeTrack = useMemo(
() =>
debounce((amount?: number, validator?: string) => {
track(AnalyticsEvent.StakeInput, {
amount,
validatorAddress: validator,
});
}, 1000),
[]
);

const onChangeAmount = (value?: BigNumber) => {
setAmountBN(value);
debouncedChangeTrack(value?.toNumber(), selectedValidator?.operatorAddress);
};

const onStake = useCallback(async () => {
Expand All @@ -115,7 +131,14 @@ export const StakeForm = ({ onDone, className }: StakeFormProps) => {
}
try {
setIsLoading(true);
await delegate(selectedValidator.operatorAddress, amountBN.toNumber());
const tx = await delegate(selectedValidator.operatorAddress, amountBN.toNumber());
const txHash = hashFromTx(tx.hash);

track(AnalyticsEvent.StakeTransaction, {
txHash,
amount: amountBN.toNumber(),
validatorAddress: selectedValidator.operatorAddress,
});
onDone?.();
} catch (err) {
log('StakeForm/onStake', err);
Expand Down
Loading

0 comments on commit cb3fb25

Please sign in to comment.