Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add input_amount to events #451

Open
wants to merge 4 commits into
base: staging
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/components/Nabla/useSwapForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { storageService } from '../../services/storage/local';
import schema, { SwapFormValues } from './schema';
import { getCaseSensitiveNetwork } from '../../helpers/networks';
import { useNetwork } from '../../contexts/network';
import { useFormStoreActions } from '../../stores/formStore';

type SwapSettings = {
from: string;
Expand All @@ -40,6 +41,7 @@ export const useSwapForm = () => {
const [isTokenSelectModalVisible, setIsTokenSelectModalVisible] = useState(false);
const [tokenSelectModalType, setTokenModalType] = useState<TokenSelectType>('from');
const { selectedNetwork, setSelectedNetwork } = useNetwork();
const { setFromAmount } = useFormStoreActions();

const initialState = useMemo(() => {
const searchParams = new URLSearchParams(window.location.search);
Expand Down Expand Up @@ -129,7 +131,9 @@ export const useSwapForm = () => {

const fromAmount: Big | undefined = useMemo(() => {
try {
return new Big(fromAmountString);
const fromAmount = new Big(fromAmountString);
setFromAmount(fromAmount);
return fromAmount;
} catch {
return undefined;
}
Expand Down
10 changes: 10 additions & 0 deletions src/contexts/events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { getNetworkId, isNetworkEVM, Networks } from '../helpers/networks';
import { LocalStorageKeys } from '../hooks/useLocalStorage';
import { storageService } from '../services/storage/local';
import { useNetwork } from './network';
import { useFromAmount } from '../stores/formStore';

declare global {
interface Window {
Expand All @@ -34,6 +35,7 @@ const UNIQUE_EVENT_TYPES: TrackableEvent['event'][] = [

export interface AmountTypeEvent {
event: `amount_type`;
input_amount: string;
}

export interface ClickDetailsEvent {
Expand All @@ -43,7 +45,9 @@ export interface ClickDetailsEvent {
export interface WalletConnectEvent {
event: 'wallet_connect';
wallet_action: 'connect' | 'disconnect' | 'change';
input_amount?: string;
account_address?: string;
network_selected?: string;
}

export interface OfframpingParameters {
Expand Down Expand Up @@ -105,6 +109,7 @@ export interface NetworkChangeEvent {

export interface FormErrorEvent {
event: 'form_error';
input_amount: string;
error_message:
| 'insufficient_balance'
| 'insufficient_liquidity'
Expand Down Expand Up @@ -149,6 +154,7 @@ const useEvents = () => {
const previousChainId = useRef<number | undefined>(undefined);
const firstRender = useRef(true);
const { selectedNetwork } = useNetwork();
const fromAmount = useFromAmount();

const scheduledQuotes = useRef<
| {
Expand Down Expand Up @@ -279,12 +285,16 @@ const useEvents = () => {
event: 'wallet_connect',
wallet_action: 'disconnect',
account_address: previous,
input_amount: fromAmount ? fromAmount.toString() : '0',
network_selected: getNetworkId(selectedNetwork).toString(),
});
} else if (wasChanged) {
trackEvent({
event: 'wallet_connect',
wallet_action: wasConnected ? 'change' : 'connect',
account_address: address,
input_amount: fromAmount ? fromAmount.toString() : '0',
network_selected: getNetworkId(selectedNetwork).toString(),
});
}

Expand Down
6 changes: 5 additions & 1 deletion src/hooks/nabla/useTokenAmountOut.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,11 @@ export function useTokenOutAmount({
},
parseError: (error) => {
const insufficientLiquidityMessage = () => {
trackEvent({ event: 'form_error', error_message: 'insufficient_liquidity' });
trackEvent({
event: 'form_error',
error_message: 'insufficient_liquidity',
input_amount: amountIn ? amountIn : '0',
});
return 'Insufficient liquidity for this exchange. Please try a smaller amount or try again later.';
};

Expand Down
34 changes: 28 additions & 6 deletions src/pages/swap/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Big from 'big.js';
import { useEffect, useMemo, useRef, useState, useCallback, FormEvent } from 'react';
import { useEffect, useMemo, useRef, useState, useCallback, FormEvent, useDeferredValue } from 'react';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You added the import but we are not actually using this. It's not a requirement in the ticket. I'm fine if we just send a new event for every user interaction without debouncing it.

import { ApiPromise } from '@polkadot/api';
import { motion } from 'framer-motion';

Expand Down Expand Up @@ -71,6 +71,7 @@ import satoshipayLogo from '../../assets/logo/satoshipay.svg';
export const SwapPage = () => {
const formRef = useRef<HTMLDivElement | null>(null);
const feeComparisonRef = useRef<FeeComparisonRef>(null);
const fromAmountRef = useRef<Big | undefined>(undefined);
const pendulumNode = usePendulumNode();
const trackQuote = useRef(false);
const [api, setApi] = useState<ApiPromise | null>(null);
Expand Down Expand Up @@ -268,6 +269,10 @@ export const SwapPage = () => {
[toToken.fiat.assetIcon, toToken.fiat.symbol, form, tokenOutAmount.isLoading, openTokenSelectModal],
);

useEffect(() => {
fromAmountRef.current = fromAmount;
}, [fromAmount]);

const WithdrawNumericInput = useMemo(
() => (
<>
Expand All @@ -278,7 +283,12 @@ export const SwapPage = () => {
onClick={() => openTokenSelectModal('from')}
onChange={() => {
// User interacted with the input field
trackEvent({ event: 'amount_type' });
setTimeout(() => {
trackEvent({
event: 'amount_type',
input_amount: fromAmountRef.current ? fromAmountRef.current.toString() : '0',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason we are using a ref, and especially using it only here? Alternatively, we could change the onChange prop of the AssetNumericInput component and pass teh new value in it. So you could access it right away. Here, you would then be able to do

onChange={(newValue: string) => {
   trackEvent({...})
}}

});
}, 3000);
// This also enables the quote tracking events
trackQuote.current = true;
}}
Expand All @@ -287,15 +297,19 @@ export const SwapPage = () => {
<UserBalance token={fromToken} onClick={(amount: string) => form.setValue('fromAmount', amount)} />
</>
),
[form, fromToken, openTokenSelectModal, trackEvent],
[form, fromAmount, fromToken, openTokenSelectModal, trackEvent],
);

function getCurrentErrorMessage() {
if (isDisconnected) return;

if (typeof userInputTokenBalance === 'string') {
if (Big(userInputTokenBalance).lt(fromAmount ?? 0) && walletAccount) {
trackEvent({ event: 'form_error', error_message: 'insufficient_balance' });
trackEvent({
event: 'form_error',
error_message: 'insufficient_balance',
input_amount: fromAmount ? fromAmount.toString() : '0',
});
return `Insufficient balance. Your balance is ${userInputTokenBalance} ${fromToken?.assetSymbol}.`;
}
}
Expand All @@ -307,14 +321,22 @@ export const SwapPage = () => {
const minAmountUnits = multiplyByPowerOfTen(Big(toToken.minWithdrawalAmountRaw), -toToken.decimals);

if (maxAmountUnits.lt(amountOut)) {
trackEvent({ event: 'form_error', error_message: 'more_than_maximum_withdrawal' });
trackEvent({
event: 'form_error',
error_message: 'more_than_maximum_withdrawal',
input_amount: fromAmount ? fromAmount.toString() : '0',
});
return `Maximum withdrawal amount is ${stringifyBigWithSignificantDecimals(maxAmountUnits, 2)} ${
toToken.fiat.symbol
}.`;
}

if (!config.test.overwriteMinimumTransferAmount && minAmountUnits.gt(amountOut)) {
trackEvent({ event: 'form_error', error_message: 'less_than_minimum_withdrawal' });
trackEvent({
event: 'form_error',
error_message: 'less_than_minimum_withdrawal',
input_amount: fromAmount ? fromAmount.toString() : '0',
});
return `Minimum withdrawal amount is ${stringifyBigWithSignificantDecimals(minAmountUnits, 2)} ${
toToken.fiat.symbol
}.`;
Expand Down
37 changes: 37 additions & 0 deletions src/stores/formStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { create } from 'zustand';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pendulum-chain/devs let me know if you like the addition of this state, if you prefer it to be more minimal (only fromAmount) or to avoid this altogether.

Although it adds extra code, I found it less confusing than passing fromAmount to the event context.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with keeping it the way you implemented it 👍

import Big from 'big.js';
import { InputTokenDetails, OutputTokenDetails } from '../constants/tokenConfig';

interface FormState {
fromAmount?: Big;
fromToken?: InputTokenDetails;
toToken?: OutputTokenDetails;
}

interface FormStoreActions {
setFromAmount: (amount?: Big) => void;
setFromToken: (token?: InputTokenDetails) => void;
setToToken: (token?: OutputTokenDetails) => void;
}

type FormStore = FormState & {
actions: FormStoreActions;
};

const useFormStore = create<FormStore>((set) => ({
fromAmount: undefined,
fromToken: undefined,
toToken: undefined,

actions: {
setFromAmount: (amount?: Big) => set({ fromAmount: amount }),
setFromToken: (token?: InputTokenDetails) => set({ fromToken: token }),
setToToken: (token?: OutputTokenDetails) => set({ toToken: token }),
},
}));

export const useFromAmount = () => useFormStore((state) => state.fromAmount);
export const useFromToken = () => useFormStore((state) => state.fromToken);
export const useToToken = () => useFormStore((state) => state.toToken);

export const useFormStoreActions = () => useFormStore((state) => state.actions);
Loading