= ({ text, disabled, pe
);
}
- if (selectedNetwork === Networks.Polygon && !isConnected) {
+ if (isNetworkEVM(selectedNetwork) && !isConnected) {
return (
diff --git a/src/constants/tokenConfig.ts b/src/constants/tokenConfig.ts
index f3850b91..9773f3f4 100644
--- a/src/constants/tokenConfig.ts
+++ b/src/constants/tokenConfig.ts
@@ -1,5 +1,5 @@
import { AssetIconType } from '../hooks/useGetAssetIcon';
-import { Networks } from '../contexts/network';
+import { Networks } from '../helpers/networks';
export interface BaseInputTokenDetails {
assetSymbol: string;
@@ -7,6 +7,7 @@ export interface BaseInputTokenDetails {
pendulumErc20WrapperAddress: string;
pendulumCurrencyId: { XCM: number };
pendulumAssetSymbol: string;
+ pendulumDecimals: number;
networkAssetIcon: AssetIconType;
network: Networks;
}
@@ -68,6 +69,7 @@ const PENDULUM_USDC_AXL = {
pendulumErc20WrapperAddress: '6eMCHeByJ3m2yPsXFkezBfCQtMs3ymUPqtAyCA41mNWmbNJe',
pendulumCurrencyId: { XCM: 12 },
pendulumAssetSymbol: 'USDC.axl',
+ pendulumDecimals: 6,
};
const PENDULUM_USDC_ASSETHUB = {
@@ -75,6 +77,7 @@ const PENDULUM_USDC_ASSETHUB = {
pendulumCurrencyId: { XCM: 2 },
foreignAssetId: 1337, // USDC on AssetHub
pendulumAssetSymbol: 'USDC',
+ pendulumDecimals: 6,
};
export const INPUT_TOKEN_CONFIG: Record>> = {
@@ -107,6 +110,111 @@ export const INPUT_TOKEN_CONFIG: Record[];
+ dataLayer: TrackableEvent[];
}
}
diff --git a/src/contexts/network.tsx b/src/contexts/network.tsx
index f6b15b08..bab19759 100644
--- a/src/contexts/network.tsx
+++ b/src/contexts/network.tsx
@@ -1,20 +1,15 @@
import { createContext } from 'preact';
import { useContext, useState, useEffect, useCallback } from 'preact/hooks';
import { useSwitchChain } from 'wagmi';
-
import { useLocalStorage, LocalStorageKeys } from '../hooks/useLocalStorage';
import { WALLETCONNECT_ASSETHUB_ID } from '../constants/constants';
-
-export enum Networks {
- AssetHub = 'AssetHub',
- Polygon = 'Polygon',
-}
+import { useOfframpActions } from '../stores/offrampStore';
+import { getNetworkId, isNetworkEVM, Networks } from '../helpers/networks';
interface NetworkContextType {
walletConnectPolkadotSelectedNetworkId: string;
selectedNetwork: Networks;
setSelectedNetwork: (network: Networks) => void;
- setOnSelectedNetworkChange: (callback: (network: Networks) => void) => void;
networkSelectorDisabled: boolean;
setNetworkSelectorDisabled: (disabled: boolean) => void;
}
@@ -23,7 +18,6 @@ const NetworkContext = createContext({
walletConnectPolkadotSelectedNetworkId: WALLETCONNECT_ASSETHUB_ID,
selectedNetwork: Networks.AssetHub,
setSelectedNetwork: () => null,
- setOnSelectedNetworkChange: () => null,
networkSelectorDisabled: false,
setNetworkSelectorDisabled: () => null,
});
@@ -39,25 +33,26 @@ export const NetworkProvider = ({ children }: NetworkProviderProps) => {
});
const [selectedNetwork, setSelectedNetworkState] = useState(selectedNetworkLocalStorage);
- const [onNetworkChange, setOnSelectedNetworkChange] = useState<((network: Networks) => void) | undefined>();
const [networkSelectorDisabled, setNetworkSelectorDisabled] = useState(false);
- const { chains, switchChain } = useSwitchChain();
+
+ const { resetOfframpState } = useOfframpActions();
+ const { switchChain } = useSwitchChain();
const setSelectedNetwork = useCallback(
- (networkId: Networks) => {
- if (onNetworkChange) {
- onNetworkChange(networkId);
- }
- setSelectedNetworkState(networkId);
- setSelectedNetworkLocalStorage(networkId);
- const chain = chains.find((c) => c.id === Number(networkId));
- if (chain) {
- switchChain({ chainId: chain.id });
+ (network: Networks) => {
+ resetOfframpState();
+ setSelectedNetworkState(network);
+ setSelectedNetworkLocalStorage(network);
+
+ // Will only switch chain on the EVM conneted wallet case.
+ if (isNetworkEVM(network)) {
+ switchChain({ chainId: getNetworkId(network) });
}
},
- [switchChain, chains, setSelectedNetworkLocalStorage, onNetworkChange],
+ [switchChain, setSelectedNetworkLocalStorage, resetOfframpState],
);
+ // Only run on first render
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const networkParam = params.get('network')?.toLowerCase();
@@ -79,7 +74,6 @@ export const NetworkProvider = ({ children }: NetworkProviderProps) => {
setSelectedNetwork,
networkSelectorDisabled,
setNetworkSelectorDisabled,
- setOnSelectedNetworkChange,
}}
>
{children}
diff --git a/src/helpers/networks.ts b/src/helpers/networks.ts
new file mode 100644
index 00000000..57b32833
--- /dev/null
+++ b/src/helpers/networks.ts
@@ -0,0 +1,80 @@
+import { polygon, bsc, arbitrum, base, avalanche, mainnet as ethereum } from '@reown/appkit/networks';
+
+// For the AssetHub network, we use a chain ID of -1. This is not a valid chain ID
+// but we just use it to differentiate between the EVM and Polkadot accounts.
+export const ASSETHUB_CHAIN_ID = -1;
+
+export enum Networks {
+ AssetHub = 'AssetHub',
+ Arbitrum = 'Arbitrum',
+ Avalanche = 'Avalanche',
+ Base = 'Base',
+ BSC = 'BSC',
+ Ethereum = 'Ethereum',
+ Polygon = 'Polygon',
+}
+
+type EVMNetworks = Exclude;
+
+interface NetworkMetadata {
+ id: number;
+ displayName: string;
+ isEVM: boolean;
+}
+
+const NETWORK_METADATA: Record = {
+ [Networks.AssetHub]: {
+ id: ASSETHUB_CHAIN_ID,
+ displayName: 'Polkadot AssetHub',
+ isEVM: false,
+ },
+ [Networks.Polygon]: {
+ id: polygon.id,
+ displayName: 'Polygon',
+ isEVM: true,
+ },
+ [Networks.Ethereum]: {
+ id: ethereum.id,
+ displayName: 'Ethereum',
+ isEVM: true,
+ },
+ [Networks.BSC]: {
+ id: bsc.id,
+ displayName: 'BNB Smart Chain',
+ isEVM: true,
+ },
+ [Networks.Arbitrum]: {
+ id: arbitrum.id,
+ displayName: 'Arbitrum One',
+ isEVM: true,
+ },
+ [Networks.Base]: {
+ id: base.id,
+ displayName: 'Base',
+ isEVM: true,
+ },
+ [Networks.Avalanche]: {
+ id: avalanche.id,
+ displayName: 'Avalanche',
+ isEVM: true,
+ },
+};
+
+export function isNetworkEVM(network: Networks): network is EVMNetworks {
+ return NETWORK_METADATA[network].isEVM;
+}
+
+export function getNetworkId(network: Networks): number {
+ return NETWORK_METADATA[network].id;
+}
+
+export function getNetworkDisplayName(network: Networks): string {
+ return NETWORK_METADATA[network].displayName;
+}
+
+const DEFAULT_NETWORK = Networks.AssetHub;
+
+export function getCaseSensitiveNetwork(network: string): Networks {
+ const normalized = network.toLowerCase();
+ return Object.values(Networks).find((n) => n.toLowerCase() === normalized) ?? DEFAULT_NETWORK;
+}
diff --git a/src/hooks/nabla/useTokenAmountOut.ts b/src/hooks/nabla/useTokenAmountOut.ts
index 9f7a966f..915dd18f 100644
--- a/src/hooks/nabla/useTokenAmountOut.ts
+++ b/src/hooks/nabla/useTokenAmountOut.ts
@@ -20,9 +20,9 @@ import {
OUTPUT_TOKEN_CONFIG,
OutputTokenType,
} from '../../constants/tokenConfig';
+import { Networks } from '../../helpers/networks';
import { SwapFormValues } from '../../components/Nabla/schema';
import { useEventsContext } from '../../contexts/events';
-import { Networks } from '../../contexts/network';
type UseTokenOutAmountProps = {
wantsSwap: boolean;
@@ -74,7 +74,7 @@ export function useTokenOutAmount({
const inputToken = getInputTokenDetailsOrDefault(network, inputTokenType);
const outputToken = OUTPUT_TOKEN_CONFIG[outputTokenType];
- const fromTokenDecimals = inputToken?.decimals;
+ const fromTokenDecimals = inputToken?.pendulumDecimals;
const amountIn =
fromTokenDecimals !== undefined && debouncedAmountBigDecimal !== undefined
diff --git a/src/hooks/offramp/useMainProcess.ts b/src/hooks/offramp/useMainProcess.ts
index 15c8cb2e..400bdfa9 100644
--- a/src/hooks/offramp/useMainProcess.ts
+++ b/src/hooks/offramp/useMainProcess.ts
@@ -1,16 +1,17 @@
-import { useEffect, StateUpdater } from 'preact/compat';
+import { useEffect } from 'preact/compat';
import Big from 'big.js';
-import { InputTokenType, OutputTokenType } from '../../constants/tokenConfig';
-import { useNetwork } from '../../contexts/network';
import { recoverFromFailure, readCurrentState } from '../../services/offrampingFlow';
-import { useSEP24 } from './useSEP24';
+import { InputTokenType, OutputTokenType } from '../../constants/tokenConfig';
+
import { useSubmitOfframp } from './useSubmitOfframp';
import { useOfframpEvents } from './useOfframpEvents';
import { useOfframpAdvancement } from './useOfframpAdvancement';
import { useOfframpActions, useOfframpState } from '../../stores/offrampStore';
-
+import { useSep24UrlInterval, useSep24InitialResponse } from '../../stores/sep24Store';
+import { useSep24Actions } from '../../stores/sep24Store';
+import { useAnchorWindowHandler } from './useSEP24/useAnchorWindowHandler';
export type SigningPhase = 'started' | 'approved' | 'signed' | 'finished';
export interface ExecutionInput {
@@ -18,41 +19,38 @@ export interface ExecutionInput {
outputTokenType: OutputTokenType;
amountInUnits: string;
offrampAmount: Big;
- setInitializeFailed: StateUpdater;
+ setInitializeFailed: (message?: string | null) => void;
}
export const useMainProcess = () => {
const { updateOfframpHookStateFromState, resetOfframpState, setOfframpStarted } = useOfframpActions();
-
const offrampState = useOfframpState();
- // Contexts
- const { setOnSelectedNetworkChange } = useNetwork();
+ // Sep 24 states
+ const firstSep24Response = useSep24InitialResponse();
+ const firstSep24Interval = useSep24UrlInterval();
+
+ const { cleanup: cleanupSep24 } = useSep24Actions();
// Custom hooks
const events = useOfframpEvents();
- const sep24 = useSEP24();
+ const handleOnAnchorWindowOpen = useAnchorWindowHandler();
// Initialize state from storage
useEffect(() => {
const recoveryState = readCurrentState();
updateOfframpHookStateFromState(recoveryState);
events.trackOfframpingEvent(recoveryState);
- }, [updateOfframpHookStateFromState, events]);
-
- // Reset offramping state when the network is changed
- useEffect(() => {
- setOnSelectedNetworkChange(resetOfframpState);
- }, [setOnSelectedNetworkChange, resetOfframpState]);
+ // Previously, adding events to the array was causeing a re-rendering loop.
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [updateOfframpHookStateFromState, events.trackOfframpingEvent]);
// Determines the current offramping phase
useOfframpAdvancement();
return {
- handleOnSubmit: useSubmitOfframp({
- ...sep24,
- }),
- firstSep24ResponseState: sep24.firstSep24Response,
+ handleOnSubmit: useSubmitOfframp(),
+ firstSep24ResponseState: firstSep24Response,
finishOfframping: () => {
events.resetUniqueEvents();
resetOfframpState();
@@ -60,12 +58,11 @@ export const useMainProcess = () => {
continueFailedFlow: () => {
updateOfframpHookStateFromState(recoverFromFailure(offrampState));
},
- handleOnAnchorWindowOpen: sep24.handleOnAnchorWindowOpen,
- // @todo: why do we need this?
+ handleOnAnchorWindowOpen: handleOnAnchorWindowOpen,
maybeCancelSep24First: () => {
- if (sep24.firstSep24IntervalRef.current !== undefined) {
+ if (firstSep24Interval !== undefined) {
setOfframpStarted(false);
- sep24.cleanSep24FirstVariables();
+ cleanupSep24();
}
},
};
diff --git a/src/hooks/offramp/useOfframpAdvancement.ts b/src/hooks/offramp/useOfframpAdvancement.ts
index e30d0d0b..2e5d8cbb 100644
--- a/src/hooks/offramp/useOfframpAdvancement.ts
+++ b/src/hooks/offramp/useOfframpAdvancement.ts
@@ -1,4 +1,4 @@
-import { useEffect } from 'preact/hooks';
+import { useEffect, useRef } from 'preact/hooks';
import { useConfig } from 'wagmi';
import { advanceOfframpingState } from '../../services/offrampingFlow';
@@ -9,49 +9,65 @@ import { usePendulumNode } from '../../contexts/polkadotNode';
import { useEventsContext } from '../../contexts/events';
import { useOfframpActions, useOfframpState } from '../../stores/offrampStore';
-import { EventStatus } from '../../components/GenericEvent';
+import { useNetwork } from '../../contexts/network';
+import { isNetworkEVM } from '../../helpers/networks';
export const useOfframpAdvancement = () => {
const { walletAccount } = usePolkadotWalletState();
const { trackEvent } = useEventsContext();
const wagmiConfig = useConfig();
+ const { selectedNetwork } = useNetwork();
const { apiComponents: pendulumNode } = usePendulumNode();
const { apiComponents: assetHubNode } = useAssetHubNode();
const offrampState = useOfframpState();
const { updateOfframpHookStateFromState, setOfframpSigningPhase } = useOfframpActions();
+ const isProcessingAdvance = useRef(false);
+
useEffect(() => {
- if (wagmiConfig.state.status !== 'connected') return;
+ if (isNetworkEVM(selectedNetwork) && wagmiConfig.state.status !== 'connected') return;
+ if (!isNetworkEVM(selectedNetwork) && !walletAccount?.address) return;
(async () => {
- if (!pendulumNode || !assetHubNode) {
- console.error('Polkadot nodes not initialized');
- return;
- }
+ try {
+ if (isProcessingAdvance.current) return;
+ isProcessingAdvance.current = true;
+ 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);
+ const nextState = await advanceOfframpingState(offrampState, {
+ wagmiConfig,
+ setOfframpSigningPhase,
+ trackEvent,
+ pendulumNode,
+ assetHubNode,
+ walletAccount,
+ });
+
+ if (JSON.stringify(offrampState) !== JSON.stringify(nextState)) {
+ updateOfframpHookStateFromState(nextState);
+ }
+ } catch (error) {
+ console.error('Error advancing offramping state:', error);
+ } finally {
+ isProcessingAdvance.current = false;
}
})();
-
- // @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]);
+ }, [
+ offrampState,
+ trackEvent,
+ updateOfframpHookStateFromState,
+ wagmiConfig.state.status,
+ selectedNetwork,
+ pendulumNode,
+ assetHubNode,
+ walletAccount?.address,
+ wagmiConfig,
+ walletAccount,
+ setOfframpSigningPhase,
+ ]);
};
diff --git a/src/hooks/offramp/useSEP24/index.ts b/src/hooks/offramp/useSEP24/index.ts
deleted file mode 100644
index 6d5eeae5..00000000
--- a/src/hooks/offramp/useSEP24/index.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { StateUpdater } from 'preact/compat';
-import Big from 'big.js';
-
-import { InputTokenType, OutputTokenType } from '../../../constants/tokenConfig';
-
-import { useSEP24State } from './useSEP24State';
-import { useSEP24Cleanup } from './useSEP24Cleanup';
-import { useAnchorWindowHandler } from './useAnchorWindowHandler';
-
-export type SigningPhase = 'started' | 'approved' | 'signed' | 'finished';
-
-export interface ExecutionInput {
- inputTokenType: InputTokenType;
- outputTokenType: OutputTokenType;
- amountInUnits: string;
- offrampAmount: Big;
- setInitializeFailed: StateUpdater;
-}
-
-export const useSEP24 = () => {
- const sep24State = useSEP24State();
- const cleanSep24FirstVariables = useSEP24Cleanup(sep24State);
- const handleOnAnchorWindowOpen = useAnchorWindowHandler(sep24State, cleanSep24FirstVariables);
-
- return {
- ...sep24State,
- cleanSep24FirstVariables,
- handleOnAnchorWindowOpen,
- };
-};
diff --git a/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts b/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts
index 457e9d55..4949a669 100644
--- a/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts
+++ b/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts
@@ -4,14 +4,19 @@ import Big from 'big.js';
import { useNetwork } from '../../../contexts/network';
import { constructInitialState } from '../../../services/offrampingFlow';
-import { sep24Second } from '../../../services/anchor';
+import { sep24Second } from '../../../services/anchor/sep24/second';
import { showToast, ToastMessage } from '../../../helpers/notifications';
-import { UseSEP24StateReturn } from './useSEP24State';
import { useTrackSEP24Events } from './useTrackSEP24Events';
import { usePendulumNode } from '../../../contexts/polkadotNode';
import { useOfframpActions } from '../../../stores/offrampStore';
+import {
+ useSep24Actions,
+ useSep24InitialResponse,
+ useSep24AnchorSessionParams,
+ useSep24ExecutionInput,
+} from '../../../stores/sep24Store';
import { useVortexAccount } from '../../useVortexAccount';
const handleAmountMismatch = (setOfframpingStarted: (started: boolean) => void): void => {
@@ -24,15 +29,19 @@ const handleError = (error: unknown, setOfframpingStarted: (started: boolean) =>
setOfframpingStarted(false);
};
-export const useAnchorWindowHandler = (sep24State: UseSEP24StateReturn, cleanupFn: () => void) => {
+export const useAnchorWindowHandler = () => {
const { trackKYCStarted, trackKYCCompleted } = useTrackSEP24Events();
const { selectedNetwork } = useNetwork();
const { apiComponents: pendulumNode } = usePendulumNode();
const { setOfframpStarted, updateOfframpHookStateFromState } = useOfframpActions();
-
- const { firstSep24Response, anchorSessionParams, executionInput } = sep24State;
const { address } = useVortexAccount();
+ const firstSep24Response = useSep24InitialResponse();
+ const anchorSessionParams = useSep24AnchorSessionParams();
+
+ const executionInput = useSep24ExecutionInput();
+ const { cleanup: cleanupSep24State } = useSep24Actions();
+
return useCallback(async () => {
if (!firstSep24Response || !anchorSessionParams || !executionInput) {
return;
@@ -44,7 +53,7 @@ export const useAnchorWindowHandler = (sep24State: UseSEP24StateReturn, cleanupF
}
trackKYCStarted(executionInput, selectedNetwork);
- cleanupFn();
+ cleanupSep24State();
try {
const secondSep24Response = await sep24Second(firstSep24Response, anchorSessionParams);
@@ -78,11 +87,11 @@ export const useAnchorWindowHandler = (sep24State: UseSEP24StateReturn, cleanupF
executionInput,
pendulumNode,
trackKYCStarted,
- cleanupFn,
- trackKYCCompleted,
selectedNetwork,
- setOfframpStarted,
- updateOfframpHookStateFromState,
+ cleanupSep24State,
address,
+ trackKYCCompleted,
+ updateOfframpHookStateFromState,
+ setOfframpStarted,
]);
};
diff --git a/src/hooks/offramp/useSEP24/useSEP24Cleanup.ts b/src/hooks/offramp/useSEP24/useSEP24Cleanup.ts
deleted file mode 100644
index 4e3bd29b..00000000
--- a/src/hooks/offramp/useSEP24/useSEP24Cleanup.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { useCallback } from 'preact/compat';
-
-import { UseSEP24StateReturn } from './useSEP24State';
-
-export const useSEP24Cleanup = (sep24State: UseSEP24StateReturn) => {
- const { firstSep24IntervalRef, setFirstSep24Response, setExecutionInput, setAnchorSessionParams } = sep24State;
-
- return useCallback(() => {
- if (firstSep24IntervalRef.current !== undefined) {
- clearInterval(firstSep24IntervalRef.current);
- firstSep24IntervalRef.current = undefined;
- setFirstSep24Response(undefined);
- setExecutionInput(undefined);
- setAnchorSessionParams(undefined);
- }
- }, [firstSep24IntervalRef, setFirstSep24Response, setExecutionInput, setAnchorSessionParams]);
-};
diff --git a/src/hooks/offramp/useSEP24/useSEP24State.ts b/src/hooks/offramp/useSEP24/useSEP24State.ts
deleted file mode 100644
index 601d30f0..00000000
--- a/src/hooks/offramp/useSEP24/useSEP24State.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { useState, useRef, StateUpdater, MutableRefObject } from 'preact/compat';
-import Big from 'big.js';
-
-import { IAnchorSessionParams, ISep24Intermediate } from '../../../services/anchor';
-import { InputTokenType, OutputTokenType } from '../../../constants/tokenConfig';
-
-export interface ExecutionInput {
- inputTokenType: InputTokenType;
- outputTokenType: OutputTokenType;
- amountInUnits: string;
- offrampAmount: Big;
- setInitializeFailed: StateUpdater;
-}
-
-export type ExtendedExecutionInput = ExecutionInput & { stellarEphemeralSecret: string };
-
-export interface UseSEP24StateReturn {
- anchorSessionParams: IAnchorSessionParams | undefined;
- firstSep24Response: ISep24Intermediate | undefined;
- executionInput: ExtendedExecutionInput | undefined;
- setAnchorSessionParams: (params: IAnchorSessionParams | undefined) => void;
- setFirstSep24Response: (response: ISep24Intermediate | undefined) => void;
- setExecutionInput: (input: ExtendedExecutionInput | undefined) => void;
- firstSep24IntervalRef: MutableRefObject;
-}
-
-export const useSEP24State = (): UseSEP24StateReturn => {
- const [anchorSessionParams, setAnchorSessionParams] = useState();
- const [firstSep24Response, setFirstSep24Response] = useState();
- const [executionInput, setExecutionInput] = useState();
- const firstSep24IntervalRef = useRef();
-
- return {
- anchorSessionParams,
- setAnchorSessionParams,
-
- firstSep24Response,
- setFirstSep24Response,
-
- executionInput,
- setExecutionInput,
-
- firstSep24IntervalRef,
- };
-};
diff --git a/src/hooks/offramp/useSEP24/useTrackSEP24Events.ts b/src/hooks/offramp/useSEP24/useTrackSEP24Events.ts
index c7e65051..f6e35f1c 100644
--- a/src/hooks/offramp/useSEP24/useTrackSEP24Events.ts
+++ b/src/hooks/offramp/useSEP24/useTrackSEP24Events.ts
@@ -1,22 +1,10 @@
-import Big from 'big.js';
-
import { createTransactionEvent, useEventsContext } from '../../../contexts/events';
import { calculateTotalReceive } from '../../../components/FeeCollapse';
import { OfframpingState } from '../../../services/offrampingFlow';
-import { Networks } from '../../../contexts/network';
-import {
- getInputTokenDetailsOrDefault,
- OUTPUT_TOKEN_CONFIG,
- InputTokenType,
- OutputTokenType,
-} from '../../../constants/tokenConfig';
+import { Networks } from '../../../helpers/networks';
+import { getInputTokenDetailsOrDefault, OUTPUT_TOKEN_CONFIG } from '../../../constants/tokenConfig';
-interface ExecutionInput {
- inputTokenType: InputTokenType;
- outputTokenType: OutputTokenType;
- amountInUnits: string;
- offrampAmount: Big;
-}
+import { ExecutionInput } from '../useMainProcess';
export const useTrackSEP24Events = () => {
const { trackEvent } = useEventsContext();
diff --git a/src/hooks/offramp/useSubmitOfframp.ts b/src/hooks/offramp/useSubmitOfframp.ts
index f3b31f2a..a4ffc881 100644
--- a/src/hooks/offramp/useSubmitOfframp.ts
+++ b/src/hooks/offramp/useSubmitOfframp.ts
@@ -1,53 +1,40 @@
-import { MutableRefObject, useCallback } from 'preact/compat';
+import { useCallback } from 'preact/compat';
import { polygon } from 'wagmi/chains';
import { useSwitchChain } from 'wagmi';
+
+import { getNetworkId, isNetworkEVM } from '../../helpers/networks';
+import { useVortexAccount } from '../useVortexAccount';
import { useNetwork } from '../../contexts/network';
import { useEventsContext } from '../../contexts/events';
import { useSiweContext } from '../../contexts/siwe';
import { calculateTotalReceive } from '../../components/FeeCollapse';
import { getInputTokenDetailsOrDefault, OUTPUT_TOKEN_CONFIG } from '../../constants/tokenConfig';
-import {
- createStellarEphemeralSecret,
- fetchTomlValues,
- IAnchorSessionParams,
- ISep24Intermediate,
- sep10,
- sep24First,
-} from '../../services/anchor';
+import { createStellarEphemeralSecret, fetchTomlValues } from '../../services/stellar';
+
+import { sep24First } from '../../services/anchor/sep24/first';
+import { sep10 } from '../../services/anchor/sep10';
import { useOfframpActions, useOfframpStarted, useOfframpState } from '../../stores/offrampStore';
-import { ExtendedExecutionInput } from './useSEP24/useSEP24State';
-import { ExecutionInput } from './useSEP24';
-import { useVortexAccount } from '../useVortexAccount';
+import { ExecutionInput } from './useMainProcess';
+import { useSep24Actions } from '../../stores/sep24Store';
-interface UseSubmitOfframpProps {
- firstSep24IntervalRef: MutableRefObject;
- setFirstSep24Response: (response: ISep24Intermediate | undefined) => void;
- setExecutionInput: (input: ExtendedExecutionInput | undefined) => void;
- setAnchorSessionParams: (params: IAnchorSessionParams | undefined) => void;
- cleanSep24FirstVariables: () => void;
-}
-
-export const useSubmitOfframp = ({
- firstSep24IntervalRef,
- setFirstSep24Response,
- setExecutionInput,
- setAnchorSessionParams,
- cleanSep24FirstVariables,
-}: UseSubmitOfframpProps) => {
+export const useSubmitOfframp = () => {
const { selectedNetwork } = useNetwork();
- const { switchChain } = useSwitchChain();
+ const { switchChainAsync, switchChain } = useSwitchChain();
const { trackEvent } = useEventsContext();
const { address } = useVortexAccount();
const { checkAndWaitForSignature, forceRefreshAndWaitForSignature } = useSiweContext();
const offrampStarted = useOfframpStarted();
const offrampState = useOfframpState();
const { setOfframpStarted, setOfframpInitiating } = useOfframpActions();
-
- const addEvent = (message: string, status: string) => {
- console.log('Add event', message, status);
- };
+ const {
+ setAnchorSessionParams,
+ setExecutionInput,
+ setInitialResponse: setInitialResponseSEP24,
+ setUrlInterval: setUrlIntervalSEP24,
+ cleanup: cleanupSEP24,
+ } = useSep24Actions();
return useCallback(
(executionInput: ExecutionInput) => {
@@ -71,6 +58,21 @@ export const useSubmitOfframp = ({
});
try {
+ // For substrate, we only have AssetHub only now. Thus no need to change.
+ if (isNetworkEVM(selectedNetwork)) {
+ await switchChainAsync({ chainId: getNetworkId(selectedNetwork) });
+ }
+
+ setOfframpStarted(true);
+
+ trackEvent({
+ event: 'transaction_confirmation',
+ from_asset: getInputTokenDetailsOrDefault(selectedNetwork, inputTokenType).assetSymbol,
+ to_asset: OUTPUT_TOKEN_CONFIG[outputTokenType].stellarAsset.code.string,
+ from_amount: amountInUnits,
+ to_amount: calculateTotalReceive(offrampAmount, OUTPUT_TOKEN_CONFIG[outputTokenType]),
+ });
+
const stellarEphemeralSecret = createStellarEphemeralSecret();
const outputToken = OUTPUT_TOKEN_CONFIG[outputTokenType];
const tomlValues = await fetchTomlValues(outputToken.tomlFileUrl!);
@@ -86,7 +88,6 @@ export const useSubmitOfframp = ({
address,
checkAndWaitForSignature,
forceRefreshAndWaitForSignature,
- addEvent,
);
const anchorSessionParams = {
@@ -107,24 +108,29 @@ export const useSubmitOfframp = ({
const url = new URL(firstSep24Response.url);
url.searchParams.append('callback', 'postMessage');
firstSep24Response.url = url.toString();
- setFirstSep24Response(firstSep24Response);
+ setInitialResponseSEP24(firstSep24Response);
};
- firstSep24IntervalRef.current = window.setInterval(fetchAndUpdateSep24Url, 20000);
+ setUrlIntervalSEP24(window.setInterval(fetchAndUpdateSep24Url, 20000));
try {
await fetchAndUpdateSep24Url();
} catch (error) {
console.error('Error finalizing the initial state of the offramping process', error);
- setInitializeFailed(true);
+ setInitializeFailed();
setOfframpStarted(false);
- cleanSep24FirstVariables();
+ cleanupSEP24();
} finally {
setOfframpInitiating(false);
}
} catch (error) {
console.error('Error initializing the offramping process', error);
- setInitializeFailed(true);
+ // Display error message, differentiating between user rejection and other errors
+ if ((error as Error).message.includes('User rejected the request')) {
+ setInitializeFailed('Please switch to the correct network and try again.');
+ } else {
+ setInitializeFailed();
+ }
setOfframpStarted(false);
setOfframpInitiating(false);
}
@@ -143,9 +149,10 @@ export const useSubmitOfframp = ({
forceRefreshAndWaitForSignature,
setExecutionInput,
setAnchorSessionParams,
- firstSep24IntervalRef,
- setFirstSep24Response,
- cleanSep24FirstVariables,
+ setInitialResponseSEP24,
+ setUrlIntervalSEP24,
+ cleanupSEP24,
+ switchChainAsync,
],
);
};
diff --git a/src/hooks/useGetAssetIcon.tsx b/src/hooks/useGetAssetIcon.tsx
index c4ac6c5e..ca05243b 100644
--- a/src/hooks/useGetAssetIcon.tsx
+++ b/src/hooks/useGetAssetIcon.tsx
@@ -2,9 +2,23 @@ import EURC from '../assets/coins/EURC.png';
import EUR from '../assets/coins/EUR.svg';
import USDC from '../assets/coins/USDC.png';
import USDT from '../assets/coins/USDT.svg';
+
+import USDC_AVALANCHE from '../assets/coins/USDC_AVALANCHE.svg';
+import USDT_AVALANCHE from '../assets/coins/USDT_AVALANCHE.svg';
+import USDC_ARBITRUM from '../assets/coins/USDC_ARBITRUM.svg';
+import USDT_ARBITRUM from '../assets/coins/USDT_ARBITRUM.svg';
+import USDC_BASE from '../assets/coins/USDC_BASE.svg';
+import USDT_BASE from '../assets/coins/USDT_BASE.svg';
+import USDC_BSC from '../assets/coins/USDC_BSC.svg';
+import USDT_BSC from '../assets/coins/USDT_BSC.svg';
+import USDC_ETHEREUM from '../assets/coins/USDC_ETHEREUM.svg';
+import USDT_ETHEREUM from '../assets/coins/USDT_ETHEREUM.svg';
+
import USDC_POLYGON from '../assets/coins/USDC_POLYGON.svg';
import USDT_POLYGON from '../assets/coins/USDT_POLYGON.svg';
+
import USDC_ASSETHUB from '../assets/coins/USDC_ASSETHUB.svg';
+
import ARS from '../assets/coins/ARS.png';
const ICONS = {
@@ -12,6 +26,16 @@ const ICONS = {
eurc: EURC,
usdc: USDC,
usdt: USDT,
+ arbitrumUSDC: USDC_ARBITRUM,
+ arbitrumUSDT: USDT_ARBITRUM,
+ avalancheUSDC: USDC_AVALANCHE,
+ avalancheUSDT: USDT_AVALANCHE,
+ baseUSDC: USDC_BASE,
+ baseUSDT: USDT_BASE,
+ bscUSDC: USDC_BSC,
+ bscUSDT: USDT_BSC,
+ ethereumUSDC: USDC_ETHEREUM,
+ ethereumUSDT: USDT_ETHEREUM,
polygonUSDC: USDC_POLYGON,
polygonUSDT: USDT_POLYGON,
assethubUSDC: USDC_ASSETHUB,
diff --git a/src/hooks/useGetNetworkIcon.tsx b/src/hooks/useGetNetworkIcon.tsx
index e4eb9b3e..c9b0d6cb 100644
--- a/src/hooks/useGetNetworkIcon.tsx
+++ b/src/hooks/useGetNetworkIcon.tsx
@@ -1,14 +1,23 @@
import ASSET_HUB from '../assets/chains/assetHub.svg';
import POLYGON from '../assets/chains/polygon.svg';
-import { Networks } from '../contexts/network';
+import ETHEREUM from '../assets/chains/ethereum.svg';
+import BSC from '../assets/chains/bsc.svg';
+import ARBITRUM from '../assets/chains/arbitrum.svg';
+import BASE from '../assets/chains/base.svg';
+import AVALANCHE from '../assets/chains/avalanche.svg';
-export const NETWORK_ICONS = {
+import { Networks } from '../helpers/networks';
+
+export const NETWORK_ICONS: Record = {
[Networks.AssetHub]: ASSET_HUB,
[Networks.Polygon]: POLYGON,
+ [Networks.Ethereum]: ETHEREUM,
+ [Networks.BSC]: BSC,
+ [Networks.Arbitrum]: ARBITRUM,
+ [Networks.Base]: BASE,
+ [Networks.Avalanche]: AVALANCHE,
};
-export type NetworkIconType = keyof typeof NETWORK_ICONS;
-
-export function useGetNetworkIcon(networkIcon: NetworkIconType) {
- return NETWORK_ICONS[networkIcon];
+export function useGetNetworkIcon(network: Networks) {
+ return NETWORK_ICONS[network];
}
diff --git a/src/hooks/useInputTokenBalance.ts b/src/hooks/useInputTokenBalance.ts
index f1b7eb50..959d244f 100644
--- a/src/hooks/useInputTokenBalance.ts
+++ b/src/hooks/useInputTokenBalance.ts
@@ -9,21 +9,24 @@ import { nativeToDecimal, USDC_DECIMALS } from '../helpers/parseNumbers';
import { usePolkadotWalletState } from '../contexts/polkadotWallet';
import { useAssetHubNode } from '../contexts/polkadotNode';
import { useVortexAccount } from './useVortexAccount';
+import { useNetwork } from '../contexts/network';
+import { getNetworkId } from '../helpers/networks';
const useEvmBalance = (
tokenAddress: `0x${string}` | undefined,
fromToken: InputTokenDetails | undefined,
): string | undefined => {
const { address } = useVortexAccount();
+ const { selectedNetwork } = useNetwork();
const { data: balance } = useReadContract({
address: tokenAddress,
abi: erc20ABI,
functionName: 'balanceOf',
args: [address],
+ chainId: getNetworkId(selectedNetwork),
});
-
- if (!fromToken || !balance) return undefined;
+ if (!fromToken || (!balance && balance !== BigInt(0))) return undefined;
return multiplyByPowerOfTen(Big(balance.toString()), -fromToken.decimals).toFixed(2, 0);
};
@@ -36,7 +39,6 @@ const useAssetHubBalance = (assetId?: number): string | undefined => {
if (!walletAccount || !assetHubNode) return;
if (!assetId) {
- console.log('Invalid assetId', assetId);
setBalance('0');
return;
}
diff --git a/src/hooks/useVortexAccount.ts b/src/hooks/useVortexAccount.ts
index 7c9b3488..c2a04139 100644
--- a/src/hooks/useVortexAccount.ts
+++ b/src/hooks/useVortexAccount.ts
@@ -1,13 +1,10 @@
-import { Networks, useNetwork } from '../contexts/network';
+import { useNetwork } from '../contexts/network';
import { useMemo, useCallback } from 'preact/compat';
import { usePolkadotWalletState } from '../contexts/polkadotWallet';
import { useAccount } from 'wagmi';
import { Signer } from '@polkadot/types/types';
import { useSignMessage } from 'wagmi';
-
-// For the AssetHub network, we use a chain ID of -1. This is not a valid chain ID
-// but we just use it to differentiate between the EVM and Polkadot accounts.
-const AssetHubChainId = -1;
+import { isNetworkEVM, ASSETHUB_CHAIN_ID } from '../helpers/networks';
// A helper hook to provide an abstraction over the account used.
// The account could be an EVM account or a Polkadot account.
@@ -19,7 +16,7 @@ export const useVortexAccount = () => {
const { signMessageAsync } = useSignMessage();
const address = useMemo(() => {
- if (selectedNetwork === Networks.AssetHub) {
+ if (!isNetworkEVM(selectedNetwork)) {
return polkadotWalletAccount?.address;
} else {
return evmAccountAddress;
@@ -27,7 +24,7 @@ export const useVortexAccount = () => {
}, [evmAccountAddress, polkadotWalletAccount, selectedNetwork]);
const isDisconnected = useMemo(() => {
- if (selectedNetwork === Networks.AssetHub) {
+ if (!isNetworkEVM(selectedNetwork)) {
return !polkadotWalletAccount;
} else {
return isEvmAccountDisconnected;
@@ -35,15 +32,15 @@ export const useVortexAccount = () => {
}, [selectedNetwork, polkadotWalletAccount, isEvmAccountDisconnected]);
const chainId = useMemo(() => {
- if (selectedNetwork === Networks.AssetHub) {
- return AssetHubChainId;
+ if (!isNetworkEVM(selectedNetwork)) {
+ return ASSETHUB_CHAIN_ID;
} else {
return evmChainId;
}
}, [selectedNetwork, evmChainId]);
const type = useMemo(() => {
- if (selectedNetwork === Networks.AssetHub) {
+ if (!isNetworkEVM(selectedNetwork)) {
return 'substrate';
} else {
return 'evm';
@@ -54,9 +51,9 @@ export const useVortexAccount = () => {
async (siweMessage: string) => {
let signature;
- if (selectedNetwork === Networks.Polygon) {
+ if (isNetworkEVM(selectedNetwork)) {
signature = await signMessageAsync({ message: siweMessage });
- } else if (selectedNetwork === Networks.AssetHub) {
+ } else {
if (!polkadotWalletAccount) {
throw new Error('getMessageSignature: Polkadot wallet account not found. Wallet must be connected to sign.');
}
@@ -66,8 +63,6 @@ export const useVortexAccount = () => {
address: polkadotWalletAccount.address,
});
signature = substrateSignature;
- } else {
- throw new Error('getMessageSignature: Unsupported network.');
}
return signature;
diff --git a/src/pages/progress/index.tsx b/src/pages/progress/index.tsx
index 4c77047e..330c9e72 100644
--- a/src/pages/progress/index.tsx
+++ b/src/pages/progress/index.tsx
@@ -5,41 +5,39 @@ import { Box } from '../../components/Box';
import { BaseLayout } from '../../layouts';
import { useEventsContext } from '../../contexts/events';
import { getInputTokenDetailsOrDefault, OUTPUT_TOKEN_CONFIG } from '../../constants/tokenConfig';
-import { Networks, useNetwork } from '../../contexts/network';
+import { useNetwork } from '../../contexts/network';
+import { Networks, isNetworkEVM, getNetworkDisplayName } from '../../helpers/networks';
function createOfframpingPhaseMessage(offrampingState: OfframpingState, network: Networks): string {
const inputToken = getInputTokenDetailsOrDefault(network, offrampingState.inputTokenType);
const outputToken = OUTPUT_TOKEN_CONFIG[offrampingState.outputTokenType];
-
- switch (offrampingState.phase) {
- case 'prepareTransactions':
- return offrampingState.network
- ? `Bridging ${inputToken.assetSymbol} from Polygon --> Moonbeam`
- : `Bridging ${inputToken.assetSymbol} from AssetHub --> Pendulum`;
- case 'squidRouter':
- case 'pendulumFundEphemeral':
- return offrampingState.network
- ? `Bridging ${inputToken.assetSymbol} from Polygon --> Moonbeam`
- : `Bridging ${inputToken.assetSymbol} from AssetHub --> Pendulum`;
- case 'executeMoonbeamXCM':
- return `Transferring ${inputToken.assetSymbol} from Moonbeam --> Pendulum`;
- case 'executeAssetHubXCM':
- return `Bridging ${inputToken.assetSymbol} from AssetHub --> Pendulum`;
- case 'subsidizePreSwap':
- case 'nablaApprove':
- case 'nablaSwap':
- case 'subsidizePostSwap':
- return `Swapping to ${outputToken.stellarAsset.code.string} on Vortex DEX`;
- case 'executeSpacewalkRedeem':
- return `Bridging ${outputToken.stellarAsset.code.string} to Stellar via Spacewalk`;
- case 'pendulumCleanup':
- case 'stellarOfframp':
- case 'stellarCleanup':
- return 'Transferring to local partner for bank transfer';
-
- default:
- return '';
- }
+ const { phase } = offrampingState;
+
+ if (phase === 'success') return 'Transaction completed successfully';
+
+ const messages: Record = {
+ prepareTransactions: isNetworkEVM(network)
+ ? `Bridging ${inputToken.assetSymbol} from ${getNetworkDisplayName(network)} --> Moonbeam`
+ : `Bridging ${inputToken.assetSymbol} from AssetHub --> Pendulum`,
+ squidRouter: isNetworkEVM(network)
+ ? `Bridging ${inputToken.assetSymbol} from ${getNetworkDisplayName(network)} --> Moonbeam`
+ : `Bridging ${inputToken.assetSymbol} from AssetHub --> Pendulum`,
+ pendulumFundEphemeral: isNetworkEVM(network)
+ ? `Bridging ${inputToken.assetSymbol} from ${getNetworkDisplayName(network)} --> Moonbeam`
+ : `Bridging ${inputToken.assetSymbol} from AssetHub --> Pendulum`,
+ executeMoonbeamXCM: `Transferring ${inputToken.assetSymbol} from Moonbeam --> Pendulum`,
+ executeAssetHubXCM: `Bridging ${inputToken.assetSymbol} from AssetHub --> Pendulum`,
+ subsidizePreSwap: `Swapping to ${outputToken.stellarAsset.code.string} on Vortex DEX`,
+ nablaApprove: `Swapping to ${outputToken.stellarAsset.code.string} on Vortex DEX`,
+ nablaSwap: `Swapping to ${outputToken.stellarAsset.code.string} on Vortex DEX`,
+ subsidizePostSwap: `Swapping to ${outputToken.stellarAsset.code.string} on Vortex DEX`,
+ executeSpacewalkRedeem: `Bridging ${outputToken.stellarAsset.code.string} to Stellar via Spacewalk`,
+ pendulumCleanup: 'Transferring to local partner for bank transfer',
+ stellarOfframp: 'Transferring to local partner for bank transfer',
+ stellarCleanup: 'Transferring to local partner for bank transfer',
+ };
+
+ return messages[phase as OfframpingPhase];
}
export const OFFRAMPING_PHASE_SECONDS: Record = {
@@ -160,9 +158,7 @@ const ProgressContent: FC<{
Your transaction is in progress.
- {selectedNetwork === Networks.AssetHub
- ? 'This usually takes 4-6 minutes.'
- : 'This usually takes 6-8 minutes.'}
+ {!isNetworkEVM(selectedNetwork) ? 'This usually takes 4-6 minutes.' : 'This usually takes 6-8 minutes.'}
{message}