Skip to content

Commit

Permalink
Merge pull request #324 from pendulum-chain/refactor/useMainProcess
Browse files Browse the repository at this point in the history
Refactor useMainProcess
  • Loading branch information
Sharqiewicz authored Dec 31, 2024
2 parents a78f472 + 81dca9f commit d1f215b
Show file tree
Hide file tree
Showing 14 changed files with 373 additions and 287 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@
"viem": "^2.21.43",
"wagmi": "^2.12.29",
"web3": "^4.10.0",
"yup": "^1.4.0"
"yup": "^1.4.0",
"zustand": "^5.0.2"
},
"devDependencies": {
"@babel/core": "^7.20.12",
Expand Down
234 changes: 43 additions & 191 deletions src/hooks/offramp/useMainProcess.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,15 @@
import { useState, useEffect, useCallback, StateUpdater, useRef } from 'preact/compat';
import { useConfig } from 'wagmi';
import { useEffect, StateUpdater } from 'preact/compat';
import Big from 'big.js';

import { EventStatus, GenericEvent } from '../../components/GenericEvent';

import { getInputTokenDetailsOrDefault, InputTokenType, OutputTokenType } from '../../constants/tokenConfig';
import { OFFRAMPING_PHASE_SECONDS } from '../../pages/progress';

import { createTransactionEvent, useEventsContext } from '../../contexts/events';
import { useAssetHubNode, usePendulumNode } from '../../contexts/polkadotNode';
import { usePolkadotWalletState } from '../../contexts/polkadotWallet';
import { Networks, useNetwork } from '../../contexts/network';

import {
clearOfframpingState,
recoverFromFailure,
readCurrentState,
advanceOfframpingState,
OfframpingState,
} from '../../services/offrampingFlow';
import { InputTokenType, OutputTokenType } from '../../constants/tokenConfig';
import { useNetwork } from '../../contexts/network';
import { recoverFromFailure, readCurrentState } from '../../services/offrampingFlow';

import { useSEP24 } from './useSEP24';
import { useSubmitOfframp } from './useSubmitOfframp';
import { useOfframpEvents } from './useOfframpEvents';
import { useOfframpAdvancement } from './useOfframpAdvancement';
import { useOfframpActions, useOfframpState } from '../../stores/offrampStore';

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

Expand All @@ -34,187 +22,51 @@ export interface ExecutionInput {
}

export const useMainProcess = () => {
const [offrampingStarted, setOfframpingStarted] = useState<boolean>(false);
const [isInitiating, setIsInitiating] = useState<boolean>(false);
const [offrampingState, setOfframpingState] = useState<OfframpingState | undefined>(undefined);
const [signingPhase, setSigningPhase] = useState<SigningPhase | undefined>(undefined);
const isProcessingAdvance = useRef(false);

const { selectedNetwork, setOnSelectedNetworkChange } = useNetwork();
const { walletAccount } = usePolkadotWalletState();

const { apiComponents: pendulumNode } = usePendulumNode();
const { apiComponents: assetHubNode } = useAssetHubNode();

const wagmiConfig = useConfig();
const { trackEvent, resetUniqueEvents } = useEventsContext();

const [, setEvents] = useState<GenericEvent[]>([]);
const {
firstSep24IntervalRef,
firstSep24Response,
setFirstSep24Response,
setExecutionInput,
setAnchorSessionParams,
cleanSep24FirstVariables,
handleOnAnchorWindowOpen: sep24HandleOnAnchorWindowOpen,
} = useSEP24();
const { updateOfframpHookStateFromState, resetOfframpState, setOfframpStarted } = useOfframpActions();

const handleOnSubmit = useSubmitOfframp({
firstSep24IntervalRef,
setFirstSep24Response,
setExecutionInput,
setAnchorSessionParams,
cleanSep24FirstVariables,
offrampingStarted,
offrampingState,
setOfframpingStarted,
setIsInitiating,
});
const offrampState = useOfframpState();

const updateHookStateFromState = useCallback(
(state: OfframpingState | undefined) => {
if (state === undefined || state.phase === 'success' || state.failure !== undefined) {
setSigningPhase(undefined);
}

setOfframpingState(state);
// Contexts
const { setOnSelectedNetworkChange } = useNetwork();

if (state?.phase === 'success') {
trackEvent(createTransactionEvent('transaction_success', state, selectedNetwork));
} else if (state?.failure !== undefined) {
const currentPhase = state?.phase;
const currentPhaseIndex = Object.keys(OFFRAMPING_PHASE_SECONDS).indexOf(currentPhase);

trackEvent({
...createTransactionEvent('transaction_failure', state, selectedNetwork),
event: 'transaction_failure',
phase_name: currentPhase,
phase_index: currentPhaseIndex,
from_asset: getInputTokenDetailsOrDefault(selectedNetwork, state.inputTokenType).assetSymbol,
error_message: state.failure.message,
});
}
},
[trackEvent, selectedNetwork],
);
// Custom hooks
const events = useOfframpEvents();
const sep24 = useSEP24();

// Initialize state from storage
useEffect(() => {
const state = readCurrentState();
updateHookStateFromState(state);
}, [updateHookStateFromState]);

const addEvent = (message: string, status: EventStatus) => {
console.log('Add event', message, status);
setEvents((prevEvents) => [...prevEvents, { value: message, status }]);
};

const resetOfframpingState = useCallback(() => {
setOfframpingState(undefined);
setOfframpingStarted(false);
setIsInitiating(false);
setAnchorSessionParams(undefined);
setFirstSep24Response(undefined);
setExecutionInput(undefined);
cleanSep24FirstVariables();
clearOfframpingState();
setSigningPhase(undefined);
}, [
setOfframpingState,
setOfframpingStarted,
setIsInitiating,
setAnchorSessionParams,
setFirstSep24Response,
setExecutionInput,
cleanSep24FirstVariables,
setSigningPhase,
]);

const handleOnAnchorWindowOpen = useCallback(async () => {
if (!pendulumNode) {
console.error('Pendulum node not initialized');
return;
}

await sep24HandleOnAnchorWindowOpen(selectedNetwork, setOfframpingStarted, updateHookStateFromState, pendulumNode);
}, [selectedNetwork, setOfframpingStarted, updateHookStateFromState, pendulumNode, sep24HandleOnAnchorWindowOpen]);

const finishOfframping = useCallback(() => {
(async () => {
clearOfframpingState();
resetUniqueEvents();
setOfframpingStarted(false);
updateHookStateFromState(undefined);
})();
}, [updateHookStateFromState, resetUniqueEvents]);

const continueFailedFlow = useCallback(() => {
const nextState = recoverFromFailure(offrampingState);
updateHookStateFromState(nextState);
}, [updateHookStateFromState, offrampingState]);
const recoveryState = readCurrentState();
updateOfframpHookStateFromState(recoveryState);
events.trackOfframpingEvent(recoveryState);
}, [updateOfframpHookStateFromState, events]);

