Skip to content

Commit

Permalink
Merge pull request #299 from pendulum-chain/279-add-new-events-to-goo…
Browse files Browse the repository at this point in the history
…gle-analytics

Add new events to google analytics
  • Loading branch information
ebma authored Nov 28, 2024
2 parents 72d314d + a5fbbce commit b829f7d
Show file tree
Hide file tree
Showing 12 changed files with 137 additions and 36 deletions.
42 changes: 38 additions & 4 deletions src/components/FeeComparison/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import Big from 'big.js';
import { useMemo } from 'preact/hooks';
import { useEffect, useRef } from 'preact/compat';
import { useQuery } from '@tanstack/react-query';
import { ChevronDownIcon } from '@heroicons/react/20/solid';
import { Skeleton } from '../Skeleton';
import vortexIcon from '../../assets/logo/blue.svg';
import { QuoteProvider, quoteProviders } from './quoteProviders';
import { NetworkType } from '../../constants/tokenConfig';
import { OfframpingParameters, useEventsContext } from '../../contexts/events';

type FeeProviderRowProps = FeeComparisonProps & { provider: QuoteProvider };

Expand Down Expand Up @@ -35,13 +37,20 @@ function FeeProviderRow({
vortexPrice,
network,
}: FeeProviderRowProps) {
const { isLoading, error, data } = useQuery({
queryKey: [sourceAssetSymbol, targetAssetSymbol, vortexPrice, provider.name, network],
const { scheduleQuote } = useEventsContext();

const {
isLoading,
error,
data: providerPrice,
} = useQuery({
queryKey: [amount, sourceAssetSymbol, targetAssetSymbol, vortexPrice, provider.name, network],
queryFn: () => provider.query(sourceAssetSymbol, targetAssetSymbol, amount, network),
retry: false, // We don't want to retry the request to avoid spamming the server
});

const providerPrice = data?.lt(0) ? undefined : data;
// The vortex price is sometimes lagging behind the amount (as it first has to be calculated asynchronously)
// We keep a reference to the previous vortex price to avoid spamming the server with the same quote.
const prevVortexPrice = useRef<Big | undefined>(undefined);

const priceDiff = useMemo(() => {
if (isLoading || error || !providerPrice) {
Expand All @@ -51,6 +60,31 @@ function FeeProviderRow({
return providerPrice.minus(vortexPrice);
}, [isLoading, error, providerPrice, vortexPrice]);

useEffect(() => {
if (!isLoading && (providerPrice || error)) {
const parameters: OfframpingParameters = {
from_amount: amount.toFixed(2),
from_asset: sourceAssetSymbol,
to_amount: vortexPrice.toFixed(2),
to_asset: targetAssetSymbol,
};
if (!prevVortexPrice.current || vortexPrice !== prevVortexPrice.current) {
scheduleQuote(provider.name, providerPrice ? providerPrice.toFixed(2, 0) : '-1', parameters);
prevVortexPrice.current = vortexPrice;
}
}
}, [
amount,
provider.name,
isLoading,
scheduleQuote,
sourceAssetSymbol,
targetAssetSymbol,
providerPrice,
vortexPrice,
error,
]);

return (
<div className="flex items-center justify-between w-full">
<a href={provider.href} target="_blank" className="flex items-center gap-4 w-full grow ml-4">
Expand Down
10 changes: 5 additions & 5 deletions src/components/FeeComparison/quoteProviders.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
import { getQueryFnForService, QuoteQuery } from '../../services/quotes';
import { getQueryFnForService, QuoteQuery, QuoteService } from '../../services/quotes';
import alchemyPayIcon from '../../assets/alchemypay.svg';
import moonpayIcon from '../../assets/moonpay.svg';
import transakIcon from '../../assets/transak.svg';

export interface QuoteProvider {
name: string;
name: QuoteService;
icon?: JSX.Element;
query: QuoteQuery;
href: string;
}

export const quoteProviders: QuoteProvider[] = [
{
name: 'AlchemyPay',
name: 'alchemypay',
icon: <img src={alchemyPayIcon} className="w-40 ml-1" alt="AlchemyPay" />,
query: getQueryFnForService('alchemypay'),
href: 'https://alchemypay.org',
},
{
name: 'MoonPay',
name: 'moonpay',
icon: <img src={moonpayIcon} className="w-40 ml-1" alt="Moonpay" />,
query: getQueryFnForService('moonpay'),
href: 'https://moonpay.com',
},
{
name: 'Transak',
name: 'transak',
icon: <img src={transakIcon} className="w-30 h-10" alt="Transak" />,
query: getQueryFnForService('transak'),
href: 'https://transak.com',
Expand Down
17 changes: 9 additions & 8 deletions src/components/Nabla/useSwapForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ export const useSwapForm = () => {
const from = useWatch({ control, name: 'from' });
const to = useWatch({ control, name: 'to' });

const fromToken = from ? INPUT_TOKEN_CONFIG[from] : undefined;
const toToken = to ? OUTPUT_TOKEN_CONFIG[to] : undefined;
const fromToken = useMemo(() => (from ? INPUT_TOKEN_CONFIG[from] : undefined), [from]);
const toToken = useMemo(() => (to ? OUTPUT_TOKEN_CONFIG[to] : undefined), [to]);

const onFromChange = useCallback(
(tokenKey: string) => {
Expand Down Expand Up @@ -96,12 +96,13 @@ export const useSwapForm = () => {
defaultValue: '0',
});

let fromAmount: Big | undefined;
try {
fromAmount = new Big(fromAmountString);
} catch {
// no action required
}
const fromAmount: Big | undefined = useMemo(() => {
try {
return new Big(fromAmountString);
} catch {
return undefined;
}
}, [fromAmountString]);

const openTokenSelectModal = useCallback((type: 'from' | 'to') => {
setTokenModalType(type);
Expand Down
60 changes: 57 additions & 3 deletions src/contexts/events.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { createContext } from 'preact';
import { PropsWithChildren, useCallback, useContext, useEffect, useRef } from 'preact/compat';
import Big from 'big.js';
import * as Sentry from '@sentry/react';
import { useAccount } from 'wagmi';
import { INPUT_TOKEN_CONFIG, OUTPUT_TOKEN_CONFIG } from '../constants/tokenConfig';
import { OfframpingState } from '../services/offrampingFlow';
import { calculateTotalReceive } from '../components/FeeCollapse';
import { QuoteService } from '../services/quotes';

declare global {
interface Window {
Expand Down Expand Up @@ -40,7 +43,7 @@ export interface WalletConnectEvent {
account_address?: string;
}

interface OfframpingParameters {
export interface OfframpingParameters {
from_asset: string;
to_asset: string;
from_amount: string;
Expand All @@ -55,6 +58,14 @@ export type TransactionFailedEvent = OfframpingParameters & {
event: 'transaction_failure';
phase_name: string;
phase_index: number;
error_message: string;
};

export type CompareQuoteEvent = OfframpingParameters & {
event: 'compare_quote';
moonpay_quote?: string;
alchemypay_quote?: string;
transak_quote?: string;
};

export interface ProgressEvent {
Expand Down Expand Up @@ -104,6 +115,7 @@ export type TrackableEvent =
| WalletConnectEvent
| TransactionEvent
| TransactionFailedEvent
| CompareQuoteEvent
| ClickSupportEvent
| FormErrorEvent
| EmailSubmissionEvent
Expand All @@ -122,6 +134,14 @@ const useEvents = () => {
const previousChainId = useRef<number | undefined>(undefined);
const userClickedState = useRef<boolean>(false);

const scheduledQuotes = useRef<
| {
parameters: OfframpingParameters;
quotes: Partial<Record<QuoteService, string>>;
}
| undefined
>(undefined);

const trackedEventTypes = useRef<Set<EventType>>(new Set());
const firedFormErrors = useRef<Set<FormErrorEvent['error_message']>>(new Set());

Expand Down Expand Up @@ -154,6 +174,40 @@ const useEvents = () => {
trackedEventTypes.current = new Set();
}, []);

/// This function is used to schedule a quote returned by a quote service. Once all quotes are ready, it emits a compare_quote event.
/// Calling this function with a quote of '-1' will make the function emit the quote as undefined.
const scheduleQuote = useCallback(
(service: QuoteService, quote: string, parameters: OfframpingParameters) => {
const prev = scheduledQuotes.current;

// Do a deep comparison of the parameters to check if they are the same.
// If they are not, reset the quotes.
const newQuotes =
prev && JSON.stringify(prev.parameters) !== JSON.stringify(parameters)
? { [service]: quote }
: { ...prev?.quotes, [service]: quote };

// If all quotes are ready, emit the event
if (Object.keys(newQuotes).length === 3) {
trackEvent({
...parameters,
event: 'compare_quote',
transak_quote: newQuotes.transak !== '-1' ? newQuotes.transak : undefined,
moonpay_quote: newQuotes.moonpay !== '-1' ? newQuotes.moonpay : undefined,
alchemypay_quote: newQuotes.alchemypay !== '-1' ? newQuotes.alchemypay : undefined,
});
// Reset the quotes
scheduledQuotes.current = undefined;
} else {
scheduledQuotes.current = {
parameters,
quotes: newQuotes,
};
}
},
[trackEvent],
);

useEffect(() => {
if (!chainId) return;

Expand Down Expand Up @@ -214,9 +268,9 @@ const useEvents = () => {
trackEvent,
resetUniqueEvents,
handleUserClickWallet,
scheduleQuote,
};
};

const Context = createContext<UseEventsContext | undefined>(undefined);

export const useEventsContext = () => {
Expand All @@ -240,6 +294,6 @@ export function createTransactionEvent(type: TransactionEvent['event'], state: O
from_asset: INPUT_TOKEN_CONFIG[state.inputTokenType].assetSymbol,
to_asset: OUTPUT_TOKEN_CONFIG[state.outputTokenType].stellarAsset.code.string,
from_amount: state.inputAmount.units,
to_amount: state.outputAmount.units,
to_amount: calculateTotalReceive(Big(state.outputAmount.units), OUTPUT_TOKEN_CONFIG[state.outputTokenType]),
};
}
10 changes: 8 additions & 2 deletions src/hooks/useMainProcess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import { showToast, ToastMessage } from '../helpers/notifications';
import { IAnchorSessionParams, ISep24Intermediate } from '../services/anchor';
import { OFFRAMPING_PHASE_SECONDS } from '../pages/progress';
import { useSiweContext } from '../contexts/siwe';
import { calculateTotalReceive } from '../components/FeeCollapse';

export type SigningPhase = 'started' | 'approved' | 'signed' | 'finished';

export interface ExecutionInput {
Expand Down Expand Up @@ -85,6 +87,7 @@ export const useMainProcess = () => {
event: 'transaction_failure',
phase_name: currentPhase,
phase_index: currentPhaseIndex,
error_message: state.failure.message,
});
}
},
Expand Down Expand Up @@ -132,7 +135,7 @@ export const useMainProcess = () => {
from_asset: INPUT_TOKEN_CONFIG[inputTokenType].assetSymbol,
to_asset: OUTPUT_TOKEN_CONFIG[outputTokenType].stellarAsset.code.string,
from_amount: amountInUnits,
to_amount: offrampAmount.toFixed(2, 0),
to_amount: calculateTotalReceive(offrampAmount, OUTPUT_TOKEN_CONFIG[outputTokenType]),
});

try {
Expand Down Expand Up @@ -219,7 +222,10 @@ export const useMainProcess = () => {
from_asset: INPUT_TOKEN_CONFIG[executionInputState.inputTokenType].assetSymbol,
to_asset: OUTPUT_TOKEN_CONFIG[executionInputState.outputTokenType].stellarAsset.code.string,
from_amount: executionInputState.amountInUnits,
to_amount: executionInputState.offrampAmount.toFixed(2, 0),
to_amount: calculateTotalReceive(
executionInputState.offrampAmount,
OUTPUT_TOKEN_CONFIG[executionInputState.outputTokenType],
),
});

// stop fetching new sep24 url's and clean session variables from the state to be safe.
Expand Down
4 changes: 2 additions & 2 deletions src/pages/failure/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const FailurePage = ({ finishOfframping, continueFailedFlow, transactionI
<Cross />
<h1 className="mt-6 text-2xl font-bold text-center text-red-500">Oops! Something went wrong</h1>
{transactionId && <TransactionInfo transactionId={transactionId} />}
{failure === 'recoverable' ? (
{failure.type === 'recoverable' ? (
<>
<p className="mt-6 text-center">
Unfortunately, your withdrawal request could not be processed in time. This could be due to a temporary
Expand All @@ -36,7 +36,7 @@ export const FailurePage = ({ finishOfframping, continueFailedFlow, transactionI
<p>Either try to continue or start over.</p>
</>
) : undefined}
{failure === 'recoverable' && (
{failure.type === 'recoverable' && (
<button className="w-full mt-5 btn-vortex-primary btn rounded-xl" onClick={continueFailedFlow}>
Continue
</button>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/swap/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export const SwapPage = () => {
const fromToken = INPUT_TOKEN_CONFIG[from];
const toToken = OUTPUT_TOKEN_CONFIG[to];
const formToAmount = form.watch('toAmount');
const vortexPrice = formToAmount ? Big(formToAmount) : Big(0);
const vortexPrice = useMemo(() => (formToAmount ? Big(formToAmount) : Big(0)), [formToAmount]);

const userInputTokenBalance = useInputTokenBalance({ fromToken });

Expand Down
10 changes: 6 additions & 4 deletions src/services/nabla.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,9 @@ export async function nablaApprove(
const inputToken = INPUT_TOKEN_CONFIG[inputTokenType];

if (!transactions) {
console.error('Missing transactions for nablaApprove');
return { ...state, failure: 'unrecoverable' };
const message = 'Missing transactions for nablaApprove';
console.error(message);
return { ...state, failure: { type: 'unrecoverable', message } };
}

const successorState = {
Expand Down Expand Up @@ -319,8 +320,9 @@ export async function nablaSwap(state: OfframpingState, { renderEvent }: Executi
const outputToken = OUTPUT_TOKEN_CONFIG[outputTokenType];

if (transactions === undefined) {
console.error('Missing transactions for nablaSwap');
return { ...state, failure: 'unrecoverable' };
const message = 'Missing transactions for nablaSwap';
console.error(message);
return { ...state, failure: { type: 'unrecoverable', message } };
}

const { api, ss58Format } = (await getApiManagerInstance()).apiData!;
Expand Down
7 changes: 5 additions & 2 deletions src/services/offrampingFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ import * as Sentry from '@sentry/react';

const minutesInMs = (minutes: number) => minutes * 60 * 1000;

export type FailureType = 'recoverable' | 'unrecoverable';
export interface FailureType {
type: 'recoverable' | 'unrecoverable';
message?: string;
}

export type OfframpingPhase =
| 'prepareTransactions'
Expand Down Expand Up @@ -281,7 +284,7 @@ export async function advanceOfframpingState(
}

console.error('Error advancing offramping state', error);
newState = { ...state, failure: 'recoverable' };
newState = { ...state, failure: { type: 'recoverable', message: error?.toString() } };
}

if (newState !== undefined) {
Expand Down
5 changes: 3 additions & 2 deletions src/services/polkadot/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,9 @@ export async function executeSpacewalkRedeem(
}

if (!transactions) {
console.error('Transactions not prepared, cannot execute Spacewalk redeem');
return { ...state, failure: 'unrecoverable' };
const message = 'Transactions not prepared, cannot execute Spacewalk redeem';
console.error(message);
return { ...state, failure: { type: 'unrecoverable', message } };
}
let redeemRequestEvent;

Expand Down
2 changes: 1 addition & 1 deletion src/services/quotes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { polygon } from 'wagmi/chains';

const QUOTE_ENDPOINT = `${SIGNING_SERVICE_URL}/v1/quotes`;

type QuoteService = 'moonpay' | 'transak' | 'alchemypay';
export type QuoteService = 'moonpay' | 'transak' | 'alchemypay';

type SupportedNetworks = typeof polygon.name;

Expand Down
Loading

0 comments on commit b829f7d

Please sign in to comment.