Skip to content

Commit

Permalink
Merge pull request #396 from pendulum-chain/391-add-value-to-form_err…
Browse files Browse the repository at this point in the history
…or-event

Add initialization error events
  • Loading branch information
ebma authored Jan 30, 2025
2 parents c6fc5e9 + a7a8b90 commit 8d72665
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 33 deletions.
34 changes: 33 additions & 1 deletion src/contexts/events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { calculateTotalReceive } from '../components/FeeCollapse';
import { QuoteService } from '../services/quotes';
import { useVortexAccount } from '../hooks/useVortexAccount';
import { Networks } from '../helpers/networks';
import { storageService } from '../services/storage/local';
import { LocalStorageKeys } from '../hooks/useLocalStorage';

declare global {
interface Window {
Expand Down Expand Up @@ -109,6 +111,18 @@ export interface FormErrorEvent {
| 'more_than_maximum_withdrawal';
}

export interface InitializationErrorEvent {
event: 'initialization_error';
error_message: InitializationErrorMessage;
}

type InitializationErrorMessage =
| 'node_connection_issue'
| 'signer_service_issue'
| 'moonbeam_account_issue'
| 'stellar_account_issue'
| 'pendulum_account_issue';

export type TrackableEvent =
| AmountTypeEvent
| ClickDetailsEvent
Expand All @@ -122,7 +136,8 @@ export type TrackableEvent =
| SigningRequestedEvent
| TransactionSignedEvent
| ProgressEvent
| NetworkChangeEvent;
| NetworkChangeEvent
| InitializationErrorEvent;

type EventType = TrackableEvent['event'];

Expand Down Expand Up @@ -154,6 +169,19 @@ const useEvents = () => {
}
}

if (event.event === 'initialization_error') {
const eventsStored = storageService.getParsed<Set<InitializationErrorMessage>>(
LocalStorageKeys.FIRED_INITIALIZATION_EVENTS,
);
const eventsSet = eventsStored ? new Set(eventsStored) : new Set();
if (eventsSet.has(event.error_message)) {
return;
} else {
eventsSet.add(event.error_message);
storageService.set(LocalStorageKeys.FIRED_INITIALIZATION_EVENTS, Array.from(eventsSet));
}
}

// Check if form error message has already been fired as we only want to fire each error message once
if (event.event === 'form_error') {
const { error_message } = event;
Expand Down Expand Up @@ -301,3 +329,7 @@ export function createTransactionEvent(
to_amount: calculateTotalReceive(Big(state.outputAmount.units), OUTPUT_TOKEN_CONFIG[state.outputTokenType]),
};
}

export function clearPersistentErrorEventStore() {
storageService.remove(LocalStorageKeys.FIRED_INITIALIZATION_EVENTS);
}
1 change: 0 additions & 1 deletion src/hooks/offramp/useOfframpAdvancement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ export const useOfframpAdvancement = () => {
if (isProcessingAdvance.current) return;
isProcessingAdvance.current = true;
if (!pendulumNode || !assetHubNode) {
console.error('Polkadot nodes not initialized');
return;
}

Expand Down
1 change: 1 addition & 0 deletions src/hooks/useLocalStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,5 @@ export enum LocalStorageKeys {
SELECTED_NETWORK = 'SELECTED_NETWORK',
SELECTED_POLKADOT_WALLET = 'SELECTED_POLKADOT_WALLET',
SELECTED_POLKADOT_ACCOUNT = 'SELECTED_POLKADOT_ACCOUNT',
FIRED_INITIALIZATION_EVENTS = 'FIRED_INITIALIZATION_EVENTS',
}
59 changes: 40 additions & 19 deletions src/pages/swap/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
} from '../../constants/tokenConfig';
import { config } from '../../config';

import { useEventsContext } from '../../contexts/events';
import { useEventsContext, clearPersistentErrorEventStore } from '../../contexts/events';
import { useNetwork } from '../../contexts/network';
import { usePendulumNode } from '../../contexts/polkadotNode';

Expand All @@ -40,8 +40,6 @@ import { useTokenOutAmount } from '../../hooks/nabla/useTokenAmountOut';
import { useMainProcess } from '../../hooks/offramp/useMainProcess';
import { useSwapUrlParams } from './useSwapUrlParams';

import { initialChecks } from '../../services/initialChecks';

import { BaseLayout } from '../../layouts';
import { ProgressPage } from '../progress';
import { FailurePage } from '../failure';
Expand All @@ -59,6 +57,12 @@ import { swapConfirm } from './helpers/swapConfirm';
import { TrustedBy } from '../../components/TrustedBy';
import { WhyVortex } from '../../components/WhyVortex';
import { usePolkadotWalletState } from '../../contexts/polkadotWallet';
import {
MoonbeamFundingAccountError,
PendulumFundingAccountError,
StellarFundingAccountError,
useSigningService,
} from '../../services/signingService';
import { OfframpSummaryDialog } from '../../components/OfframpSummaryDialog';

import satoshipayLogo from '../../assets/logo/satoshipay.svg';
Expand All @@ -71,7 +75,7 @@ export const SwapPage = () => {
const { isDisconnected, address } = useVortexAccount();
const [initializeFailedMessage, setInitializeFailedMessage] = useState<string | null>(null);
const [apiInitializeFailed, setApiInitializeFailed] = useState(false);
const [_, setIsReady] = useState(false);
const [isReady, setIsReady] = useState(false);
const [showCompareFees, setShowCompareFees] = useState(false);
const [isOfframpSummaryDialogVisible, setIsOfframpSummaryDialogVisible] = useState(false);
const [cachedAnchorUrl, setCachedAnchorUrl] = useState<string | undefined>(undefined);
Expand All @@ -81,16 +85,46 @@ export const SwapPage = () => {
const { walletAccount } = usePolkadotWalletState();

const [termsAnimationKey, setTermsAnimationKey] = useState(0);
const {
error: signingServiceError,
isLoading: isSigningServiceLoading,
isError: isSigningServiceError,
} = useSigningService();

const { setTermsAccepted, toggleTermsChecked, termsChecked, termsAccepted, termsError, setTermsError } =
useTermsAndConditions();

useEffect(() => {
setApiInitializeFailed(!pendulumNode.apiComponents?.api && pendulumNode?.isFetched);
if (!pendulumNode.apiComponents?.api && pendulumNode?.isFetched) {
setApiInitializeFailed(true);
trackEvent({ event: 'initialization_error', error_message: 'node_connection_issue' });
}
if (pendulumNode.apiComponents?.api) {
setApi(pendulumNode.apiComponents.api);
}
}, [pendulumNode]);
}, [pendulumNode, trackEvent, setApiInitializeFailed]);

useEffect(() => {
if (isSigningServiceError && !isSigningServiceLoading) {
if (signingServiceError instanceof StellarFundingAccountError) {
trackEvent({ event: 'initialization_error', error_message: 'stellar_account_issue' });
} else if (signingServiceError instanceof PendulumFundingAccountError) {
trackEvent({ event: 'initialization_error', error_message: 'pendulum_account_issue' });
} else if (signingServiceError instanceof MoonbeamFundingAccountError) {
trackEvent({ event: 'initialization_error', error_message: 'moonbeam_account_issue' });
} else {
trackEvent({ event: 'initialization_error', error_message: 'signer_service_issue' });
}
setInitializeFailed();
}
}, [isSigningServiceLoading, isSigningServiceError, signingServiceError, trackEvent]);

useEffect(() => {
if (api && !isSigningServiceError && !isSigningServiceLoading) {
setIsReady(true);
clearPersistentErrorEventStore();
}
}, [api, isSigningServiceError, isSigningServiceLoading]);

// Maybe go into a state of UI errors??
const setInitializeFailed = useCallback((message?: string | null) => {
Expand All @@ -100,19 +134,6 @@ export const SwapPage = () => {
);
}, []);

useEffect(() => {
const initialize = async () => {
try {
await initialChecks();
setIsReady(true);
} catch (error) {
setInitializeFailed();
}
};

initialize();
}, [setInitializeFailed]);

// Main process hook
const {
handleOnSubmit,
Expand Down
83 changes: 71 additions & 12 deletions src/services/signingService.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useQuery } from '@tanstack/react-query';
import { SIGNING_SERVICE_URL } from '../constants/constants';
import { OutputTokenType } from '../constants/tokenConfig';

Expand Down Expand Up @@ -26,28 +27,86 @@ export interface SignerServiceSep10Request {
usesMemo?: boolean;
}

// @todo: implement @tanstack/react-query
// Generic error for signing service
export class SigningServiceError extends Error {
constructor(message: string) {
super(message);
this.name = 'SigningServiceError';
}
}

// Specific errors for each funding account
export class StellarFundingAccountError extends SigningServiceError {
constructor() {
super('Stellar account is inactive');
this.name = 'StellarFundingAccountError';
}
}

export class PendulumFundingAccountError extends SigningServiceError {
constructor() {
super('Pendulum account is inactive');
this.name = 'PendulumFundingAccountError';
}
}

export class MoonbeamFundingAccountError extends SigningServiceError {
constructor() {
super('Moonbeam account is inactive');
this.name = 'MoonbeamFundingAccountError';
}
}

export const fetchSigningServiceAccountId = async (): Promise<SigningServiceStatus> => {
try {
const serviceResponse: SigningServiceStatus = await (await fetch(`${SIGNING_SERVICE_URL}/v1/status`)).json();
const allServicesActive = Object.values(serviceResponse).every((service: AccountStatusResponse) => service.status);
const response = await fetch(`${SIGNING_SERVICE_URL}/v1/status`);
if (!response.ok) {
throw new SigningServiceError('Failed to fetch signing service status');
}

if (allServicesActive) {
return {
stellar: serviceResponse.stellar,
pendulum: serviceResponse.pendulum,
moonbeam: serviceResponse.moonbeam,
};
const serviceResponse: SigningServiceStatus = await response.json();

if (!serviceResponse.stellar?.status) {
throw new StellarFundingAccountError();
}
if (!serviceResponse.pendulum?.status) {
throw new PendulumFundingAccountError();
}
if (!serviceResponse.moonbeam?.status) {
throw new MoonbeamFundingAccountError();
}

// we really want to throw for both cases: accounts not funded, or service down.
throw new Error('One or more funding accounts are inactive');
return {
stellar: serviceResponse.stellar,
pendulum: serviceResponse.pendulum,
moonbeam: serviceResponse.moonbeam,
};
} catch (error) {
if (error instanceof SigningServiceError) {
throw error;
}
console.error('Signing service is down: ', error);
throw new Error('Signing service is down');
throw new SigningServiceError('Signing service is down');
}
};

export const useSigningService = () => {
return useQuery({
queryKey: ['signingService'],
queryFn: fetchSigningServiceAccountId,
retry: (failureCount, error) => {
if (
error instanceof StellarFundingAccountError ||
error instanceof PendulumFundingAccountError ||
error instanceof MoonbeamFundingAccountError
) {
return false;
}
return failureCount < 3;
},
});
};

export const fetchSep10Signatures = async ({
challengeXDR,
outToken,
Expand Down

0 comments on commit 8d72665

Please sign in to comment.