// Reset offramping state when the network is changed
useEffect(() => {
if (selectedNetwork == Networks.Polygon && wagmiConfig.state.status !== 'connected') return;
if (selectedNetwork == Networks.AssetHub && !walletAccount?.address) return;

(async () => {
try {
if (isProcessingAdvance.current) return;
isProcessingAdvance.current = true;

if (!pendulumNode || !assetHubNode) {
console.error('Polkadot nodes not initialized');
return;
}
setOnSelectedNetworkChange(resetOfframpState);
}, [setOnSelectedNetworkChange, resetOfframpState]);

const nextState = await advanceOfframpingState(offrampingState, {
renderEvent: addEvent,
wagmiConfig,
setSigningPhase,
trackEvent,
pendulumNode,
assetHubNode,
walletAccount,
});

if (JSON.stringify(offrampingState) !== JSON.stringify(nextState)) {
updateHookStateFromState(nextState);
}
} catch (error) {
console.error('Error advancing offramping state:', error);
} finally {
isProcessingAdvance.current = false;
}
})();
}, [
offrampingState,
trackEvent,
updateHookStateFromState,
wagmiConfig,
pendulumNode,
assetHubNode,
wagmiConfig.state.status,
walletAccount?.address,
]);

const maybeCancelSep24First = useCallback(() => {
if (firstSep24IntervalRef.current !== undefined) {
setOfframpingStarted(false);
cleanSep24FirstVariables();
}
}, [firstSep24IntervalRef, cleanSep24FirstVariables]);
// Determines the current offramping phase
useOfframpAdvancement();

return {
handleOnSubmit,
firstSep24ResponseState: firstSep24Response,
offrampingState,
offrampingStarted,
isInitiating,
setIsInitiating,
finishOfframping,
continueFailedFlow,
handleOnAnchorWindowOpen,
signingPhase,
maybeCancelSep24First,
handleOnSubmit: useSubmitOfframp({
...sep24,
}),
firstSep24ResponseState: sep24.firstSep24Response,
finishOfframping: () => {
events.resetUniqueEvents();
resetOfframpState();
},
continueFailedFlow: () => {
updateOfframpHookStateFromState(recoverFromFailure(offrampState));
},
handleOnAnchorWindowOpen: sep24.handleOnAnchorWindowOpen,
// @todo: why do we need this?
maybeCancelSep24First: () => {
if (sep24.firstSep24IntervalRef.current !== undefined) {
setOfframpStarted(false);
sep24.cleanSep24FirstVariables();
}
},
};
};
57 changes: 57 additions & 0 deletions src/hooks/offramp/useOfframpAdvancement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useEffect } from 'preact/hooks';
import { useConfig } from 'wagmi';

import { advanceOfframpingState } from '../../services/offrampingFlow';

import { usePolkadotWalletState } from '../../contexts/polkadotWallet';
import { useAssetHubNode } from '../../contexts/polkadotNode';
import { usePendulumNode } from '../../contexts/polkadotNode';
import { useEventsContext } from '../../contexts/events';

import { useOfframpActions, useOfframpState } from '../../stores/offrampStore';
import { EventStatus } from '../../components/GenericEvent';

export const useOfframpAdvancement = () => {
const { walletAccount } = usePolkadotWalletState();
const { trackEvent } = useEventsContext();
const wagmiConfig = useConfig();

const { apiComponents: pendulumNode } = usePendulumNode();
const { apiComponents: assetHubNode } = useAssetHubNode();

const offrampState = useOfframpState();
const { updateOfframpHookStateFromState, setOfframpSigningPhase } = useOfframpActions();

useEffect(() => {
if (wagmiConfig.state.status !== 'connected') return;

(async () => {
if (!pendulumNode || !assetHubNode) {
console.error('Polkadot nodes not initialized');
return;
}

const nextState = await advanceOfframpingState(offrampState, {
wagmiConfig,
setOfframpSigningPhase,
trackEvent,
pendulumNode,
assetHubNode,
walletAccount,
renderEvent: (message: string, status: EventStatus) => {
console.log('renderEvent: ', message, status);
},
});

if (JSON.stringify(offrampState) !== JSON.stringify(nextState)) {
updateOfframpHookStateFromState(nextState);
}
})();

// @todo: investigate and remove this
// This effect has dependencies that are used inside the async function (assetHubNode, pendulumNode, walletAccount)
// but we intentionally exclude them from the dependency array to prevent unnecessary re-renders.
// These dependencies are stable and won't change during the lifecycle of this hook.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [offrampState, trackEvent, updateOfframpHookStateFromState, wagmiConfig]);
};
35 changes: 35 additions & 0 deletions src/hooks/offramp/useOfframpEvents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useCallback } from 'preact/compat';
import { createTransactionEvent } from '../../contexts/events';
import { useEventsContext } from '../../contexts/events';
import { useNetwork } from '../../contexts/network';

import { getInputTokenDetailsOrDefault } from '../../constants/tokenConfig';
import { OfframpingState } from '../../services/offrampingFlow';
import { OFFRAMPING_PHASE_SECONDS } from '../../pages/progress';

export const useOfframpEvents = () => {
const { trackEvent, resetUniqueEvents } = useEventsContext();
const { selectedNetwork } = useNetwork();

const trackOfframpingEvent = useCallback(
(state: OfframpingState | undefined) => {
if (!state) return;

if (state.phase === 'success') {
trackEvent(createTransactionEvent('transaction_success', state, selectedNetwork));
} else if (state.failure) {
trackEvent({
...createTransactionEvent('transaction_failure', state, selectedNetwork),
event: 'transaction_failure',
phase_name: state.phase,
phase_index: Object.keys(OFFRAMPING_PHASE_SECONDS).indexOf(state.phase),
from_asset: getInputTokenDetailsOrDefault(selectedNetwork, state.inputTokenType).assetSymbol,
error_message: state.failure.message,
});
}
},
[trackEvent, selectedNetwork],
);

return { trackOfframpingEvent, trackEvent, resetUniqueEvents };
};
Loading

0 comments on commit d1f215b

Please sign in to comment.