From 609065e821638cb005c8c34b7c2e1a5fb193d096 Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Thu, 12 Dec 2024 11:05:44 -0300 Subject: [PATCH 01/32] add new networks to arrays and configs --- src/components/Nabla/useSwapForm.tsx | 29 +++-- src/components/NetworkSelector/index.tsx | 5 + src/components/SigningBox/index.tsx | 60 +++++----- .../buttons/SwapSubmitButton/index.tsx | 4 +- src/constants/tokenConfig.ts | 105 ++++++++++++++++++ src/contexts/network.tsx | 19 ++++ src/hooks/useGetNetworkIcon.tsx | 5 + src/pages/swap/index.tsx | 2 +- src/services/offrampingFlow.ts | 24 ++-- src/services/phases/polkadot/ephemeral.tsx | 8 +- src/services/phases/signedTransactions.ts | 4 +- src/wagmiConfig.ts | 4 +- 12 files changed, 215 insertions(+), 54 deletions(-) diff --git a/src/components/Nabla/useSwapForm.tsx b/src/components/Nabla/useSwapForm.tsx index 8f46d996..7cb73881 100644 --- a/src/components/Nabla/useSwapForm.tsx +++ b/src/components/Nabla/useSwapForm.tsx @@ -23,14 +23,27 @@ interface SwapSettings { const storageSet = debounce(storageService.set, 1000); const setStorageForSwapSettings = storageSet.bind(null, storageKeys.SWAP_SETTINGS); -function getCaseSensitiveNetwork(network: string): Networks { - if (network.toLowerCase() === 'assethub') { - return Networks.AssetHub; - } else if (network.toLowerCase() === 'polygon') { - return Networks.Polygon; - } else { - console.warn('Invalid network type'); - return Networks.AssetHub; +export function getCaseSensitiveNetwork(network: string): Networks { + const lowercasedNetwork = network.toLowerCase(); + + switch (lowercasedNetwork) { + case 'assethub': + return Networks.AssetHub; + case 'polygon': + return Networks.Polygon; + case 'ethereum': + return Networks.Ethereum; + case 'bsc': + return Networks.BSC; + case 'arbitrum': + return Networks.Arbitrum; + case 'base': + return Networks.Base; + case 'avalanche': + return Networks.Avalanche; + default: + console.warn('getCaseSensitiveNetwork: Invalid network type'); + return Networks.AssetHub; } } diff --git a/src/components/NetworkSelector/index.tsx b/src/components/NetworkSelector/index.tsx index 71be979d..c418de89 100644 --- a/src/components/NetworkSelector/index.tsx +++ b/src/components/NetworkSelector/index.tsx @@ -8,6 +8,11 @@ import { useState, useRef, useEffect } from 'preact/hooks'; const NETWORK_DISPLAY_NAMES: Record = { [Networks.AssetHub]: 'Polkadot AssetHub', [Networks.Polygon]: 'Polygon', + [Networks.Ethereum]: 'Ethereum', + [Networks.BSC]: 'Binance Smart Chain', + [Networks.Arbitrum]: 'Arbitrum', + [Networks.Base]: 'Base', + [Networks.Avalanche]: 'Avalanche', }; function networkToDisplayName(network: Networks): string { diff --git a/src/components/SigningBox/index.tsx b/src/components/SigningBox/index.tsx index 7556d085..69d1a32e 100644 --- a/src/components/SigningBox/index.tsx +++ b/src/components/SigningBox/index.tsx @@ -3,7 +3,7 @@ import { FC } from 'preact/compat'; import accountBalanceWalletIcon from '../../assets/account-balance-wallet.svg'; import { SigningPhase } from '../../hooks/offramp/useMainProcess'; -import { Networks, useNetwork } from '../../contexts/network'; +import { isNetworkEVM, Networks, useNetwork } from '../../contexts/network'; import { Spinner } from '../Spinner'; interface ProgressConfig { @@ -13,31 +13,37 @@ interface ProgressConfig { approved: string; } -const PROGRESS_CONFIG: Record = { - [Networks.AssetHub]: { - started: '33', - finished: '100', - signed: '0', - approved: '0', - }, - [Networks.Polygon]: { - started: '25', - approved: '50', - signed: '75', - finished: '100', - }, -}; +function getProgressConfig(network: Networks, step: SigningPhase): ProgressConfig { + if (isNetworkEVM(network)) { + return { + started: '25', + approved: '50', + signed: '75', + finished: '100', + }; + } else { + return { + started: '33', + finished: '100', + signed: '0', + approved: '0', + }; + } +} -const SIGNATURE_CONFIG = { - [Networks.AssetHub]: { - maxSignatures: 1, - getSignatureNumber: () => '1', - }, - [Networks.Polygon]: { - maxSignatures: 2, - getSignatureNumber: (step: SigningPhase) => (step === 'started' ? '1' : '2'), - }, -}; +function getSignatureConfig(network: Networks): any { + if (isNetworkEVM(network)) { + return { + maxSignatures: 2, + getSignatureNumber: (step: SigningPhase) => (step === 'started' ? '1' : '2'), + }; + } else { + return { + maxSignatures: 1, + getSignatureNumber: () => '1', + }; + } +} interface SigningBoxProps { step?: SigningPhase; @@ -50,8 +56,8 @@ export const SigningBox: FC = ({ step }) => { if (!['started', 'approved', 'signed'].includes(step)) return null; if (selectedNetwork === Networks.AssetHub && (step === 'approved' || step === 'signed')) return null; - const progressValue = PROGRESS_CONFIG[selectedNetwork][step] || '0'; - const { maxSignatures, getSignatureNumber } = SIGNATURE_CONFIG[selectedNetwork]; + const progressValue = getProgressConfig(selectedNetwork, step) || '0'; + const { maxSignatures, getSignatureNumber } = getSignatureConfig(selectedNetwork); return (
diff --git a/src/components/buttons/SwapSubmitButton/index.tsx b/src/components/buttons/SwapSubmitButton/index.tsx index 069ef765..557e0fc7 100644 --- a/src/components/buttons/SwapSubmitButton/index.tsx +++ b/src/components/buttons/SwapSubmitButton/index.tsx @@ -3,7 +3,7 @@ import { Spinner } from '../../Spinner'; import { useAppKitAccount } from '@reown/appkit/react'; import { ConnectWalletButton } from '../ConnectWalletButton'; import { usePolkadotWalletState } from '../../../contexts/polkadotWallet'; -import { Networks, useNetwork } from '../../../contexts/network'; +import { Networks, useNetwork, isNetworkEVM } from '../../../contexts/network'; interface SwapSubmitButtonProps { text: string; @@ -26,7 +26,7 @@ export const SwapSubmitButton: FC = ({ 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..b27f48d5 100644 --- a/src/constants/tokenConfig.ts +++ b/src/constants/tokenConfig.ts @@ -107,6 +107,111 @@ export const INPUT_TOKEN_CONFIG: Record { amount={fromAmount} targetAssetSymbol={toToken.fiat.symbol} vortexPrice={vortexPrice} - network={Networks.Polygon} + network={Networks.Polygon} // TODO, need to pass the proper selected network, unless it is substrate. Also, assuming it works with all supported EVMs /> )} diff --git a/src/services/offrampingFlow.ts b/src/services/offrampingFlow.ts index 54f281f6..70ebc0f6 100644 --- a/src/services/offrampingFlow.ts +++ b/src/services/offrampingFlow.ts @@ -10,7 +10,7 @@ import { u8aToHex } from '@polkadot/util'; import { RenderEventHandler } from '../components/GenericEvent'; import { SigningPhase } from '../hooks/offramp/useMainProcess'; import { TrackableEvent } from '../contexts/events'; -import { Networks } from '../contexts/network'; +import { isNetworkEVM, Networks } from '../contexts/network'; import { SepResult } from './anchor'; import { @@ -129,11 +129,8 @@ export type StateTransitionFunction = ( const OFFRAMPING_STATE_LOCAL_STORAGE_KEY = 'offrampingState'; const minutesInMs = (minutes: number) => minutes * 60 * 1000; -const STATE_ADVANCEMENT_HANDLERS: Record< - keyof typeof Networks, - Partial> -> = { - Polygon: { +const STATE_ADVANCEMENT_HANDLERS: Record>> = { + squidrouter: { prepareTransactions, squidRouter, pendulumFundEphemeral, @@ -147,7 +144,7 @@ const STATE_ADVANCEMENT_HANDLERS: Record< stellarOfframp, stellarCleanup, }, - AssetHub: { + xcm: { prepareTransactions, pendulumFundEphemeral, executeAssetHubXCM, @@ -162,6 +159,17 @@ const STATE_ADVANCEMENT_HANDLERS: Record< }, }; +function selectNextStateAdvancementHandler( + network: Networks, + phase: OfframpingPhase, +): StateTransitionFunction | undefined { + if (isNetworkEVM(network)) { + return STATE_ADVANCEMENT_HANDLERS['squidrouter'][phase]; + } else { + return STATE_ADVANCEMENT_HANDLERS['xcm'][phase]; + } +} + export async function constructInitialState({ sep24Id, stellarEphemeralSecret, @@ -272,7 +280,7 @@ export const advanceOfframpingState = async ( let newState: OfframpingState | undefined; try { - const nextHandler = STATE_ADVANCEMENT_HANDLERS[state.network][phase]; + const nextHandler = selectNextStateAdvancementHandler(state.network, phase); if (!nextHandler) { throw new Error(`No handler for phase ${phase} on network ${state.network}`); } diff --git a/src/services/phases/polkadot/ephemeral.tsx b/src/services/phases/polkadot/ephemeral.tsx index d7790ae9..8ef8e25f 100644 --- a/src/services/phases/polkadot/ephemeral.tsx +++ b/src/services/phases/polkadot/ephemeral.tsx @@ -15,7 +15,7 @@ import { SIGNING_SERVICE_URL } from '../../../constants/constants'; import { multiplyByPowerOfTen } from '../../../helpers/contracts'; import { waitUntilTrue } from '../../../helpers/function'; -import { Networks } from '../../../contexts/network'; +import { isNetworkEVM, Networks } from '../../../contexts/network'; import { ExecutionContext, OfframpingState } from '../../offrampingFlow'; import { fetchSigningServiceAccountId } from '../../signingService'; import { isHashRegistered } from '../moonbeam'; @@ -88,7 +88,7 @@ export async function pendulumFundEphemeral( const { squidRouterSwapHash } = state; const { wagmiConfig } = context; - if (state.network !== Networks.AssetHub) { + if (isNetworkEVM(state.network)) { if (squidRouterSwapHash === undefined) { throw new Error('No squid router swap hash found'); } @@ -108,13 +108,13 @@ export async function pendulumFundEphemeral( await waitUntilTrue(() => isEphemeralFunded(state, context)); } - if (state.network !== Networks.AssetHub) { + if (isNetworkEVM(state.network)) { await waitUntilTrue(() => isHashRegistered(state.squidRouterReceiverHash)); } return { ...state, - phase: state.network === Networks.AssetHub ? 'executeAssetHubXCM' : 'executeMoonbeamXCM', + phase: isNetworkEVM(state.network) ? 'executeAssetHubXCM' : 'executeMoonbeamXCM', }; } diff --git a/src/services/phases/signedTransactions.ts b/src/services/phases/signedTransactions.ts index 745f7874..97305998 100644 --- a/src/services/phases/signedTransactions.ts +++ b/src/services/phases/signedTransactions.ts @@ -3,7 +3,7 @@ import { ApiPromise, Keyring } from '@polkadot/api'; import { Extrinsic } from '@pendulum-chain/api-solang'; import { Keypair } from 'stellar-sdk'; -import { Networks } from '../../contexts/network'; +import { isNetworkEVM, Networks } from '../../contexts/network'; import { ExecutionContext, OfframpingState } from '../offrampingFlow'; import { fetchSigningServiceAccountId } from '../signingService'; @@ -99,6 +99,6 @@ export async function prepareTransactions(state: OfframpingState, context: Execu return { ...state, transactions, - phase: state.network === Networks.AssetHub ? 'pendulumFundEphemeral' : 'squidRouter', + phase: isNetworkEVM(state.network) ? 'pendulumFundEphemeral' : 'squidRouter', }; } diff --git a/src/wagmiConfig.ts b/src/wagmiConfig.ts index 2c246bb0..05a71451 100644 --- a/src/wagmiConfig.ts +++ b/src/wagmiConfig.ts @@ -1,4 +1,4 @@ -import { polygon } from '@reown/appkit/networks'; +import { polygon, bsc, arbitrum, base, avalanche } from '@reown/appkit/networks'; import { http } from 'wagmi'; import { config } from './config'; import { WagmiAdapter } from '@reown/appkit-adapter-wagmi'; @@ -22,7 +22,7 @@ const metadata = { }; // 3. Set the networks -const networks = [polygon]; +const networks = [polygon, bsc, arbitrum, base, avalanche]; const projectId = '495a5f574d57e27fd65caa26d9ea4f10'; // 4. Create Wagmi Adapter From d78dbaf12581ebc706e961c65bb4dd3938fd270f Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Thu, 12 Dec 2024 12:31:12 -0300 Subject: [PATCH 02/32] show 0 balance, squidrouter fix for multiple evm networks --- src/components/NetworkSelector/index.tsx | 4 +-- src/contexts/network.tsx | 33 ++++++++++++++++--- src/hooks/useInputTokenBalance.ts | 2 +- src/pages/swap/index.tsx | 2 +- src/services/phases/moonbeam.ts | 4 +-- .../squidrouter/__tests__/route.test.tsx | 5 +-- src/services/phases/squidrouter/config.ts | 26 ++++++++++++--- src/services/phases/squidrouter/process.ts | 1 + src/services/phases/squidrouter/route.ts | 20 +++++++---- src/wagmiConfig.ts | 5 +-- 10 files changed, 77 insertions(+), 25 deletions(-) diff --git a/src/components/NetworkSelector/index.tsx b/src/components/NetworkSelector/index.tsx index c418de89..21d9a73c 100644 --- a/src/components/NetworkSelector/index.tsx +++ b/src/components/NetworkSelector/index.tsx @@ -9,8 +9,8 @@ const NETWORK_DISPLAY_NAMES: Record = { [Networks.AssetHub]: 'Polkadot AssetHub', [Networks.Polygon]: 'Polygon', [Networks.Ethereum]: 'Ethereum', - [Networks.BSC]: 'Binance Smart Chain', - [Networks.Arbitrum]: 'Arbitrum', + [Networks.BSC]: 'BNB Smart Chain', + [Networks.Arbitrum]: 'Arbitrum One', [Networks.Base]: 'Base', [Networks.Avalanche]: 'Avalanche', }; diff --git a/src/contexts/network.tsx b/src/contexts/network.tsx index 73cc264d..4c02208d 100644 --- a/src/contexts/network.tsx +++ b/src/contexts/network.tsx @@ -29,6 +29,25 @@ export function isNetworkEVM(network: Networks): boolean { } } +export function getNetworkId(network: Networks): number | undefined { + switch (network) { + case Networks.Polygon: + return 137; + case Networks.Ethereum: + return 1; + case Networks.BSC: + return 56; + case Networks.Arbitrum: + return 42161; + case Networks.Base: + return 8453; + case Networks.Avalanche: + return 43114; + default: + return undefined; + } +} + interface NetworkContextType { walletConnectPolkadotSelectedNetworkId: string; selectedNetwork: Networks; @@ -63,13 +82,16 @@ export const NetworkProvider = ({ children }: NetworkProviderProps) => { const { chains, switchChain } = useSwitchChain(); const setSelectedNetwork = useCallback( - (networkId: Networks) => { + (network: Networks) => { if (onNetworkChange) { - onNetworkChange(networkId); + onNetworkChange(network); } - setSelectedNetworkState(networkId); - setSelectedNetworkLocalStorage(networkId); - const chain = chains.find((c) => c.id === Number(networkId)); + console.log('setSelectedNetwork', network); + setSelectedNetworkState(network); + setSelectedNetworkLocalStorage(network); + + // Will only switch chain on the EVM conneted wallet, if chain id has been defined in wagmi config. + const chain = chains.find((c) => c.id === getNetworkId(network)); if (chain) { switchChain({ chainId: chain.id }); } @@ -77,6 +99,7 @@ export const NetworkProvider = ({ children }: NetworkProviderProps) => { [switchChain, chains, setSelectedNetworkLocalStorage, onNetworkChange], ); + // Only run on first render useEffect(() => { const params = new URLSearchParams(window.location.search); const networkParam = params.get('network')?.toLowerCase(); diff --git a/src/hooks/useInputTokenBalance.ts b/src/hooks/useInputTokenBalance.ts index 85a05b10..c82d3ce7 100644 --- a/src/hooks/useInputTokenBalance.ts +++ b/src/hooks/useInputTokenBalance.ts @@ -22,7 +22,7 @@ const useEvmBalance = ( args: [address], }); - if (!fromToken || !balance) return undefined; + if (!fromToken || (!balance && balance !== BigInt(0))) return undefined; return multiplyByPowerOfTen(Big(balance.toString()), -fromToken.decimals).toFixed(2, 0); }; diff --git a/src/pages/swap/index.tsx b/src/pages/swap/index.tsx index d2d7a891..7812da4b 100644 --- a/src/pages/swap/index.tsx +++ b/src/pages/swap/index.tsx @@ -194,7 +194,7 @@ export const SwapPage = () => { outputToken.stellarAsset.issuer.hex, expectedRedeemAmountRaw, ), - testRoute(fromToken, inputAmountRaw, address!), // Address is both sender and receiver (in different chains) + testRoute(fromToken, inputAmountRaw, address!, selectedNetwork), // Address is both sender and receiver (in different chains) ]) .then(() => { console.log('Initial checks completed. Starting process..'); diff --git a/src/services/phases/moonbeam.ts b/src/services/phases/moonbeam.ts index a0978646..ce38fe5f 100644 --- a/src/services/phases/moonbeam.ts +++ b/src/services/phases/moonbeam.ts @@ -13,7 +13,7 @@ import encodePayload from './squidrouter/payload'; import { ExecutionContext, OfframpingState } from '../offrampingFlow'; import { getRawInputBalance } from './polkadot/ephemeral'; -import { squidRouterConfig } from './squidrouter/config'; +import { squidRouterConfigBase } from './squidrouter/config'; export const moonbeamConfig = createConfig({ chains: [moonbeam], @@ -81,7 +81,7 @@ export async function isHashRegistered(hash: `0x${string}`): Promise { const result = (await readContract(moonbeamConfig, { abi: squidReceiverABI, chainId: moonbeam.id, - address: squidRouterConfig.receivingContractAddress, + address: squidRouterConfigBase.receivingContractAddress, functionName: 'xcmDataMapping', args: [hash], })) as bigint; diff --git a/src/services/phases/squidrouter/__tests__/route.test.tsx b/src/services/phases/squidrouter/__tests__/route.test.tsx index d6daaf29..3f03291d 100644 --- a/src/services/phases/squidrouter/__tests__/route.test.tsx +++ b/src/services/phases/squidrouter/__tests__/route.test.tsx @@ -5,6 +5,7 @@ import { INPUT_TOKEN_CONFIG } from '../../../../constants/tokenConfig'; import { InputTokenDetails } from '../../../../constants/tokenConfig'; import { createSquidRouterHash } from '../../../../helpers/crypto'; import { createRandomString } from '../../../../helpers/crypto'; +import { Networks } from '../../../../contexts/network'; // We need to add a delay to the beforeEach hook to ensure that the test does not run before the SquidRouter API is ready beforeEach(() => { @@ -26,13 +27,13 @@ describe('Squidrouter', () => { const squidRouterHash = createSquidRouterHash(squidRouterReceiverId, somePayload); const amount = '1000000000'; - return getRouteTransactionRequest(userAddress, amount, squidRouterHash, inputToken); + return getRouteTransactionRequest(userAddress, amount, squidRouterHash, inputToken, Networks.Polygon); } async function testRouteForToken(inputToken: InputTokenDetails) { const userAddress = '0x7Ba99e99Bc669B3508AFf9CC0A898E869459F877' as `0x${string}`; const amount = '1000000000'; - return testRoute(inputToken, amount, userAddress); + return testRoute(inputToken, amount, userAddress, Networks.Polygon); } it('should successfully query a route for USDC', async () => { diff --git a/src/services/phases/squidrouter/config.ts b/src/services/phases/squidrouter/config.ts index dd6ca40c..c1d9da8f 100644 --- a/src/services/phases/squidrouter/config.ts +++ b/src/services/phases/squidrouter/config.ts @@ -1,15 +1,33 @@ -interface Config { - fromChainId: string; +import { getNetworkId, Networks } from '../../../contexts/network'; + +interface ConfigBase { toChainId: string; axlUSDC_MOONBEAM: string; integratorId: string; receivingContractAddress: `0x${string}`; } -export const squidRouterConfig: Config = { - fromChainId: '137', +interface Config extends ConfigBase { + fromChainId: string; +} + +export const squidRouterConfigBase: ConfigBase = { toChainId: '1284', axlUSDC_MOONBEAM: '0xca01a1d0993565291051daff390892518acfad3a', integratorId: 'pendulum-7cffebc5-f84f-4669-96b4-4f8c82640811', receivingContractAddress: '0x2AB52086e8edaB28193172209407FF9df1103CDc', }; + +export function getSquidRouterConfig(network: Networks): Config { + let networkId = getNetworkId(network); + if (!networkId) { + throw new Error('getSquidRouterConfig: Network must be EVM to support SquidRouter'); + } + return { + fromChainId: networkId.toString(), + toChainId: squidRouterConfigBase.toChainId, + axlUSDC_MOONBEAM: squidRouterConfigBase.axlUSDC_MOONBEAM, + integratorId: squidRouterConfigBase.integratorId, + receivingContractAddress: squidRouterConfigBase.receivingContractAddress, + }; +} diff --git a/src/services/phases/squidrouter/process.ts b/src/services/phases/squidrouter/process.ts index 96c0fb24..1f789508 100644 --- a/src/services/phases/squidrouter/process.ts +++ b/src/services/phases/squidrouter/process.ts @@ -28,6 +28,7 @@ export async function squidRouter( state.inputAmount.raw, state.squidRouterReceiverHash, inputToken, + state.network, ); console.log( diff --git a/src/services/phases/squidrouter/route.ts b/src/services/phases/squidrouter/route.ts index 3a6c9905..d6f38289 100644 --- a/src/services/phases/squidrouter/route.ts +++ b/src/services/phases/squidrouter/route.ts @@ -4,7 +4,8 @@ import { encodeFunctionData } from 'viem'; import squidReceiverABI from '../../../../mooncontracts/splitReceiverABI.json'; import { InputTokenDetails, isEvmInputTokenDetails } from '../../../constants/tokenConfig'; import erc20ABI from '../../../contracts/ERC20'; -import { squidRouterConfig } from './config'; +import { getSquidRouterConfig, squidRouterConfigBase } from './config'; +import { Networks } from '../../../contexts/network'; interface RouteParams { fromAddress: string; @@ -32,8 +33,9 @@ function createRouteParams( amount: string, squidRouterReceiverHash: `0x${string}`, inputToken: InputTokenDetails, + fromNetwork: Networks, ): RouteParams { - const { fromChainId, toChainId, receivingContractAddress, axlUSDC_MOONBEAM } = squidRouterConfig; + const { fromChainId, toChainId, receivingContractAddress, axlUSDC_MOONBEAM } = getSquidRouterConfig(fromNetwork); if (!isEvmInputTokenDetails(inputToken)) { throw new Error(`Token ${inputToken.assetSymbol} is not supported on EVM chains`); @@ -107,7 +109,7 @@ function createRouteParams( async function getRoute(params: RouteParams) { // This is the integrator ID for the Squid API at 'https://apiplus.squidrouter.com/v2' - const { integratorId } = squidRouterConfig; + const { integratorId } = squidRouterConfigBase; const url = 'https://apiplus.squidrouter.com/v2/route'; try { @@ -134,8 +136,9 @@ export async function getRouteTransactionRequest( amount: string, squidRouterReceiverHash: `0x${string}`, inputToken: InputTokenDetails, + fromNetwork: Networks, ) { - const routeParams = createRouteParams(userAddress, amount, squidRouterReceiverHash, inputToken); + const routeParams = createRouteParams(userAddress, amount, squidRouterReceiverHash, inputToken, fromNetwork); // Get the swap route using Squid API const routeResult = await getRoute(routeParams); @@ -154,8 +157,13 @@ export async function getRouteTransactionRequest( }; } -export async function testRoute(testingToken: InputTokenDetails, attemptedAmountRaw: string, address: `0x${string}`) { - const { fromChainId, toChainId, axlUSDC_MOONBEAM } = squidRouterConfig; +export async function testRoute( + testingToken: InputTokenDetails, + attemptedAmountRaw: string, + address: `0x${string}`, + fromNetwork: Networks, +) { + const { fromChainId, toChainId, axlUSDC_MOONBEAM } = getSquidRouterConfig(fromNetwork); if (!isEvmInputTokenDetails(testingToken)) { return; } diff --git a/src/wagmiConfig.ts b/src/wagmiConfig.ts index 05a71451..c9f0e358 100644 --- a/src/wagmiConfig.ts +++ b/src/wagmiConfig.ts @@ -1,10 +1,11 @@ -import { polygon, bsc, arbitrum, base, avalanche } from '@reown/appkit/networks'; +import { polygon, bsc, arbitrum, base, avalanche, mainnet } from '@reown/appkit/networks'; import { http } from 'wagmi'; import { config } from './config'; import { WagmiAdapter } from '@reown/appkit-adapter-wagmi'; import { createAppKit } from '@reown/appkit/react'; // If we have an Alchemy API key, we can use it to fetch data from Polygon, otherwise use the default endpoint +// TODO we need to add better RPCs because metamask warns about unkown ones (defaults). Avalanche, base, etc. const transports = config.alchemyApiKey ? { [polygon.id]: http(`https://polygon-mainnet.g.alchemy.com/v2/${config.alchemyApiKey}`), @@ -22,7 +23,7 @@ const metadata = { }; // 3. Set the networks -const networks = [polygon, bsc, arbitrum, base, avalanche]; +const networks = [mainnet, polygon, bsc, arbitrum, base, avalanche]; const projectId = '495a5f574d57e27fd65caa26d9ea4f10'; // 4. Create Wagmi Adapter From 2769cb62e41bcf60874207f37da85e2aed636ed3 Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Thu, 12 Dec 2024 15:14:30 -0300 Subject: [PATCH 03/32] custom initialization error message, change evm selected network on confirm, handle different evm decimals --- src/constants/tokenConfig.ts | 3 ++ src/hooks/nabla/useTokenAmountOut.ts | 2 +- src/hooks/offramp/useMainProcess.ts | 2 +- src/hooks/offramp/useSEP24/index.ts | 8 ---- src/hooks/offramp/useSEP24/useSEP24State.ts | 8 +--- .../offramp/useSEP24/useTrackSEP24Events.ts | 7 +-- src/hooks/offramp/useSubmitOfframp.ts | 44 ++++++++++++------- src/hooks/useInputTokenBalance.ts | 2 +- src/pages/swap/index.tsx | 36 +++++++++------ src/services/phases/nabla.ts | 2 +- 10 files changed, 60 insertions(+), 54 deletions(-) diff --git a/src/constants/tokenConfig.ts b/src/constants/tokenConfig.ts index b27f48d5..a15d76d7 100644 --- a/src/constants/tokenConfig.ts +++ b/src/constants/tokenConfig.ts @@ -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>> = { diff --git a/src/hooks/nabla/useTokenAmountOut.ts b/src/hooks/nabla/useTokenAmountOut.ts index 4e3ab0df..834c8c27 100644 --- a/src/hooks/nabla/useTokenAmountOut.ts +++ b/src/hooks/nabla/useTokenAmountOut.ts @@ -72,7 +72,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 22260e88..d43eea08 100644 --- a/src/hooks/offramp/useMainProcess.ts +++ b/src/hooks/offramp/useMainProcess.ts @@ -30,7 +30,7 @@ export interface ExecutionInput { outputTokenType: OutputTokenType; amountInUnits: string; offrampAmount: Big; - setInitializeFailed: StateUpdater; + setInitializeFailed: (message?: string | null) => void; } export const useMainProcess = () => { diff --git a/src/hooks/offramp/useSEP24/index.ts b/src/hooks/offramp/useSEP24/index.ts index 6d5eeae5..9f139a00 100644 --- a/src/hooks/offramp/useSEP24/index.ts +++ b/src/hooks/offramp/useSEP24/index.ts @@ -9,14 +9,6 @@ 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); diff --git a/src/hooks/offramp/useSEP24/useSEP24State.ts b/src/hooks/offramp/useSEP24/useSEP24State.ts index 601d30f0..bf060e72 100644 --- a/src/hooks/offramp/useSEP24/useSEP24State.ts +++ b/src/hooks/offramp/useSEP24/useSEP24State.ts @@ -4,13 +4,7 @@ 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; -} +import { ExecutionInput } from '../useMainProcess'; export type ExtendedExecutionInput = ExecutionInput & { stellarEphemeralSecret: string }; diff --git a/src/hooks/offramp/useSEP24/useTrackSEP24Events.ts b/src/hooks/offramp/useSEP24/useTrackSEP24Events.ts index c7e65051..17953f53 100644 --- a/src/hooks/offramp/useSEP24/useTrackSEP24Events.ts +++ b/src/hooks/offramp/useSEP24/useTrackSEP24Events.ts @@ -11,12 +11,7 @@ import { OutputTokenType, } 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 748bf934..3e27498b 100644 --- a/src/hooks/offramp/useSubmitOfframp.ts +++ b/src/hooks/offramp/useSubmitOfframp.ts @@ -1,7 +1,7 @@ import { MutableRefObject, useCallback } from 'preact/compat'; import { polygon } from 'wagmi/chains'; import { useAccount, useSwitchChain } from 'wagmi'; -import { useNetwork } from '../../contexts/network'; +import { getNetworkId, useNetwork } from '../../contexts/network'; import { useEventsContext } from '../../contexts/events'; import { useSiweContext } from '../../contexts/siwe'; @@ -18,7 +18,7 @@ import { import { OfframpingState } from '../../services/offrampingFlow'; import { ExtendedExecutionInput } from './useSEP24/useSEP24State'; -import { ExecutionInput } from './useSEP24'; +import { ExecutionInput } from './useMainProcess'; interface UseSubmitOfframpProps { firstSep24IntervalRef: MutableRefObject; @@ -44,7 +44,7 @@ export const useSubmitOfframp = ({ setIsInitiating, }: UseSubmitOfframpProps) => { const { selectedNetwork } = useNetwork(); - const { switchChain } = useSwitchChain(); + const { switchChainAsync, switchChain } = useSwitchChain(); const { trackEvent } = useEventsContext(); const { address } = useAccount(); const { checkAndWaitForSignature, forceRefreshAndWaitForSignature } = useSiweContext(); @@ -63,18 +63,27 @@ export const useSubmitOfframp = ({ } (async () => { - switchChain({ chainId: polygon.id }); - setOfframpingStarted(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]), - }); + let chainId = getNetworkId(selectedNetwork); + if (!chainId) { + setInitializeFailed(); + setOfframpingStarted(false); + setIsInitiating(false); + return; + } try { + await switchChainAsync({ chainId: chainId! }); + + setOfframpingStarted(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!); @@ -116,7 +125,7 @@ export const useSubmitOfframp = ({ await fetchAndUpdateSep24Url(); } catch (error) { console.error('Error finalizing the initial state of the offramping process', error); - setInitializeFailed(true); + setInitializeFailed(); setOfframpingStarted(false); cleanSep24FirstVariables(); } finally { @@ -124,7 +133,12 @@ export const useSubmitOfframp = ({ } } 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(); + } setOfframpingStarted(false); setIsInitiating(false); } diff --git a/src/hooks/useInputTokenBalance.ts b/src/hooks/useInputTokenBalance.ts index c82d3ce7..99315149 100644 --- a/src/hooks/useInputTokenBalance.ts +++ b/src/hooks/useInputTokenBalance.ts @@ -9,6 +9,7 @@ import { nativeToDecimal, USDC_DECIMALS } from '../helpers/parseNumbers'; import { usePolkadotWalletState } from '../contexts/polkadotWallet'; import { useAssetHubNode } from '../contexts/polkadotNode'; +// TODO maybe improve: if the user switches the network in the selector and REJECTS the switch in wallet, then balance will be 0. const useEvmBalance = ( tokenAddress: `0x${string}` | undefined, fromToken: InputTokenDetails | undefined, @@ -21,7 +22,6 @@ const useEvmBalance = ( functionName: 'balanceOf', args: [address], }); - if (!fromToken || (!balance && balance !== BigInt(0))) return undefined; return multiplyByPowerOfTen(Big(balance.toString()), -fromToken.decimals).toFixed(2, 0); }; diff --git a/src/pages/swap/index.tsx b/src/pages/swap/index.tsx index 7812da4b..41cac515 100644 --- a/src/pages/swap/index.tsx +++ b/src/pages/swap/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useRef, useState } from 'preact/hooks'; +import { useEffect, useMemo, useRef, useState, useCallback } from 'preact/hooks'; import { Fragment } from 'preact'; import { useAccount } from 'wagmi'; import { ArrowDownIcon } from '@heroicons/react/20/solid'; @@ -62,7 +62,7 @@ export const SwapPage = () => { const pendulumNode = usePendulumNode(); const [api, setApi] = useState(null); const { isDisconnected, address } = useAccount(); - const [initializeFailed, setInitializeFailed] = useState(false); + const [initializeFailedMessage, setInitializeFailedMessage] = useState(null); const [apiInitializeFailed, setApiInitializeFailed] = useState(false); const [_, setIsReady] = useState(false); const [showCompareFees, setShowCompareFees] = useState(false); @@ -84,13 +84,23 @@ export const SwapPage = () => { await initialChecks(); setIsReady(true); } catch (error) { - setInitializeFailed(true); + setInitializeFailed(); } }; initialize(); }, []); + // Define the function using useCallback + const setInitializeFailed = useCallback( + (message?: string | null) => { + setInitializeFailedMessage( + message ?? 'Application initialization failed. Please reload, or try again later if the problem persists.', + ); + }, + [], // No dependencies, so the function remains stable + ); + // Main process hook const { handleOnSubmit, @@ -208,7 +218,7 @@ export const SwapPage = () => { }) .catch((_error) => { setIsInitiating(false); - setInitializeFailed(true); + setInitializeFailed(); }); } @@ -298,12 +308,12 @@ export const SwapPage = () => { // Do not show any error if the user is disconnected if (isDisconnected) return; - if (typeof userInputTokenBalance === 'string') { - if (Big(userInputTokenBalance).lt(fromAmount ?? 0)) { - trackEvent({ event: 'form_error', error_message: 'insufficient_balance' }); - return `Insufficient balance. Your balance is ${userInputTokenBalance} ${fromToken?.assetSymbol}.`; - } - } + // if (typeof userInputTokenBalance === 'string') { + // if (Big(userInputTokenBalance).lt(fromAmount ?? 0)) { + // trackEvent({ event: 'form_error', error_message: 'insufficient_balance' }); + // return `Insufficient balance. Your balance is ${userInputTokenBalance} ${fromToken?.assetSymbol}.`; + // } + // } const amountOut = tokenOutAmount.data?.roundedDownQuotedAmountOut; @@ -418,10 +428,8 @@ export const SwapPage = () => {
- {(initializeFailed || apiInitializeFailed) && ( -

- Application initialization failed. Please reload, or try again later if the problem persists. -

+ {(initializeFailedMessage || apiInitializeFailed) && ( +

{initializeFailedMessage}

)}
diff --git a/src/services/phases/nabla.ts b/src/services/phases/nabla.ts index 95496d6b..eece8d77 100644 --- a/src/services/phases/nabla.ts +++ b/src/services/phases/nabla.ts @@ -113,7 +113,7 @@ export async function prepareNablaApproveTransaction( throw new Error(message); } - const currentAllowance = parseContractBalanceResponse(inputToken.decimals, response.value); + const currentAllowance = parseContractBalanceResponse(inputToken.pendulumDecimals, response.value); //maybe do allowance if (currentAllowance === undefined || currentAllowance.rawBalance.lt(Big(inputAmount.raw))) { From 4cd9e7a5db5fca5e2efad369f2aa2b6ebc0da4fb Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Thu, 12 Dec 2024 15:24:18 -0300 Subject: [PATCH 04/32] fix inverted state transitions --- src/services/phases/polkadot/ephemeral.tsx | 2 +- src/services/phases/signedTransactions.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/phases/polkadot/ephemeral.tsx b/src/services/phases/polkadot/ephemeral.tsx index 8ef8e25f..f76ecb3c 100644 --- a/src/services/phases/polkadot/ephemeral.tsx +++ b/src/services/phases/polkadot/ephemeral.tsx @@ -114,7 +114,7 @@ export async function pendulumFundEphemeral( return { ...state, - phase: isNetworkEVM(state.network) ? 'executeAssetHubXCM' : 'executeMoonbeamXCM', + phase: isNetworkEVM(state.network) ? 'executeMoonbeamXCM' : 'executeAssetHubXCM', }; } diff --git a/src/services/phases/signedTransactions.ts b/src/services/phases/signedTransactions.ts index 97305998..9822fdc3 100644 --- a/src/services/phases/signedTransactions.ts +++ b/src/services/phases/signedTransactions.ts @@ -99,6 +99,6 @@ export async function prepareTransactions(state: OfframpingState, context: Execu return { ...state, transactions, - phase: isNetworkEVM(state.network) ? 'pendulumFundEphemeral' : 'squidRouter', + phase: isNetworkEVM(state.network) ? 'squidRouter' : 'pendulumFundEphemeral', }; } From d67ce21c2d9592f44a0aae887dab4b06a993afc2 Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Fri, 13 Dec 2024 11:24:17 -0300 Subject: [PATCH 05/32] add some logos, fix substrate wallet only connected offramp --- src/assets/chains/arbitrum.svg | 37 ++++++++++++++++++++++++ src/assets/chains/avalanche.svg | 4 +++ src/assets/chains/base.svg | 4 +++ src/assets/chains/bsc.svg | 9 ++++++ src/assets/chains/ethereum.svg | 15 ++++++++++ src/assets/coins/USDC_ETHEREUM.svg | 29 +++++++++++++++++++ src/hooks/offramp/useSubmitOfframp.ts | 22 +++++++------- src/hooks/useGetNetworkIcon.tsx | 16 ++++++---- src/pages/swap/index.tsx | 1 + src/services/anchor/index.ts | 3 -- src/services/phases/squidrouter/route.ts | 2 +- 11 files changed, 123 insertions(+), 19 deletions(-) create mode 100644 src/assets/chains/arbitrum.svg create mode 100644 src/assets/chains/avalanche.svg create mode 100644 src/assets/chains/base.svg create mode 100644 src/assets/chains/bsc.svg create mode 100644 src/assets/chains/ethereum.svg create mode 100644 src/assets/coins/USDC_ETHEREUM.svg diff --git a/src/assets/chains/arbitrum.svg b/src/assets/chains/arbitrum.svg new file mode 100644 index 00000000..56ed24c9 --- /dev/null +++ b/src/assets/chains/arbitrum.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/chains/avalanche.svg b/src/assets/chains/avalanche.svg new file mode 100644 index 00000000..ebff2a2c --- /dev/null +++ b/src/assets/chains/avalanche.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/assets/chains/base.svg b/src/assets/chains/base.svg new file mode 100644 index 00000000..eefeec83 --- /dev/null +++ b/src/assets/chains/base.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/assets/chains/bsc.svg b/src/assets/chains/bsc.svg new file mode 100644 index 00000000..d1b98da4 --- /dev/null +++ b/src/assets/chains/bsc.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/assets/chains/ethereum.svg b/src/assets/chains/ethereum.svg new file mode 100644 index 00000000..20a69608 --- /dev/null +++ b/src/assets/chains/ethereum.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/coins/USDC_ETHEREUM.svg b/src/assets/coins/USDC_ETHEREUM.svg new file mode 100644 index 00000000..d7507d86 --- /dev/null +++ b/src/assets/coins/USDC_ETHEREUM.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/hooks/offramp/useSubmitOfframp.ts b/src/hooks/offramp/useSubmitOfframp.ts index 3e27498b..9fdfece1 100644 --- a/src/hooks/offramp/useSubmitOfframp.ts +++ b/src/hooks/offramp/useSubmitOfframp.ts @@ -1,7 +1,7 @@ import { MutableRefObject, useCallback } from 'preact/compat'; import { polygon } from 'wagmi/chains'; import { useAccount, useSwitchChain } from 'wagmi'; -import { getNetworkId, useNetwork } from '../../contexts/network'; +import { getNetworkId, isNetworkEVM, useNetwork } from '../../contexts/network'; import { useEventsContext } from '../../contexts/events'; import { useSiweContext } from '../../contexts/siwe'; @@ -63,16 +63,18 @@ export const useSubmitOfframp = ({ } (async () => { - let chainId = getNetworkId(selectedNetwork); - if (!chainId) { - setInitializeFailed(); - setOfframpingStarted(false); - setIsInitiating(false); - return; - } - try { - await switchChainAsync({ chainId: chainId! }); + let chainId = getNetworkId(selectedNetwork); + if (!chainId && isNetworkEVM(selectedNetwork)) { + setInitializeFailed(); + setOfframpingStarted(false); + setIsInitiating(false); + return; + } + + if (isNetworkEVM(selectedNetwork)) { + await switchChainAsync({ chainId: chainId! }); + } setOfframpingStarted(true); diff --git a/src/hooks/useGetNetworkIcon.tsx b/src/hooks/useGetNetworkIcon.tsx index 357dfc1b..2a4978c8 100644 --- a/src/hooks/useGetNetworkIcon.tsx +++ b/src/hooks/useGetNetworkIcon.tsx @@ -1,15 +1,21 @@ import ASSET_HUB from '../assets/chains/assetHub.svg'; import POLYGON from '../assets/chains/polygon.svg'; +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'; + import { Networks } from '../contexts/network'; export const NETWORK_ICONS = { [Networks.AssetHub]: ASSET_HUB, [Networks.Polygon]: POLYGON, - [Networks.Ethereum]: POLYGON, // TODO get proper icons - [Networks.BSC]: POLYGON, - [Networks.Arbitrum]: POLYGON, - [Networks.Base]: POLYGON, - [Networks.Avalanche]: POLYGON, + [Networks.Ethereum]: ETHEREUM, + [Networks.BSC]: BSC, + [Networks.Arbitrum]: ARBITRUM, + [Networks.Base]: BASE, + [Networks.Avalanche]: AVALANCHE, }; export type NetworkIconType = keyof typeof NETWORK_ICONS; diff --git a/src/pages/swap/index.tsx b/src/pages/swap/index.tsx index 41cac515..3dc03c8d 100644 --- a/src/pages/swap/index.tsx +++ b/src/pages/swap/index.tsx @@ -217,6 +217,7 @@ export const SwapPage = () => { }); }) .catch((_error) => { + console.log('Initial checks failed. Aborting process..', _error); setIsInitiating(false); setInitializeFailed(); }); diff --git a/src/services/anchor/index.ts b/src/services/anchor/index.ts index 8e6110fc..cd4c89a4 100644 --- a/src/services/anchor/index.ts +++ b/src/services/anchor/index.ts @@ -231,9 +231,6 @@ export async function sep24First( amount: sessionParams.offrampAmount, account: sep10Account, // THIS is a particularity of Anclap. Should be able to work just with the epmhemeral account // or at least the anchor should be able to get it from the JWT. - // Since we signed with the master/omnibus from the service, we need to specify the corresponding public here - // memo: deriveMemoFromAddress(address!), - // memo_type: 'id', }); } else { sep24Params = new URLSearchParams({ diff --git a/src/services/phases/squidrouter/route.ts b/src/services/phases/squidrouter/route.ts index d6f38289..345f6fd4 100644 --- a/src/services/phases/squidrouter/route.ts +++ b/src/services/phases/squidrouter/route.ts @@ -163,10 +163,10 @@ export async function testRoute( address: `0x${string}`, fromNetwork: Networks, ) { - const { fromChainId, toChainId, axlUSDC_MOONBEAM } = getSquidRouterConfig(fromNetwork); if (!isEvmInputTokenDetails(testingToken)) { return; } + const { fromChainId, toChainId, axlUSDC_MOONBEAM } = getSquidRouterConfig(fromNetwork); const sharedRouteParams: RouteParams = { fromAddress: address, From 61753fabee9e87c926e720efede523f04a495942 Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Fri, 13 Dec 2024 12:05:58 -0300 Subject: [PATCH 06/32] add more tokens with chain logos --- src/assets/coins/USDC_ARBITRUM.svg | 26 ++++++++++++++++++++++++++ src/assets/coins/USDC_AVALANCHE.svg | 24 ++++++++++++++++++++++++ src/assets/coins/USDC_BASE.svg | 22 ++++++++++++++++++++++ src/assets/coins/USDC_BSC.svg | 25 +++++++++++++++++++++++++ src/assets/coins/USDT_ARBITRUM.svg | 24 ++++++++++++++++++++++++ src/assets/coins/USDT_AVALANCHE.svg | 23 +++++++++++++++++++++++ src/assets/coins/USDT_BASE.svg | 24 ++++++++++++++++++++++++ src/assets/coins/USDT_BSC.svg | 24 ++++++++++++++++++++++++ src/assets/coins/USDT_ETHEREUM.svg | 28 ++++++++++++++++++++++++++++ src/constants/tokenConfig.ts | 20 ++++++++++---------- src/contexts/network.tsx | 1 - src/hooks/useGetAssetIcon.tsx | 25 +++++++++++++++++++++++++ src/hooks/useInputTokenBalance.ts | 1 - 13 files changed, 255 insertions(+), 12 deletions(-) create mode 100644 src/assets/coins/USDC_ARBITRUM.svg create mode 100644 src/assets/coins/USDC_AVALANCHE.svg create mode 100644 src/assets/coins/USDC_BASE.svg create mode 100644 src/assets/coins/USDC_BSC.svg create mode 100644 src/assets/coins/USDT_ARBITRUM.svg create mode 100644 src/assets/coins/USDT_AVALANCHE.svg create mode 100644 src/assets/coins/USDT_BASE.svg create mode 100644 src/assets/coins/USDT_BSC.svg create mode 100644 src/assets/coins/USDT_ETHEREUM.svg diff --git a/src/assets/coins/USDC_ARBITRUM.svg b/src/assets/coins/USDC_ARBITRUM.svg new file mode 100644 index 00000000..3ce39960 --- /dev/null +++ b/src/assets/coins/USDC_ARBITRUM.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/coins/USDC_AVALANCHE.svg b/src/assets/coins/USDC_AVALANCHE.svg new file mode 100644 index 00000000..3f463968 --- /dev/null +++ b/src/assets/coins/USDC_AVALANCHE.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/coins/USDC_BASE.svg b/src/assets/coins/USDC_BASE.svg new file mode 100644 index 00000000..51704966 --- /dev/null +++ b/src/assets/coins/USDC_BASE.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/coins/USDC_BSC.svg b/src/assets/coins/USDC_BSC.svg new file mode 100644 index 00000000..2f520d09 --- /dev/null +++ b/src/assets/coins/USDC_BSC.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/coins/USDT_ARBITRUM.svg b/src/assets/coins/USDT_ARBITRUM.svg new file mode 100644 index 00000000..a810783c --- /dev/null +++ b/src/assets/coins/USDT_ARBITRUM.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/coins/USDT_AVALANCHE.svg b/src/assets/coins/USDT_AVALANCHE.svg new file mode 100644 index 00000000..35d41f88 --- /dev/null +++ b/src/assets/coins/USDT_AVALANCHE.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/coins/USDT_BASE.svg b/src/assets/coins/USDT_BASE.svg new file mode 100644 index 00000000..43812da6 --- /dev/null +++ b/src/assets/coins/USDT_BASE.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/coins/USDT_BSC.svg b/src/assets/coins/USDT_BSC.svg new file mode 100644 index 00000000..84bf51e7 --- /dev/null +++ b/src/assets/coins/USDT_BSC.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/coins/USDT_ETHEREUM.svg b/src/assets/coins/USDT_ETHEREUM.svg new file mode 100644 index 00000000..9bd7909c --- /dev/null +++ b/src/assets/coins/USDT_ETHEREUM.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/constants/tokenConfig.ts b/src/constants/tokenConfig.ts index a15d76d7..083e8583 100644 --- a/src/constants/tokenConfig.ts +++ b/src/constants/tokenConfig.ts @@ -114,7 +114,7 @@ export const INPUT_TOKEN_CONFIG: Record { if (onNetworkChange) { onNetworkChange(network); } - console.log('setSelectedNetwork', network); setSelectedNetworkState(network); setSelectedNetworkLocalStorage(network); diff --git a/src/hooks/useGetAssetIcon.tsx b/src/hooks/useGetAssetIcon.tsx index c4ac6c5e..25e2999a 100644 --- a/src/hooks/useGetAssetIcon.tsx +++ b/src/hooks/useGetAssetIcon.tsx @@ -2,16 +2,41 @@ 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'; +import { i } from 'vite/dist/node/types.d-aGj9QkWt'; const ICONS = { eur: EUR, 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/useInputTokenBalance.ts b/src/hooks/useInputTokenBalance.ts index 99315149..6bd37af2 100644 --- a/src/hooks/useInputTokenBalance.ts +++ b/src/hooks/useInputTokenBalance.ts @@ -35,7 +35,6 @@ const useAssetHubBalance = (assetId?: number): string | undefined => { if (!walletAccount || !assetHubNode) return; if (!assetId) { - console.log('Invalid assetId', assetId); setBalance('0'); return; } From c355dd8da7597bd61f9ee874f4e78e773e110819 Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Wed, 18 Dec 2024 17:06:00 -0300 Subject: [PATCH 07/32] improvements to getChainId, network check --- src/contexts/network.tsx | 28 ++++++++++--------- .../useSEP24/useAnchorWindowHandler.ts | 1 - src/hooks/offramp/useSubmitOfframp.ts | 18 ++++++------ src/hooks/useVortexAccount.ts | 4 +-- 4 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/contexts/network.tsx b/src/contexts/network.tsx index 989d695a..ae91976a 100644 --- a/src/contexts/network.tsx +++ b/src/contexts/network.tsx @@ -1,9 +1,10 @@ import { createContext } from 'preact'; import { useContext, useState, useEffect, useCallback } from 'preact/hooks'; import { useSwitchChain } from 'wagmi'; - +import { polygon, bsc, arbitrum, base, avalanche, mainnet as ethereum } from '@reown/appkit/networks'; import { useLocalStorage, LocalStorageKeys } from '../hooks/useLocalStorage'; import { WALLETCONNECT_ASSETHUB_ID } from '../constants/constants'; +import { AssetHubChainId } from '../hooks/useVortexAccount'; export enum Networks { AssetHub = 'AssetHub', @@ -29,22 +30,24 @@ export function isNetworkEVM(network: Networks): boolean { } } -export function getNetworkId(network: Networks): number | undefined { +export function getNetworkId(network: Networks): number { switch (network) { case Networks.Polygon: - return 137; + return polygon.id; case Networks.Ethereum: - return 1; + return ethereum.id; case Networks.BSC: - return 56; + return bsc.id; case Networks.Arbitrum: - return 42161; + return arbitrum.id; case Networks.Base: - return 8453; + return base.id; case Networks.Avalanche: - return 43114; + return avalanche.id; + case Networks.AssetHub: + return AssetHubChainId; default: - return undefined; + throw new Error('getNetworkId: unsupported network'); } } @@ -89,10 +92,9 @@ export const NetworkProvider = ({ children }: NetworkProviderProps) => { setSelectedNetworkState(network); setSelectedNetworkLocalStorage(network); - // Will only switch chain on the EVM conneted wallet, if chain id has been defined in wagmi config. - const chain = chains.find((c) => c.id === getNetworkId(network)); - if (chain) { - switchChain({ chainId: chain.id }); + // Will only switch chain on the EVM conneted wallet case. + if (isNetworkEVM(network)) { + switchChain({ chainId: getNetworkId(network) }); } }, [switchChain, chains, setSelectedNetworkLocalStorage, onNetworkChange], diff --git a/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts b/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts index c3d889e2..383757a5 100644 --- a/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts +++ b/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts @@ -75,7 +75,6 @@ export const useAnchorWindowHandler = (sep24State: UseSEP24StateReturn, cleanupF executionInput, pendulumNode, trackKYCStarted, - cleanupFn, trackKYCCompleted, selectedNetwork, setOfframpStarted, diff --git a/src/hooks/offramp/useSubmitOfframp.ts b/src/hooks/offramp/useSubmitOfframp.ts index 10e6ffff..9287e47d 100644 --- a/src/hooks/offramp/useSubmitOfframp.ts +++ b/src/hooks/offramp/useSubmitOfframp.ts @@ -71,16 +71,9 @@ export const useSubmitOfframp = ({ }); try { - let chainId = getNetworkId(selectedNetwork); - if (!chainId && isNetworkEVM(selectedNetwork)) { - setInitializeFailed(); - setOfframpStarted(false); - setOfframpInitiating(false); - return; - } - + // For substrate, we only have AssetHub only now. Thus no need to change. if (isNetworkEVM(selectedNetwork)) { - await switchChainAsync({ chainId: chainId! }); + await switchChainAsync({ chainId: getNetworkId(selectedNetwork) }); } setOfframpStarted(true); @@ -146,7 +139,12 @@ export const useSubmitOfframp = ({ } } catch (error) { console.error('Error initializing the offramping process', error); - setInitializeFailed(); + // 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); } diff --git a/src/hooks/useVortexAccount.ts b/src/hooks/useVortexAccount.ts index 710eb250..06929b56 100644 --- a/src/hooks/useVortexAccount.ts +++ b/src/hooks/useVortexAccount.ts @@ -1,4 +1,4 @@ -import { isNetworkEVM, Networks, useNetwork } from '../contexts/network'; +import { isNetworkEVM, useNetwork } from '../contexts/network'; import { useMemo, useCallback } from 'preact/compat'; import { usePolkadotWalletState } from '../contexts/polkadotWallet'; import { useAccount } from 'wagmi'; @@ -7,7 +7,7 @@ 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; +export const AssetHubChainId = -1; // A helper hook to provide an abstraction over the account used. // The account could be an EVM account or a Polkadot account. From 1f87c8355f799146c946f9ffafca642185d626bd Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Wed, 18 Dec 2024 17:16:10 -0300 Subject: [PATCH 08/32] remove helper swapConfirm --- .../calculateSwapAmountsWithMargin.ts | 25 ----- src/pages/swap/helpers/swapConfirm/index.ts | 103 ------------------ .../swapConfirm/performSwapInitialChecks.ts | 27 ----- .../helpers/swapConfirm/validateSwapInputs.ts | 28 ----- src/pages/swap/index.tsx | 14 +-- 5 files changed, 5 insertions(+), 192 deletions(-) delete mode 100644 src/pages/swap/helpers/swapConfirm/calculateSwapAmountsWithMargin.ts delete mode 100644 src/pages/swap/helpers/swapConfirm/index.ts delete mode 100644 src/pages/swap/helpers/swapConfirm/performSwapInitialChecks.ts delete mode 100644 src/pages/swap/helpers/swapConfirm/validateSwapInputs.ts diff --git a/src/pages/swap/helpers/swapConfirm/calculateSwapAmountsWithMargin.ts b/src/pages/swap/helpers/swapConfirm/calculateSwapAmountsWithMargin.ts deleted file mode 100644 index 51b28bdf..00000000 --- a/src/pages/swap/helpers/swapConfirm/calculateSwapAmountsWithMargin.ts +++ /dev/null @@ -1,25 +0,0 @@ -import Big from 'big.js'; - -import { ContractBalance, multiplyByPowerOfTen } from '../../../../helpers/contracts'; -import { InputTokenDetails, OutputTokenDetails } from '../../../../constants/tokenConfig'; -import { SPACEWALK_REDEEM_SAFETY_MARGIN } from '../../../../constants/constants'; - -export const calculateSwapAmountsWithMargin = ( - fromAmount: Big, - preciseQuotedAmountOut: ContractBalance, - inputToken: InputTokenDetails, - outputToken: OutputTokenDetails, -) => { - // Calculate output amount with margin - const outputAmountBigMargin = preciseQuotedAmountOut.preciseBigDecimal - .round(2, 0) - .mul(1 + SPACEWALK_REDEEM_SAFETY_MARGIN); - const expectedRedeemAmountRaw = multiplyByPowerOfTen(outputAmountBigMargin, outputToken.decimals).toFixed(); - - // Calculate input amount with margin - const inputAmountBig = Big(fromAmount); - const inputAmountBigMargin = inputAmountBig.mul(1 + SPACEWALK_REDEEM_SAFETY_MARGIN); - const inputAmountRaw = multiplyByPowerOfTen(inputAmountBigMargin, inputToken.decimals).toFixed(); - - return { expectedRedeemAmountRaw, inputAmountRaw }; -}; diff --git a/src/pages/swap/helpers/swapConfirm/index.ts b/src/pages/swap/helpers/swapConfirm/index.ts deleted file mode 100644 index 547c363b..00000000 --- a/src/pages/swap/helpers/swapConfirm/index.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { StateUpdater } from 'preact/hooks'; -import { ApiPromise } from '@polkadot/api'; -import Big from 'big.js'; - -import { - getInputTokenDetailsOrDefault, - InputTokenType, - OutputTokenType, - OUTPUT_TOKEN_CONFIG, -} from '../../../../constants/tokenConfig'; - -import { ExecutionInput } from '../../../../hooks/offramp/useMainProcess'; -import { TokenOutData } from '../../../../hooks/nabla/useTokenAmountOut'; -import { Networks } from '../../../../contexts/network'; - -import { calculateSwapAmountsWithMargin } from './calculateSwapAmountsWithMargin'; -import { performSwapInitialChecks } from './performSwapInitialChecks'; -import { validateSwapInputs } from './validateSwapInputs'; - -interface SwapConfirmParams { - address: string | undefined; - api: ApiPromise | null; - from: InputTokenType; - fromAmount: Big | undefined; - fromAmountString: string; - handleOnSubmit: (executionInput: ExecutionInput) => void; - inputAmountIsStable: boolean; - requiresSquidRouter: boolean; - selectedNetwork: Networks; - setInitializeFailed: (message?: string | null | undefined) => void; - setIsInitiating: StateUpdater; - setTermsAccepted: (accepted: boolean) => void; - to: OutputTokenType; - tokenOutAmount: { data: TokenOutData | undefined }; -} - -export function swapConfirm(e: Event, params: SwapConfirmParams) { - e.preventDefault(); - - const { - address, - api, - from, - fromAmount, - fromAmountString, - handleOnSubmit, - inputAmountIsStable, - requiresSquidRouter, - selectedNetwork, - setInitializeFailed, - setIsInitiating, - setTermsAccepted, - to, - tokenOutAmount, - } = params; - - const validInputs = validateSwapInputs(inputAmountIsStable, address, fromAmount, tokenOutAmount.data); - if (!validInputs) { - return; - } - - setIsInitiating(true); - - const outputToken = OUTPUT_TOKEN_CONFIG[to]; - const inputToken = getInputTokenDetailsOrDefault(selectedNetwork, from); - - const { expectedRedeemAmountRaw, inputAmountRaw } = calculateSwapAmountsWithMargin( - validInputs.fromAmount, - validInputs.tokenOutAmountData.preciseQuotedAmountOut, - inputToken, - outputToken, - ); - - performSwapInitialChecks( - api!, - outputToken, - inputToken, - expectedRedeemAmountRaw, - inputAmountRaw, - address!, - requiresSquidRouter, - selectedNetwork, - ) - .then(() => { - console.log('Initial checks completed. Starting process..'); - - // here we should set that the user has accepted the terms and conditions in the local storage - setTermsAccepted(true); - - handleOnSubmit({ - inputTokenType: from, - outputTokenType: to, - amountInUnits: fromAmountString, - offrampAmount: validInputs.tokenOutAmountData.roundedDownQuotedAmountOut, - setInitializeFailed, - }); - }) - .catch((_error) => { - console.error('Error during swap confirmation:', _error); - setIsInitiating(false); - setInitializeFailed(); - }); -} diff --git a/src/pages/swap/helpers/swapConfirm/performSwapInitialChecks.ts b/src/pages/swap/helpers/swapConfirm/performSwapInitialChecks.ts deleted file mode 100644 index fa6749c1..00000000 --- a/src/pages/swap/helpers/swapConfirm/performSwapInitialChecks.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ApiPromise } from '@polkadot/api'; - -import { InputTokenDetails, OutputTokenDetails } from '../../../../constants/tokenConfig'; -import { getVaultsForCurrency } from '../../../../services/phases/polkadot/spacewalk'; -import { testRoute } from '../../../../services/phases/squidrouter/route'; -import { Networks } from '../../../../contexts/network'; - -export const performSwapInitialChecks = async ( - api: ApiPromise, - outputToken: OutputTokenDetails, - fromToken: InputTokenDetails, - expectedRedeemAmountRaw: string, - inputAmountRaw: string, - address: string, - requiresSquidRouter: boolean, - network: Networks, -) => { - await Promise.all([ - getVaultsForCurrency( - api, - outputToken.stellarAsset.code.hex, - outputToken.stellarAsset.issuer.hex, - expectedRedeemAmountRaw, - ), - requiresSquidRouter ? testRoute(fromToken, inputAmountRaw, address, network) : Promise.resolve(), - ]); -}; diff --git a/src/pages/swap/helpers/swapConfirm/validateSwapInputs.ts b/src/pages/swap/helpers/swapConfirm/validateSwapInputs.ts deleted file mode 100644 index 4e423cc6..00000000 --- a/src/pages/swap/helpers/swapConfirm/validateSwapInputs.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { TokenOutData } from '../../../../hooks/nabla/useTokenAmountOut'; - -type ValidSwapInputs = { - inputAmountIsStable: true; - address: string; - fromAmount: Big; - tokenOutAmountData: TokenOutData; -}; - -export const validateSwapInputs = ( - inputAmountIsStable: boolean, - address: string | undefined, - fromAmount: Big | undefined, - tokenOutAmountData: TokenOutData | undefined, -): ValidSwapInputs | false => { - if (!inputAmountIsStable) return false; - if (!address) return false; - if (fromAmount === undefined) { - console.log('Input amount is undefined'); - return false; - } - if (!tokenOutAmountData) { - console.log('Output amount is undefined'); - return false; - } - - return { inputAmountIsStable, address, fromAmount, tokenOutAmountData }; -}; diff --git a/src/pages/swap/index.tsx b/src/pages/swap/index.tsx index be1181fd..194c4e2a 100644 --- a/src/pages/swap/index.tsx +++ b/src/pages/swap/index.tsx @@ -104,15 +104,11 @@ export const SwapPage = () => { initialize(); }, []); - // Define the function using useCallback - const setInitializeFailed = useCallback( - (message?: string | null) => { - setInitializeFailedMessage( - message ?? 'Application initialization failed. Please reload, or try again later if the problem persists.', - ); - }, - [], // No dependencies, so the function remains stable - ); + const setInitializeFailed = useCallback((message?: string | null) => { + setInitializeFailedMessage( + message ?? 'Application initialization failed. Please reload, or try again later if the problem persists.', + ); + }, []); // Main process hook const { From 26ed1b51b605270e0bacfeb133054fbda9f63182 Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Thu, 19 Dec 2024 11:03:38 -0300 Subject: [PATCH 09/32] sep24 hooks refactor into zustand state --- src/hooks/offramp/useMainProcess.ts | 24 ++++--- src/hooks/offramp/useSEP24/index.ts | 22 ------ .../useSEP24/useAnchorWindowHandler.ts | 9 +-- src/hooks/offramp/useSEP24/useSEP24Cleanup.ts | 17 ----- src/hooks/offramp/useSEP24/useSEP24State.ts | 39 ----------- src/hooks/offramp/useSubmitOfframp.ts | 42 +++--------- src/pages/swap/index.tsx | 1 + src/services/anchor/index.ts | 6 -- src/stores/sep24Store.ts | 67 +++++++++++++++++++ 9 files changed, 95 insertions(+), 132 deletions(-) delete mode 100644 src/hooks/offramp/useSEP24/index.ts delete mode 100644 src/hooks/offramp/useSEP24/useSEP24Cleanup.ts delete mode 100644 src/hooks/offramp/useSEP24/useSEP24State.ts create mode 100644 src/stores/sep24Store.ts diff --git a/src/hooks/offramp/useMainProcess.ts b/src/hooks/offramp/useMainProcess.ts index 762a9790..02d1c494 100644 --- a/src/hooks/offramp/useMainProcess.ts +++ b/src/hooks/offramp/useMainProcess.ts @@ -5,12 +5,13 @@ 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'; - +import { useSep24Store } from '../../stores/sep24Store'; +import { useSep24Actions } from '../../stores/sep24Store'; +import { useAnchorWindowHandler } from './useSEP24/useAnchorWindowHandler'; export type SigningPhase = 'started' | 'approved' | 'signed' | 'finished'; export interface ExecutionInput { @@ -23,15 +24,18 @@ export interface ExecutionInput { export const useMainProcess = () => { const { updateOfframpHookStateFromState, resetOfframpState, setOfframpStarted } = useOfframpActions(); - const offrampState = useOfframpState(); + // Sep 24 states + const { firstSep24Response, firstSep24Interval } = useSep24Store.getState(); + const { cleanupSep24State } = useSep24Actions(); + // Contexts const { setOnSelectedNetworkChange } = useNetwork(); // Custom hooks const events = useOfframpEvents(); - const sep24 = useSEP24(); + const handleOnAnchorWindowOpen = useAnchorWindowHandler(); // Initialize state from storage useEffect(() => { @@ -51,10 +55,8 @@ export const useMainProcess = () => { }); return { - handleOnSubmit: useSubmitOfframp({ - ...sep24, - }), - firstSep24ResponseState: sep24.firstSep24Response, + handleOnSubmit: useSubmitOfframp(), + firstSep24ResponseState: firstSep24Response, finishOfframping: () => { events.resetUniqueEvents(); resetOfframpState(); @@ -62,12 +64,12 @@ export const useMainProcess = () => { continueFailedFlow: () => { updateOfframpHookStateFromState(recoverFromFailure(offrampState)); }, - handleOnAnchorWindowOpen: sep24.handleOnAnchorWindowOpen, + handleOnAnchorWindowOpen: handleOnAnchorWindowOpen, // @todo: why do we need this? maybeCancelSep24First: () => { - if (sep24.firstSep24IntervalRef.current !== undefined) { + if (firstSep24Interval !== undefined) { setOfframpStarted(false); - sep24.cleanSep24FirstVariables(); + cleanupSep24State(); } }, }; diff --git a/src/hooks/offramp/useSEP24/index.ts b/src/hooks/offramp/useSEP24/index.ts deleted file mode 100644 index 9f139a00..00000000 --- a/src/hooks/offramp/useSEP24/index.ts +++ /dev/null @@ -1,22 +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 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 383757a5..b3b3c386 100644 --- a/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts +++ b/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts @@ -8,10 +8,10 @@ import { sep24Second } from '../../../services/anchor'; 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 { useSep24Store, useSep24Actions } from '../../../stores/sep24Store'; const handleAmountMismatch = (setOfframpingStarted: (started: boolean) => void): void => { setOfframpingStarted(false); @@ -23,13 +23,14 @@ 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 { firstSep24Response, anchorSessionParams, executionInput } = useSep24Store.getState(); + const { cleanupSep24State } = useSep24Actions(); return useCallback(async () => { if (!firstSep24Response || !anchorSessionParams || !executionInput) { @@ -42,7 +43,7 @@ export const useAnchorWindowHandler = (sep24State: UseSEP24StateReturn, cleanupF } trackKYCStarted(executionInput, selectedNetwork); - cleanupFn(); + cleanupSep24State(); try { const secondSep24Response = await sep24Second(firstSep24Response, anchorSessionParams); 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 bf060e72..00000000 --- a/src/hooks/offramp/useSEP24/useSEP24State.ts +++ /dev/null @@ -1,39 +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'; - -import { ExecutionInput } from '../useMainProcess'; - -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/useSubmitOfframp.ts b/src/hooks/offramp/useSubmitOfframp.ts index 9287e47d..8109908a 100644 --- a/src/hooks/offramp/useSubmitOfframp.ts +++ b/src/hooks/offramp/useSubmitOfframp.ts @@ -8,34 +8,13 @@ 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, sep10, sep24First } from '../../services/anchor'; import { useOfframpActions, useOfframpStarted, useOfframpState } from '../../stores/offrampStore'; -import { ExtendedExecutionInput } from './useSEP24/useSEP24State'; 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 { switchChainAsync, switchChain } = useSwitchChain(); const { trackEvent } = useEventsContext(); @@ -44,10 +23,8 @@ export const useSubmitOfframp = ({ const offrampStarted = useOfframpStarted(); const offrampState = useOfframpState(); const { setOfframpStarted, setOfframpInitiating } = useOfframpActions(); - - const addEvent = (message: string, status: string) => { - console.log('Add event', message, status); - }; + const { setAnchorSessionParams, setFirstSep24Response, setExecutionInput, cleanupSep24State, setFirstSep24Interval } = + useSep24Actions(); return useCallback( (executionInput: ExecutionInput) => { @@ -101,7 +78,6 @@ export const useSubmitOfframp = ({ address, checkAndWaitForSignature, forceRefreshAndWaitForSignature, - addEvent, ); const anchorSessionParams = { @@ -125,7 +101,7 @@ export const useSubmitOfframp = ({ setFirstSep24Response(firstSep24Response); }; - firstSep24IntervalRef.current = window.setInterval(fetchAndUpdateSep24Url, 20000); + setFirstSep24Interval(window.setInterval(fetchAndUpdateSep24Url, 20000)); try { await fetchAndUpdateSep24Url(); @@ -133,7 +109,7 @@ export const useSubmitOfframp = ({ console.error('Error finalizing the initial state of the offramping process', error); setInitializeFailed(); setOfframpStarted(false); - cleanSep24FirstVariables(); + cleanupSep24State(); } finally { setOfframpInitiating(false); } @@ -163,9 +139,9 @@ export const useSubmitOfframp = ({ forceRefreshAndWaitForSignature, setExecutionInput, setAnchorSessionParams, - firstSep24IntervalRef, setFirstSep24Response, - cleanSep24FirstVariables, + setFirstSep24Interval, + cleanupSep24State, ], ); }; diff --git a/src/pages/swap/index.tsx b/src/pages/swap/index.tsx index 194c4e2a..89bd4e7c 100644 --- a/src/pages/swap/index.tsx +++ b/src/pages/swap/index.tsx @@ -104,6 +104,7 @@ export const SwapPage = () => { initialize(); }, []); + // Maybe go into a state of UI errors?? const setInitializeFailed = useCallback((message?: string | null) => { setInitializeFailedMessage( message ?? 'Application initialization failed. Please reload, or try again later if the problem persists.', diff --git a/src/services/anchor/index.ts b/src/services/anchor/index.ts index 69ac23cd..59973b03 100644 --- a/src/services/anchor/index.ts +++ b/src/services/anchor/index.ts @@ -145,7 +145,6 @@ export const sep10 = async ( address: string, checkAndWaitForSignature: () => Promise, forceRefreshAndWaitForSignature: () => Promise, - renderEvent: (event: string, status: EventStatus) => void, ): Promise<{ token: string; sep10Account: string }> => { const { signingKey, webAuthEndpoint } = tomlValues; @@ -215,11 +214,6 @@ export const sep10 = async ( } const { token } = await jwt.json(); - // print the ephemeral secret, for testing - renderEvent( - `Unique recovery code (Please keep safe in case something fails): ${ephemeralKeys.secret()}`, - EventStatus.Waiting, - ); return { token, sep10Account }; }; diff --git a/src/stores/sep24Store.ts b/src/stores/sep24Store.ts new file mode 100644 index 00000000..6a660e60 --- /dev/null +++ b/src/stores/sep24Store.ts @@ -0,0 +1,67 @@ +import { create } from 'zustand'; +import { IAnchorSessionParams, ISep24Intermediate } from '../services/anchor'; +import { ExecutionInput } from '../hooks/offramp/useMainProcess'; + +export type ExtendedExecutionInput = ExecutionInput & { stellarEphemeralSecret: string }; + +export interface Sep24State { + anchorSessionParams: IAnchorSessionParams | undefined; + firstSep24Response: ISep24Intermediate | undefined; + executionInput: ExtendedExecutionInput | undefined; + firstSep24Interval: number | undefined; +} + +export interface Sep24Actions { + setAnchorSessionParams: (params: IAnchorSessionParams | undefined) => void; + setFirstSep24Response: (response: ISep24Intermediate | undefined) => void; + setExecutionInput: (input: ExtendedExecutionInput | undefined) => void; + setFirstSep24Interval: (interval: number | undefined) => void; + resetSep24State: () => void; + cleanupSep24State: () => void; +} + +interface Sep24Store extends Sep24State { + actions: Sep24Actions; +} + +export const useSep24Store = create()((set, get) => ({ + // Initial state + anchorSessionParams: undefined, + firstSep24Response: undefined, + executionInput: undefined, + firstSep24Interval: undefined, + + actions: { + // Setters + setAnchorSessionParams: (params) => set({ anchorSessionParams: params }), + setFirstSep24Response: (response) => set({ firstSep24Response: response }), + setExecutionInput: (input) => set({ executionInput: input }), + setFirstSep24Interval: (interval) => set({ firstSep24Interval: interval }), + + // Reset all state + resetSep24State: () => + set({ + anchorSessionParams: undefined, + firstSep24Response: undefined, + executionInput: undefined, + firstSep24Interval: undefined, + }), + + // Cleanup action + cleanupSep24State: () => { + const { firstSep24Interval } = get(); + const actions = get().actions; + + if (firstSep24Interval !== undefined) { + clearInterval(firstSep24Interval); + actions.setFirstSep24Interval(undefined); + actions.setFirstSep24Response(undefined); + actions.setExecutionInput(undefined); + actions.setAnchorSessionParams(undefined); + } + }, + }, +})); + +// Selector hooks +export const useSep24Actions = () => useSep24Store((state) => state.actions); From 96f336f194e6e7a263de63ebf43c0f5667984bf8 Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Thu, 19 Dec 2024 14:27:34 -0300 Subject: [PATCH 10/32] fixes to offramping state manager --- src/contexts/network.tsx | 13 +++++-------- src/hooks/offramp/useMainProcess.ts | 13 ++----------- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/src/contexts/network.tsx b/src/contexts/network.tsx index ae91976a..41663213 100644 --- a/src/contexts/network.tsx +++ b/src/contexts/network.tsx @@ -5,6 +5,7 @@ import { polygon, bsc, arbitrum, base, avalanche, mainnet as ethereum } from '@r import { useLocalStorage, LocalStorageKeys } from '../hooks/useLocalStorage'; import { WALLETCONNECT_ASSETHUB_ID } from '../constants/constants'; import { AssetHubChainId } from '../hooks/useVortexAccount'; +import { useOfframpActions } from '../stores/offrampStore'; export enum Networks { AssetHub = 'AssetHub', @@ -55,7 +56,6 @@ interface NetworkContextType { walletConnectPolkadotSelectedNetworkId: string; selectedNetwork: Networks; setSelectedNetwork: (network: Networks) => void; - setOnSelectedNetworkChange: (callback: (network: Networks) => void) => void; networkSelectorDisabled: boolean; setNetworkSelectorDisabled: (disabled: boolean) => void; } @@ -64,7 +64,6 @@ const NetworkContext = createContext({ walletConnectPolkadotSelectedNetworkId: WALLETCONNECT_ASSETHUB_ID, selectedNetwork: Networks.AssetHub, setSelectedNetwork: () => null, - setOnSelectedNetworkChange: () => null, networkSelectorDisabled: false, setNetworkSelectorDisabled: () => null, }); @@ -80,15 +79,14 @@ export const NetworkProvider = ({ children }: NetworkProviderProps) => { }); const [selectedNetwork, setSelectedNetworkState] = useState(selectedNetworkLocalStorage); - const [onNetworkChange, setOnSelectedNetworkChange] = useState<((network: Networks) => void) | undefined>(); const [networkSelectorDisabled, setNetworkSelectorDisabled] = useState(false); + + const { resetOfframpState } = useOfframpActions(); const { chains, switchChain } = useSwitchChain(); const setSelectedNetwork = useCallback( (network: Networks) => { - if (onNetworkChange) { - onNetworkChange(network); - } + resetOfframpState(); setSelectedNetworkState(network); setSelectedNetworkLocalStorage(network); @@ -97,7 +95,7 @@ export const NetworkProvider = ({ children }: NetworkProviderProps) => { switchChain({ chainId: getNetworkId(network) }); } }, - [switchChain, chains, setSelectedNetworkLocalStorage, onNetworkChange], + [switchChain, chains, setSelectedNetworkLocalStorage], ); // Only run on first render @@ -122,7 +120,6 @@ export const NetworkProvider = ({ children }: NetworkProviderProps) => { setSelectedNetwork, networkSelectorDisabled, setNetworkSelectorDisabled, - setOnSelectedNetworkChange, }} > {children} diff --git a/src/hooks/offramp/useMainProcess.ts b/src/hooks/offramp/useMainProcess.ts index 02d1c494..a6e76496 100644 --- a/src/hooks/offramp/useMainProcess.ts +++ b/src/hooks/offramp/useMainProcess.ts @@ -1,8 +1,7 @@ -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 { useSubmitOfframp } from './useSubmitOfframp'; @@ -30,9 +29,6 @@ export const useMainProcess = () => { const { firstSep24Response, firstSep24Interval } = useSep24Store.getState(); const { cleanupSep24State } = useSep24Actions(); - // Contexts - const { setOnSelectedNetworkChange } = useNetwork(); - // Custom hooks const events = useOfframpEvents(); const handleOnAnchorWindowOpen = useAnchorWindowHandler(); @@ -42,12 +38,7 @@ export const useMainProcess = () => { const recoveryState = readCurrentState(); updateOfframpHookStateFromState(recoveryState); events.trackOfframpingEvent(recoveryState); - }, [updateOfframpHookStateFromState, events]); - - // Reset offramping state when the network is changed - useEffect(() => { - setOnSelectedNetworkChange(resetOfframpState); - }, [setOnSelectedNetworkChange, resetOfframpState]); + }, [updateOfframpHookStateFromState, events.trackOfframpingEvent]); // Determines the current offramping phase useOfframpAdvancement({ From 47caca76dc982e4a3eb20e009333ab6f318460a5 Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Tue, 24 Dec 2024 09:47:10 -0300 Subject: [PATCH 11/32] merged current staging --- postcss.config.cjs => postcss.config.js | 2 +- .../googleSpreadSheet.controller.js | 7 ++- .../src/api/controllers/storage.controller.js | 36 ++++++++--- .../src/api/middlewares/validators.js | 21 ++++++- .../src/api/services/spreadsheet.service.js | 41 ++++++++----- src/assets/SocialsGithub.tsx | 24 ++++++++ src/assets/SocialsTelegram.tsx | 19 ++++++ src/assets/SocialsX.tsx | 24 ++++++++ src/assets/logo/satoshipay.svg | 22 +++++++ src/components/Footer/index.tsx | 56 ++++++++++++++++++ src/components/Navbar/index.tsx | 2 +- src/components/NetworkSelector/index.tsx | 4 +- src/components/PoweredBy/index.tsx | 12 ++++ src/constants/constants.ts | 1 + src/hooks/offramp/useMainProcess.ts | 10 +++- .../useSEP24/useAnchorWindowHandler.ts | 3 + src/layouts/index.tsx | 2 + src/pages/swap/index.tsx | 19 +++--- src/pages/swap/useSwapUrlParams.ts | 27 +++++++++ src/services/anchor/index.ts | 2 + src/services/offrampingFlow.ts | 4 ++ src/services/phases/polkadot/spacewalk.tsx | 1 - src/services/phases/signedTransactions.ts | 7 +-- src/services/phases/stellar/index.tsx | 59 ++++++++++++++++--- src/services/storage/remote.ts | 2 +- tailwind.config.js | 2 + 26 files changed, 353 insertions(+), 56 deletions(-) rename postcss.config.cjs => postcss.config.js (86%) create mode 100644 src/assets/SocialsGithub.tsx create mode 100644 src/assets/SocialsTelegram.tsx create mode 100644 src/assets/SocialsX.tsx create mode 100644 src/assets/logo/satoshipay.svg create mode 100644 src/components/Footer/index.tsx create mode 100644 src/components/PoweredBy/index.tsx create mode 100644 src/pages/swap/useSwapUrlParams.ts diff --git a/postcss.config.cjs b/postcss.config.js similarity index 86% rename from postcss.config.cjs rename to postcss.config.js index e569373f..feab3515 100644 --- a/postcss.config.cjs +++ b/postcss.config.js @@ -1,4 +1,4 @@ -module.exports = { +export default { plugins: { 'postcss-import': {}, 'tailwindcss/nesting': {}, diff --git a/signer-service/src/api/controllers/googleSpreadSheet.controller.js b/signer-service/src/api/controllers/googleSpreadSheet.controller.js index 2c005bd2..acb2a623 100644 --- a/signer-service/src/api/controllers/googleSpreadSheet.controller.js +++ b/signer-service/src/api/controllers/googleSpreadSheet.controller.js @@ -3,7 +3,7 @@ require('dotenv').config(); const { spreadsheet } = require('../../config/vars'); const { initGoogleSpreadsheet, getOrCreateSheet, appendData } = require('../services/spreadsheet.service'); -exports.storeDataInGoogleSpreadsheet = async (req, res, spreadsheetId, sheetHeaderValues) => { +async function storeDataInGoogleSpreadsheet(req, res, spreadsheetId, sheetHeaderValues) { try { // We expect the data to be an object that matches our schema const data = req.body; @@ -12,7 +12,6 @@ exports.storeDataInGoogleSpreadsheet = async (req, res, spreadsheetId, sheetHead const sheet = await initGoogleSpreadsheet(spreadsheetId, spreadsheet.googleCredentials).then((doc) => getOrCreateSheet(doc, sheetHeaderValues), ); - if (sheet) { console.log('Appending data to sheet'); await appendData(sheet, data); @@ -24,4 +23,6 @@ exports.storeDataInGoogleSpreadsheet = async (req, res, spreadsheetId, sheetHead console.error('Error in storeData:', error); return res.status(500).json({ error: 'Failed to store data', details: error.message }); } -}; +} + +module.exports = { storeDataInGoogleSpreadsheet }; diff --git a/signer-service/src/api/controllers/storage.controller.js b/signer-service/src/api/controllers/storage.controller.js index ae8338b7..1b9310c6 100644 --- a/signer-service/src/api/controllers/storage.controller.js +++ b/signer-service/src/api/controllers/storage.controller.js @@ -1,10 +1,10 @@ const { spreadsheet } = require('../../config/vars'); -const { storeDataInGoogleSpreadsheet } = require('./googleSpreadSheet.controller'); +const { storeDataInGoogleSpreadsheet } = require('./googleSpreadSheet.controller.js'); -// These are the headers for the Google Spreadsheet -const DUMP_SHEET_HEADER_VALUES = [ +// These are the headers for the Google Spreadsheet for polygon offramp +const DUMP_SHEET_HEADER_VALUES_EVM = [ 'timestamp', - 'polygonAddress', + 'offramperAddress', 'stellarEphemeralPublicKey', 'pendulumEphemeralPublicKey', 'nablaApprovalTx', @@ -20,7 +20,29 @@ const DUMP_SHEET_HEADER_VALUES = [ 'squidRouterReceiverHash', ]; -exports.DUMP_SHEET_HEADER_VALUES = DUMP_SHEET_HEADER_VALUES; +const DUMP_SHEET_HEADER_VALUES_ASSETHUB = [ + 'timestamp', + 'offramperAddress', + 'stellarEphemeralPublicKey', + 'pendulumEphemeralPublicKey', + 'nablaApprovalTx', + 'nablaSwapTx', + 'spacewalkRedeemTx', + 'stellarOfframpTx', + 'stellarCleanupTx', + 'inputAmount', + 'inputTokenType', + 'outputAmount', + 'outputTokenType', +]; +exports.DUMP_SHEET_HEADER_VALUES_ASSETHUB = DUMP_SHEET_HEADER_VALUES_ASSETHUB; +exports.DUMP_SHEET_HEADER_VALUES_EVM = DUMP_SHEET_HEADER_VALUES_EVM; + +exports.storeData = async (req, res) => { + const sheetHeaderValues = req.body.offramperAddress.includes('0x') + ? DUMP_SHEET_HEADER_VALUES_EVM + : DUMP_SHEET_HEADER_VALUES_ASSETHUB; + console.log(sheetHeaderValues); -exports.storeData = async (req, res) => - storeDataInGoogleSpreadsheet(req, res, spreadsheet.storageSheetId, DUMP_SHEET_HEADER_VALUES); + storeDataInGoogleSpreadsheet(req, res, spreadsheet.storageSheetId, sheetHeaderValues); +}; diff --git a/signer-service/src/api/middlewares/validators.js b/signer-service/src/api/middlewares/validators.js index c6782505..51d876a7 100644 --- a/signer-service/src/api/middlewares/validators.js +++ b/signer-service/src/api/middlewares/validators.js @@ -1,7 +1,10 @@ const { TOKEN_CONFIG } = require('../../constants/tokenConfig'); -const { DUMP_SHEET_HEADER_VALUES } = require('../controllers/storage.controller'); const { EMAIL_SHEET_HEADER_VALUES } = require('../controllers/email.controller'); const { RATING_SHEET_HEADER_VALUES } = require('../controllers/rating.controller'); +const { + DUMP_SHEET_HEADER_VALUES_ASSETHUB, + DUMP_SHEET_HEADER_VALUES_EVM, +} = require('../controllers/storage.controller'); const { SUPPORTED_PROVIDERS, SUPPORTED_CRYPTO_CURRENCIES, @@ -84,6 +87,20 @@ const validateChangeOpInput = (req, res, next) => { next(); }; +const validateRequestBodyValuesForTransactionStore = () => (req, res, next) => { + const data = req.body; + const offramperAddress = data.offramperAddress; + if (!offramperAddress) { + return res.status(400).json({ error: 'Missing offramperAddress parameter' }); + } + + const requiredRequestBodyKeys = offramperAddress.includes('0x') + ? DUMP_SHEET_HEADER_VALUES_EVM + : DUMP_SHEET_HEADER_VALUES_ASSETHUB; + + validateRequestBodyValues(requiredRequestBodyKeys)(req, res, next); +}; + const validateRequestBodyValues = (requiredRequestBodyKeys) => (req, res, next) => { const data = req.body; @@ -97,7 +114,7 @@ const validateRequestBodyValues = (requiredRequestBodyKeys) => (req, res, next) next(); }; -const validateStorageInput = validateRequestBodyValues(DUMP_SHEET_HEADER_VALUES); +const validateStorageInput = validateRequestBodyValuesForTransactionStore(); const validateEmailInput = validateRequestBodyValues(EMAIL_SHEET_HEADER_VALUES); const validateRatingInput = validateRequestBodyValues(RATING_SHEET_HEADER_VALUES); const validateExecuteXCM = validateRequestBodyValues(['id', 'payload']); diff --git a/signer-service/src/api/services/spreadsheet.service.js b/signer-service/src/api/services/spreadsheet.service.js index 2f2b0daa..3c3c0597 100644 --- a/signer-service/src/api/services/spreadsheet.service.js +++ b/signer-service/src/api/services/spreadsheet.service.js @@ -31,26 +31,37 @@ exports.initGoogleSpreadsheet = async (sheetId, googleCredentials) => { // doc: GoogleSpreadsheet, headerValues: string[] exports.getOrCreateSheet = async (doc, headerValues) => { - let sheet = doc.sheetsByIndex[0]; + let matchingSheet = null; + try { - await sheet.loadHeaderRow(); - const sheetHeaders = sheet.headerValues; - - // Compare the header values to the expected header values - if ( - sheetHeaders.length !== headerValues.length && - sheetHeaders.every((value, index) => value === headerValues[index]) - ) { - // Create a new sheet if the headers don't match - console.log('Creating new sheet'); - sheet = await doc.addSheet({ headerValues }); + for (let i = 0; i < Math.min(doc.sheetsByIndex.length, 10); i++) { + const sheet = doc.sheetsByIndex[i]; + try { + await sheet.loadHeaderRow(); + const sheetHeaders = sheet.headerValues; + + if ( + sheetHeaders.length === headerValues.length && + sheetHeaders.every((value, index) => value === headerValues[index]) + ) { + matchingSheet = sheet; + break; + } + } catch (error) { + continue; + } + } + + if (!matchingSheet) { + console.log(' Creating a new sheet.'); + matchingSheet = await doc.addSheet({ headerValues }); } } catch (error) { - // Assume the error is due to the sheet not having any rows - await sheet.setHeaderRow(headerValues); + console.error('Error iterating sheets:', error.message); + throw error; } - return sheet; + return matchingSheet; }; // sheet: GoogleSpreadsheetWorksheet, data: Record diff --git a/src/assets/SocialsGithub.tsx b/src/assets/SocialsGithub.tsx new file mode 100644 index 00000000..95c90bcb --- /dev/null +++ b/src/assets/SocialsGithub.tsx @@ -0,0 +1,24 @@ +export function Github({ className }: { className?: string }) { + return ( + + + + + + + + + + + ); +} diff --git a/src/assets/SocialsTelegram.tsx b/src/assets/SocialsTelegram.tsx new file mode 100644 index 00000000..2c36bd48 --- /dev/null +++ b/src/assets/SocialsTelegram.tsx @@ -0,0 +1,19 @@ +export function Telegram({ className }: { className?: string }) { + return ( + + + + ); +} diff --git a/src/assets/SocialsX.tsx b/src/assets/SocialsX.tsx new file mode 100644 index 00000000..8ac01463 --- /dev/null +++ b/src/assets/SocialsX.tsx @@ -0,0 +1,24 @@ +export function X({ className }: { className?: string }) { + return ( + + + + + + + + + + + ); +} diff --git a/src/assets/logo/satoshipay.svg b/src/assets/logo/satoshipay.svg new file mode 100644 index 00000000..ae85e9fa --- /dev/null +++ b/src/assets/logo/satoshipay.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/Footer/index.tsx b/src/components/Footer/index.tsx new file mode 100644 index 00000000..25ba776c --- /dev/null +++ b/src/components/Footer/index.tsx @@ -0,0 +1,56 @@ +import { ComponentType } from 'preact'; +import { Telegram } from '../../assets/SocialsTelegram'; +import { Github } from '../../assets/SocialsGithub'; +import { X } from '../../assets/SocialsX'; + +interface SocialLink { + name: string; + icon: ComponentType<{ className?: string }>; + url: string; +} + +const SOCIALS: SocialLink[] = [ + { + name: 'X', + icon: X, + url: 'https://x.com/Vortex_Fi', + }, + { + name: 'Telegram', + icon: Telegram, + url: 'https://t.me/vortex_fi', + }, + { + name: 'Github', + icon: Github, + url: 'https://github.com/pendulum-chain/vortex', + }, +]; + +const SocialIcon = ({ social }: { social: SocialLink }) => ( + + + +); + +const Copyright = () => ( +
+

Copyright © {new Date().getFullYear()}, Vortex.

+

All rights reserved.

+
+); + +export function Footer() { + return ( +
+
+ +
+ {SOCIALS.map((social) => ( + + ))} +
+
+
+ ); +} diff --git a/src/components/Navbar/index.tsx b/src/components/Navbar/index.tsx index 1eb611d7..f70e73f1 100644 --- a/src/components/Navbar/index.tsx +++ b/src/components/Navbar/index.tsx @@ -97,7 +97,7 @@ export const Navbar = () => { const { networkSelectorDisabled } = useNetwork(); return ( -
+
+ ); +} diff --git a/src/constants/constants.ts b/src/constants/constants.ts index f4097194..1d266731 100644 --- a/src/constants/constants.ts +++ b/src/constants/constants.ts @@ -2,6 +2,7 @@ import { config } from '../config'; export const HORIZON_URL = 'https://horizon.stellar.org'; export const STELLAR_BASE_FEE = '1000000'; +export const STELLAR_EPHEMERAL_STARTING_BALANCE_UNITS = '2.5'; // Amount to send to the new stellar ephemeral account created export const PENDULUM_WSS = 'wss://rpc-pendulum.prd.pendulumchain.tech'; export const ASSETHUB_WSS = 'wss://polkadot-asset-hub-rpc.polkadot.io'; export const WALLETCONNECT_ASSETHUB_ID = 'polkadot:68d56f15f85d3136970ec16946040bc1'; diff --git a/src/hooks/offramp/useMainProcess.ts b/src/hooks/offramp/useMainProcess.ts index a6e76496..a93007ad 100644 --- a/src/hooks/offramp/useMainProcess.ts +++ b/src/hooks/offramp/useMainProcess.ts @@ -1,8 +1,9 @@ -import { useEffect } from 'preact/compat'; +import { useState, useEffect, useRef } from 'preact/compat'; import Big from 'big.js'; +import { recoverFromFailure, readCurrentState, OfframpingState } from '../../services/offrampingFlow'; + import { InputTokenType, OutputTokenType } from '../../constants/tokenConfig'; -import { recoverFromFailure, readCurrentState } from '../../services/offrampingFlow'; import { useSubmitOfframp } from './useSubmitOfframp'; import { useOfframpEvents } from './useOfframpEvents'; @@ -24,6 +25,11 @@ export interface ExecutionInput { export const useMainProcess = () => { const { updateOfframpHookStateFromState, resetOfframpState, setOfframpStarted } = useOfframpActions(); const offrampState = useOfframpState(); + const [offrampingStarted, setOfframpingStarted] = useState(false); + const [isInitiating, setIsInitiating] = useState(false); + const [offrampingState, setOfframpingState] = useState(undefined); + const [signingPhase, setSigningPhase] = useState(undefined); + const isProcessingAdvance = useRef(false); // Sep 24 states const { firstSep24Response, firstSep24Interval } = useSep24Store.getState(); diff --git a/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts b/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts index b3b3c386..3091cd1b 100644 --- a/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts +++ b/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts @@ -12,6 +12,7 @@ import { useTrackSEP24Events } from './useTrackSEP24Events'; import { usePendulumNode } from '../../../contexts/polkadotNode'; import { useOfframpActions } from '../../../stores/offrampStore'; import { useSep24Store, useSep24Actions } from '../../../stores/sep24Store'; +import { useVortexAccount } from '../../useVortexAccount'; const handleAmountMismatch = (setOfframpingStarted: (started: boolean) => void): void => { setOfframpingStarted(false); @@ -28,6 +29,7 @@ export const useAnchorWindowHandler = () => { const { selectedNetwork } = useNetwork(); const { apiComponents: pendulumNode } = usePendulumNode(); const { setOfframpStarted, updateOfframpHookStateFromState } = useOfframpActions(); + const { address } = useVortexAccount(); const { firstSep24Response, anchorSessionParams, executionInput } = useSep24Store.getState(); const { cleanupSep24State } = useSep24Actions(); @@ -63,6 +65,7 @@ export const useAnchorWindowHandler = () => { sepResult: secondSep24Response, network: selectedNetwork, pendulumNode, + offramperAddress: address!, }); trackKYCCompleted(initialState, selectedNetwork); diff --git a/src/layouts/index.tsx b/src/layouts/index.tsx index 188e5e95..a8aaefbd 100644 --- a/src/layouts/index.tsx +++ b/src/layouts/index.tsx @@ -1,5 +1,6 @@ import { FC } from 'preact/compat'; import { Navbar } from '../components/Navbar'; +import { Footer } from '../components/Footer'; interface BaseLayoutProps { main: ReactNode; @@ -11,5 +12,6 @@ export const BaseLayout: FC = ({ main, modals }) => ( {modals} {main} +
); diff --git a/src/pages/swap/index.tsx b/src/pages/swap/index.tsx index 89bd4e7c..935ba15b 100644 --- a/src/pages/swap/index.tsx +++ b/src/pages/swap/index.tsx @@ -1,8 +1,7 @@ -import { useEffect, useMemo, useRef, useState, useCallback } from 'preact/hooks'; import { Fragment } from 'preact'; import { ArrowDownIcon } from '@heroicons/react/20/solid'; import Big from 'big.js'; - +import { useEffect, useMemo, useRef, useState, useCallback } from 'preact/hooks'; import { ApiPromise } from '@polkadot/api'; import { calculateTotalReceive, FeeCollapse } from '../../components/FeeCollapse'; @@ -19,6 +18,7 @@ import { UserBalance } from '../../components/UserBalance'; import { SigningBox } from '../../components/SigningBox'; import { SignInModal } from '../../components/SignIn'; import { SPACEWALK_REDEEM_SAFETY_MARGIN } from '../../constants/constants'; +import { PoweredBy } from '../../components/PoweredBy'; import { getInputTokenDetailsOrDefault, @@ -40,6 +40,7 @@ import { showToast, ToastMessage } from '../../helpers/notifications'; import { useInputTokenBalance } from '../../hooks/useInputTokenBalance'; import { useTokenOutAmount } from '../../hooks/nabla/useTokenAmountOut'; import { useMainProcess } from '../../hooks/offramp/useMainProcess'; +import { useSwapUrlParams } from './useSwapUrlParams'; import { initialChecks } from '../../services/initialChecks'; import { getVaultsForCurrency } from '../../services/phases/polkadot/spacewalk'; @@ -148,6 +149,8 @@ export const SwapPage = () => { to, } = useSwapForm(); + useSwapUrlParams({ form, setShowCompareFees }); + const fromToken = getInputTokenDetailsOrDefault(selectedNetwork, from); const toToken = OUTPUT_TOKEN_CONFIG[to]; const formToAmount = form.watch('toAmount'); @@ -172,7 +175,7 @@ export const SwapPage = () => { tokenOutAmount.stableAmountInUnits != '' && Big(tokenOutAmount.stableAmountInUnits).gt(Big(0)); - function onConfirm(e: Event) { + function onSwapConfirm(e: Event) { e.preventDefault(); if (!inputAmountIsStable) return; @@ -414,10 +417,10 @@ export const SwapPage = () => {
-

Withdraw

+

Withdraw

@@ -483,11 +486,13 @@ export const SwapPage = () => { ) : ( )}
+
+ {showCompareFees && fromToken && fromAmount && toToken && ( ; + setShowCompareFees: StateUpdater; +} + +export const useSwapUrlParams = ({ form, setShowCompareFees }: UseSwapUrlParamsProps) => { + useEffect(() => { + const params = new URLSearchParams(window.location.search); + const fromAmountParam = params.get('fromAmount'); + if (fromAmountParam) { + const parsedAmount = Number(fromAmountParam); + if (!isNaN(parsedAmount) && parsedAmount >= 0) { + form.setValue('fromAmount', parsedAmount.toFixed(2)); + } + } + + const showCompareFeesParam = params.get('showCompareFees'); + if (showCompareFeesParam === 'true') { + setShowCompareFees(true); + } + }, [form, setShowCompareFees]); +}; diff --git a/src/services/anchor/index.ts b/src/services/anchor/index.ts index 59973b03..7ed74b00 100644 --- a/src/services/anchor/index.ts +++ b/src/services/anchor/index.ts @@ -274,6 +274,8 @@ export async function sep24Second( const { sep24Url } = tomlValues; if (config.test.mockSep24) { + // sleep 10 secs + await new Promise((resolve) => setTimeout(resolve, 10000)); return { amount: sessionParams.offrampAmount, memo: 'MYK1722323689', diff --git a/src/services/offrampingFlow.ts b/src/services/offrampingFlow.ts index a148d726..51c77a3e 100644 --- a/src/services/offrampingFlow.ts +++ b/src/services/offrampingFlow.ts @@ -83,6 +83,7 @@ export interface InitiateStateArguments { sepResult: SepResult; network: Networks; pendulumNode: { ss58Format: number; api: ApiPromise; decimals: number }; + offramperAddress: string; } export interface OfframpingState { @@ -118,6 +119,7 @@ export interface OfframpingState { nablaSwapTransaction: string; }; network: Networks; + offramperAddress: string; } export type StateTransitionFunction = ( @@ -180,6 +182,7 @@ export async function constructInitialState({ sepResult, network, pendulumNode, + offramperAddress, }: InitiateStateArguments) { const { seed: pendulumEphemeralSeed, address: pendulumEphemeralAddress } = await createPendulumEphemeralSeed( pendulumNode, @@ -224,6 +227,7 @@ export async function constructInitialState({ sepResult, network, pendulumEphemeralAddress, + offramperAddress, }; storageService.set(OFFRAMPING_STATE_LOCAL_STORAGE_KEY, initialState); diff --git a/src/services/phases/polkadot/spacewalk.tsx b/src/services/phases/polkadot/spacewalk.tsx index afc334a7..c57c98ba 100644 --- a/src/services/phases/polkadot/spacewalk.tsx +++ b/src/services/phases/polkadot/spacewalk.tsx @@ -151,7 +151,6 @@ export class VaultService { options.era = 0; const stellarPkBytes = Uint8Array.from(stellarPkBytesBuffer); - return this.apiComponents!.api.tx.redeem.requestRedeem(amountRaw, stellarPkBytes, this.vaultId!).signAsync( addressOrPair, options, diff --git a/src/services/phases/signedTransactions.ts b/src/services/phases/signedTransactions.ts index 9822fdc3..b4a890f0 100644 --- a/src/services/phases/signedTransactions.ts +++ b/src/services/phases/signedTransactions.ts @@ -48,6 +48,7 @@ export async function prepareTransactions(state: OfframpingState, context: Execu const stellarFundingAccountId = (await fetchSigningServiceAccountId()).stellar.public; const stellarEphemeralKeypair = Keypair.fromSecret(stellarEphemeralSecret); const stellarEphemeralPublicKey = stellarEphemeralKeypair.publicKey(); + const { offrampingTransaction, mergeAccountTransaction } = await setUpAccountAndOperations( stellarFundingAccountId, stellarEphemeralKeypair, @@ -68,15 +69,11 @@ export async function prepareTransactions(state: OfframpingState, context: Execu const pendulumEphemeralKeypair = keyring.addFromUri(pendulumEphemeralSeed); const pendulumEphemeralPublicKey = pendulumEphemeralKeypair.address; - // Get the Polygon account connected by the user - const polygonAccount = getAccount(context.wagmiConfig); - const polygonAddress = polygonAccount.address; - // Try to store the data in the backend try { const data = { timestamp: new Date().toISOString(), - polygonAddress: polygonAddress || '', + offramperAddress: state.offramperAddress, stellarEphemeralPublicKey, pendulumEphemeralPublicKey, nablaApprovalTx: transactions.nablaApproveTransaction, diff --git a/src/services/phases/stellar/index.tsx b/src/services/phases/stellar/index.tsx index 73559ac9..c233fff1 100644 --- a/src/services/phases/stellar/index.tsx +++ b/src/services/phases/stellar/index.tsx @@ -14,7 +14,12 @@ import { } from 'stellar-sdk'; import { OUTPUT_TOKEN_CONFIG, OutputTokenDetails, OutputTokenType } from '../../../constants/tokenConfig'; -import { HORIZON_URL, SIGNING_SERVICE_URL, STELLAR_BASE_FEE } from '../../../constants/constants'; +import { + HORIZON_URL, + SIGNING_SERVICE_URL, + STELLAR_BASE_FEE, + STELLAR_EPHEMERAL_STARTING_BALANCE_UNITS, +} from '../../../constants/constants'; import { fetchSigningServiceAccountId } from '../../signingService'; import { OfframpingState } from '../../offrampingFlow'; import { SepResult } from '../../anchor'; @@ -22,6 +27,38 @@ import { SepResult } from '../../anchor'; const horizonServer = new Horizon.Server(HORIZON_URL); const NETWORK_PASSPHRASE = Networks.PUBLIC; +// This is a helper function to add a timeout to the loadAccount function and retry it. +// The Horizon.Server class does not allow defining a custom timeout for network queries. +async function loadAccountWithRetry( + ephemeralAccountId: string, + retries = 3, + timeout = 15000, +): Promise { + let lastError: Error | null = null; + + const loadAccountWithTimeout = (accountId: string, timeout: number): Promise => { + return Promise.race([ + horizonServer.loadAccount(accountId), + new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), timeout)), + ]); + }; + + for (let i = 0; i < retries; i++) { + try { + return await loadAccountWithTimeout(ephemeralAccountId, timeout); + } catch (err: any) { + if (err?.toString().includes('NotFoundError')) { + // The account does not exist + return null; + } + console.log(`Attempt ${i + 1} to load account ${ephemeralAccountId} failed: ${err}`); + lastError = err; + } + } + + throw new Error(`Failed to load account ${ephemeralAccountId} after ${retries} attempts: ` + lastError?.toString()); +} + export interface StellarOperations { offrampingTransaction: Transaction; mergeAccountTransaction: Transaction; @@ -58,10 +95,11 @@ async function isEphemeralCreated(stellarEphemeralSecret: string): Promise { - const ephemeralAccount = await horizonServer.loadAccount(ephemeralKeypair.publicKey()); + const ephemeralAccount = await loadAccountWithRetry(ephemeralKeypair.publicKey()); + if (!ephemeralAccount) { + throw new Error('Ephemeral account does not exist on network.'); + } + const { offrampingTransaction, mergeAccountTransaction } = await createOfframpAndMergeTransaction( fundingAccountId, sepResult, @@ -131,7 +173,7 @@ async function setupStellarAccount( .addOperation( Operation.createAccount({ destination: ephemeralAccountId, - startingBalance: '2.5', + startingBalance: STELLAR_EPHEMERAL_STARTING_BALANCE_UNITS, }), ) .addOperation( @@ -163,8 +205,7 @@ async function setupStellarAccount( await horizonServer.submitTransaction(createAccountTransaction); } catch (error: unknown) { const horizonError = error as { response: { data: { extras: any } } }; - console.log(horizonError.response.data.extras); - console.error(horizonError.response.data.extras.toString()); + console.error('Transaction submission to horizon failed', horizonError.toString()); throw new Error('Could not submit the account creation transaction'); } } diff --git a/src/services/storage/remote.ts b/src/services/storage/remote.ts index 4c4e402b..4b48cc67 100644 --- a/src/services/storage/remote.ts +++ b/src/services/storage/remote.ts @@ -3,7 +3,7 @@ import { SIGNING_SERVICE_URL } from '../../constants/constants'; // These are the headers for the Google Spreadsheet interface DumpData { timestamp: string; - polygonAddress: string; + offramperAddress: string; stellarEphemeralPublicKey: string; pendulumEphemeralPublicKey: string; nablaApprovalTx: string; diff --git a/tailwind.config.js b/tailwind.config.js index 2a52f698..cf2593f7 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,3 +1,5 @@ +import daisyui from 'daisyui'; + const colors = { whiteAlpha: { 50: 'rgba(255, 255, 255, 0.04)', From a8cfd37f217db565cb9d7f89531ea600abbaea7a Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Tue, 24 Dec 2024 10:54:41 -0300 Subject: [PATCH 12/32] solving lint issues and warnings --- src/components/SigningBox/index.tsx | 2 +- src/components/buttons/ConnectWalletButton/index.tsx | 2 +- src/components/buttons/SwapSubmitButton/index.tsx | 2 +- src/contexts/network.tsx | 2 +- src/hooks/offramp/useMainProcess.ts | 11 ++++------- src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts | 2 ++ src/hooks/offramp/useSEP24/useTrackSEP24Events.ts | 9 +-------- src/hooks/offramp/useSubmitOfframp.ts | 3 ++- src/hooks/useGetAssetIcon.tsx | 1 - src/pages/swap/index.tsx | 8 ++------ src/services/anchor/index.ts | 1 - src/services/phases/polkadot/ephemeral.tsx | 2 +- src/services/phases/signedTransactions.ts | 3 +-- src/services/phases/squidrouter/config.ts | 2 +- 14 files changed, 18 insertions(+), 32 deletions(-) diff --git a/src/components/SigningBox/index.tsx b/src/components/SigningBox/index.tsx index 19002e51..b1cdc355 100644 --- a/src/components/SigningBox/index.tsx +++ b/src/components/SigningBox/index.tsx @@ -13,7 +13,7 @@ interface ProgressConfig { approved: string; } -function getProgressConfig(network: Networks, step: SigningPhase): ProgressConfig { +function getProgressConfig(network: Networks): ProgressConfig { if (isNetworkEVM(network)) { return { started: '25', diff --git a/src/components/buttons/ConnectWalletButton/index.tsx b/src/components/buttons/ConnectWalletButton/index.tsx index 71549c9a..5faa5ff4 100644 --- a/src/components/buttons/ConnectWalletButton/index.tsx +++ b/src/components/buttons/ConnectWalletButton/index.tsx @@ -1,4 +1,4 @@ -import { isNetworkEVM, Networks, useNetwork } from '../../../contexts/network'; +import { isNetworkEVM, useNetwork } from '../../../contexts/network'; import { EVMWalletButton } from '../EVMWalletButton'; import { PolkadotWalletButton } from '../PolkadotWalletButton'; diff --git a/src/components/buttons/SwapSubmitButton/index.tsx b/src/components/buttons/SwapSubmitButton/index.tsx index 07afe760..a9167e31 100644 --- a/src/components/buttons/SwapSubmitButton/index.tsx +++ b/src/components/buttons/SwapSubmitButton/index.tsx @@ -3,7 +3,7 @@ import { Spinner } from '../../Spinner'; import { useAppKitAccount } from '@reown/appkit/react'; import { ConnectWalletButton } from '../ConnectWalletButton'; import { usePolkadotWalletState } from '../../../contexts/polkadotWallet'; -import { Networks, useNetwork, isNetworkEVM } from '../../../contexts/network'; +import { useNetwork, isNetworkEVM } from '../../../contexts/network'; interface SwapSubmitButtonProps { text: string; diff --git a/src/contexts/network.tsx b/src/contexts/network.tsx index 41663213..c93a3fbe 100644 --- a/src/contexts/network.tsx +++ b/src/contexts/network.tsx @@ -95,7 +95,7 @@ export const NetworkProvider = ({ children }: NetworkProviderProps) => { switchChain({ chainId: getNetworkId(network) }); } }, - [switchChain, chains, setSelectedNetworkLocalStorage], + [switchChain, setSelectedNetworkLocalStorage, resetOfframpState], ); // Only run on first render diff --git a/src/hooks/offramp/useMainProcess.ts b/src/hooks/offramp/useMainProcess.ts index a93007ad..db23c581 100644 --- a/src/hooks/offramp/useMainProcess.ts +++ b/src/hooks/offramp/useMainProcess.ts @@ -1,7 +1,7 @@ -import { useState, useEffect, useRef } from 'preact/compat'; +import { useEffect } from 'preact/compat'; import Big from 'big.js'; -import { recoverFromFailure, readCurrentState, OfframpingState } from '../../services/offrampingFlow'; +import { recoverFromFailure, readCurrentState } from '../../services/offrampingFlow'; import { InputTokenType, OutputTokenType } from '../../constants/tokenConfig'; @@ -25,11 +25,6 @@ export interface ExecutionInput { export const useMainProcess = () => { const { updateOfframpHookStateFromState, resetOfframpState, setOfframpStarted } = useOfframpActions(); const offrampState = useOfframpState(); - const [offrampingStarted, setOfframpingStarted] = useState(false); - const [isInitiating, setIsInitiating] = useState(false); - const [offrampingState, setOfframpingState] = useState(undefined); - const [signingPhase, setSigningPhase] = useState(undefined); - const isProcessingAdvance = useRef(false); // Sep 24 states const { firstSep24Response, firstSep24Interval } = useSep24Store.getState(); @@ -44,6 +39,8 @@ export const useMainProcess = () => { const recoveryState = readCurrentState(); updateOfframpHookStateFromState(recoveryState); events.trackOfframpingEvent(recoveryState); + // 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 diff --git a/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts b/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts index 3091cd1b..d77afa93 100644 --- a/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts +++ b/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts @@ -74,6 +74,8 @@ export const useAnchorWindowHandler = () => { handleError(error, setOfframpStarted); } }, [ + address, + cleanupSep24State, firstSep24Response, anchorSessionParams, executionInput, diff --git a/src/hooks/offramp/useSEP24/useTrackSEP24Events.ts b/src/hooks/offramp/useSEP24/useTrackSEP24Events.ts index 17953f53..4b2065bf 100644 --- a/src/hooks/offramp/useSEP24/useTrackSEP24Events.ts +++ b/src/hooks/offramp/useSEP24/useTrackSEP24Events.ts @@ -1,15 +1,8 @@ -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 { getInputTokenDetailsOrDefault, OUTPUT_TOKEN_CONFIG } from '../../../constants/tokenConfig'; import { ExecutionInput } from '../useMainProcess'; diff --git a/src/hooks/offramp/useSubmitOfframp.ts b/src/hooks/offramp/useSubmitOfframp.ts index 8109908a..d7d7e48b 100644 --- a/src/hooks/offramp/useSubmitOfframp.ts +++ b/src/hooks/offramp/useSubmitOfframp.ts @@ -1,4 +1,4 @@ -import { MutableRefObject, useCallback } from 'preact/compat'; +import { useCallback } from 'preact/compat'; import { polygon } from 'wagmi/chains'; import { useSwitchChain } from 'wagmi'; import { useVortexAccount } from '../useVortexAccount'; @@ -142,6 +142,7 @@ export const useSubmitOfframp = () => { setFirstSep24Response, setFirstSep24Interval, cleanupSep24State, + switchChainAsync, ], ); }; diff --git a/src/hooks/useGetAssetIcon.tsx b/src/hooks/useGetAssetIcon.tsx index 25e2999a..ca05243b 100644 --- a/src/hooks/useGetAssetIcon.tsx +++ b/src/hooks/useGetAssetIcon.tsx @@ -20,7 +20,6 @@ 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'; -import { i } from 'vite/dist/node/types.d-aGj9QkWt'; const ICONS = { eur: EUR, diff --git a/src/pages/swap/index.tsx b/src/pages/swap/index.tsx index 935ba15b..fc19bbbf 100644 --- a/src/pages/swap/index.tsx +++ b/src/pages/swap/index.tsx @@ -37,7 +37,6 @@ import { useSiweContext } from '../../contexts/siwe'; import { multiplyByPowerOfTen, stringifyBigWithSignificantDecimals } from '../../helpers/contracts'; import { showToast, ToastMessage } from '../../helpers/notifications'; -import { useInputTokenBalance } from '../../hooks/useInputTokenBalance'; import { useTokenOutAmount } from '../../hooks/nabla/useTokenAmountOut'; import { useMainProcess } from '../../hooks/offramp/useMainProcess'; import { useSwapUrlParams } from './useSwapUrlParams'; @@ -80,10 +79,9 @@ export const SwapPage = () => { const { selectedNetwork, setNetworkSelectorDisabled } = useNetwork(); const { signingPending, handleSign, handleCancel } = useSiweContext(); - const [termsAnimationKey, setTermsAnimationKey] = useState(0); + const [termsAnimationKey] = useState(0); - const { setTermsAccepted, toggleTermsChecked, termsChecked, termsAccepted, termsError, setTermsError } = - useTermsAndConditions(); + const { toggleTermsChecked, termsChecked, termsAccepted, termsError, setTermsError } = useTermsAndConditions(); useEffect(() => { setApiInitializeFailed(!pendulumNode.apiComponents?.api && pendulumNode?.isFetched); @@ -157,8 +155,6 @@ export const SwapPage = () => { // The price comparison is only available for Polygon (for now) const vortexPrice = useMemo(() => (formToAmount ? Big(formToAmount) : Big(0)), [formToAmount]); - const userInputTokenBalance = useInputTokenBalance({ fromToken }); - const tokenOutAmount = useTokenOutAmount({ wantsSwap: true, api, diff --git a/src/services/anchor/index.ts b/src/services/anchor/index.ts index 7ed74b00..93f9a73d 100644 --- a/src/services/anchor/index.ts +++ b/src/services/anchor/index.ts @@ -4,7 +4,6 @@ import { Keyring } from '@polkadot/api'; import { fetchSep10Signatures, fetchSigningServiceAccountId, SignerServiceSep10Request } from '../signingService'; import { OutputTokenDetails, OutputTokenType } from '../../constants/tokenConfig'; -import { EventStatus } from '../../components/GenericEvent'; import { OUTPUT_TOKEN_CONFIG } from '../../constants/tokenConfig'; import { SIGNING_SERVICE_URL } from '../../constants/constants'; diff --git a/src/services/phases/polkadot/ephemeral.tsx b/src/services/phases/polkadot/ephemeral.tsx index f76ecb3c..d09acab1 100644 --- a/src/services/phases/polkadot/ephemeral.tsx +++ b/src/services/phases/polkadot/ephemeral.tsx @@ -15,7 +15,7 @@ import { SIGNING_SERVICE_URL } from '../../../constants/constants'; import { multiplyByPowerOfTen } from '../../../helpers/contracts'; import { waitUntilTrue } from '../../../helpers/function'; -import { isNetworkEVM, Networks } from '../../../contexts/network'; +import { isNetworkEVM } from '../../../contexts/network'; import { ExecutionContext, OfframpingState } from '../../offrampingFlow'; import { fetchSigningServiceAccountId } from '../../signingService'; import { isHashRegistered } from '../moonbeam'; diff --git a/src/services/phases/signedTransactions.ts b/src/services/phases/signedTransactions.ts index b4a890f0..8bdad1b1 100644 --- a/src/services/phases/signedTransactions.ts +++ b/src/services/phases/signedTransactions.ts @@ -1,9 +1,8 @@ -import { getAccount } from '@wagmi/core'; import { ApiPromise, Keyring } from '@polkadot/api'; import { Extrinsic } from '@pendulum-chain/api-solang'; import { Keypair } from 'stellar-sdk'; -import { isNetworkEVM, Networks } from '../../contexts/network'; +import { isNetworkEVM } from '../../contexts/network'; import { ExecutionContext, OfframpingState } from '../offrampingFlow'; import { fetchSigningServiceAccountId } from '../signingService'; diff --git a/src/services/phases/squidrouter/config.ts b/src/services/phases/squidrouter/config.ts index c1d9da8f..48503be3 100644 --- a/src/services/phases/squidrouter/config.ts +++ b/src/services/phases/squidrouter/config.ts @@ -19,7 +19,7 @@ export const squidRouterConfigBase: ConfigBase = { }; export function getSquidRouterConfig(network: Networks): Config { - let networkId = getNetworkId(network); + const networkId = getNetworkId(network); if (!networkId) { throw new Error('getSquidRouterConfig: Network must be EVM to support SquidRouter'); } From 4203d69159223d8a10b121d1e38193a0c6190998 Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Tue, 24 Dec 2024 11:08:59 -0300 Subject: [PATCH 13/32] introduce latest fixes for advanceOfframpingState useEffect --- src/hooks/offramp/useOfframpAdvancement.ts | 66 ++++++++++++++-------- 1 file changed, 41 insertions(+), 25 deletions(-) diff --git a/src/hooks/offramp/useOfframpAdvancement.ts b/src/hooks/offramp/useOfframpAdvancement.ts index e4565d7c..e16ed75f 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'; @@ -10,6 +10,7 @@ import { usePendulumNode } from '../../contexts/polkadotNode'; import { useEventsContext } from '../../contexts/events'; import { useOfframpActions, useOfframpState } from '../../stores/offrampStore'; +import { isNetworkEVM, useNetwork } from '../../contexts/network'; interface AdvancementDeps { addEvent: (message: string, status: EventStatus) => void; @@ -20,40 +21,55 @@ export const useOfframpAdvancement = ({ addEvent }: AdvancementDeps) => { 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, { - renderEvent: addEvent, - wagmiConfig, - setOfframpSigningPhase, - trackEvent, - pendulumNode, - assetHubNode, - walletAccount, - }); - - if (JSON.stringify(offrampState) !== JSON.stringify(nextState)) { - updateOfframpHookStateFromState(nextState); + const nextState = await advanceOfframpingState(offrampState, { + renderEvent: addEvent, + 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, + ]); }; From d76793b4dd1fc29faebe7365aeb977c58f14cbf2 Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Tue, 24 Dec 2024 11:37:56 -0300 Subject: [PATCH 14/32] fix build --- src/components/SigningBox/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SigningBox/index.tsx b/src/components/SigningBox/index.tsx index b1cdc355..bc416c8c 100644 --- a/src/components/SigningBox/index.tsx +++ b/src/components/SigningBox/index.tsx @@ -56,7 +56,7 @@ export const SigningBox: FC = ({ step }) => { if (!['started', 'approved', 'signed'].includes(step)) return null; if (!isNetworkEVM(selectedNetwork) && (step === 'approved' || step === 'signed')) return null; - const progressValue = getProgressConfig(selectedNetwork, step) || '0'; + const progressValue = getProgressConfig(selectedNetwork)[step] || '0'; const { maxSignatures, getSignatureNumber } = getSignatureConfig(selectedNetwork); return ( From c1efa3dca7b4b2a8db3abc2f05b56b6329e239e4 Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Thu, 26 Dec 2024 12:19:01 -0300 Subject: [PATCH 15/32] support other evm networks for fee comparisson --- signer-service/src/api/services/alchemypay.service.js | 8 ++++++++ src/pages/swap/index.tsx | 11 ++++++++--- src/services/quotes/index.ts | 4 ++-- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/signer-service/src/api/services/alchemypay.service.js b/signer-service/src/api/services/alchemypay.service.js index cbd1b1a7..bcdf8a72 100644 --- a/signer-service/src/api/services/alchemypay.service.js +++ b/signer-service/src/api/services/alchemypay.service.js @@ -190,6 +190,14 @@ function getAlchemyPayNetworkCode(network) { switch (network.toUpperCase()) { case 'POLYGON': return 'MATIC'; + case 'BSC': + return 'BSC'; + case 'ARBITRUM': + return 'ARBITRUM'; + case 'AVALANCHE': + return 'AVAX'; + case 'ETHEREUM': + return 'ETH'; default: return network; } diff --git a/src/pages/swap/index.tsx b/src/pages/swap/index.tsx index fc19bbbf..7cdba6eb 100644 --- a/src/pages/swap/index.tsx +++ b/src/pages/swap/index.tsx @@ -276,8 +276,13 @@ export const SwapPage = () => { }, []); useEffect(() => { - if (offrampState?.phase !== undefined) { - setNetworkSelectorDisabled(true); + switch (offrampState?.phase) { + case undefined: + setNetworkSelectorDisabled(false); + break; + default: + setNetworkSelectorDisabled(true); + break; } }, [offrampState, setNetworkSelectorDisabled]); @@ -496,7 +501,7 @@ export const SwapPage = () => { amount={fromAmount} targetAssetSymbol={toToken.fiat.symbol} vortexPrice={vortexPrice} - network={Networks.Polygon} // TODO, need to pass the proper selected network, unless it is substrate. Also, assuming it works with all supported EVMs + network={selectedNetwork} /> )} diff --git a/src/services/quotes/index.ts b/src/services/quotes/index.ts index 19dfee85..698d4c83 100644 --- a/src/services/quotes/index.ts +++ b/src/services/quotes/index.ts @@ -2,7 +2,7 @@ import { polygon } from 'wagmi/chains'; import Big from 'big.js'; import { SIGNING_SERVICE_URL } from '../../constants/constants'; -import { Networks } from '../../contexts/network'; +import { isNetworkEVM, Networks } from '../../contexts/network'; const QUOTE_ENDPOINT = `${SIGNING_SERVICE_URL}/v1/quotes`; @@ -28,7 +28,7 @@ async function getQuoteFromService( amount: Big, network: Networks, ): Promise { - if (network !== polygon.name) { + if (!isNetworkEVM(network)) { throw new Error(`Network ${network} is not supported`); } // Fetch the quote from the service with a GET request From 55b592ddd50181324bdc87226ce26c3389ccf33d Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Thu, 26 Dec 2024 13:04:01 -0300 Subject: [PATCH 16/32] modify network code for transak quote --- signer-service/src/api/services/transak.service.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/signer-service/src/api/services/transak.service.js b/signer-service/src/api/services/transak.service.js index ebaf1e58..25405208 100644 --- a/signer-service/src/api/services/transak.service.js +++ b/signer-service/src/api/services/transak.service.js @@ -45,12 +45,7 @@ async function priceQuery(cryptoCurrency, fiatCurrency, cryptoAmount, network, i // Helper function to get the network code for Transak. It seems like Transak just uses the commonly known network names // as the code for the network parameter in their API so we just return the network as is. function getTransakNetworkCode(network) { - switch (network.toUpperCase()) { - case 'POLYGON': - return 'polygon'; - default: - return network; - } + return network.toLowerCase(); } function getCryptoCode(fromCrypto) { From 8b4925298002e2674cd7b8d7f3fa00ced9e82196 Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Tue, 31 Dec 2024 10:13:12 -0300 Subject: [PATCH 17/32] replace render event function with console log --- src/components/GenericEvent.tsx | 2 -- src/services/phases/nabla.ts | 29 +++++++++++++------------- src/services/phases/polkadot/index.tsx | 14 ++++++------- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/components/GenericEvent.tsx b/src/components/GenericEvent.tsx index 3883c17e..818ae4f7 100644 --- a/src/components/GenericEvent.tsx +++ b/src/components/GenericEvent.tsx @@ -6,8 +6,6 @@ export enum EventStatus { Waiting = 'waiting', } -export type RenderEventHandler = (event: string, status: EventStatus) => void; - export interface GenericEvent { value: string; status: EventStatus; diff --git a/src/services/phases/nabla.ts b/src/services/phases/nabla.ts index eece8d77..d5426d30 100644 --- a/src/services/phases/nabla.ts +++ b/src/services/phases/nabla.ts @@ -80,7 +80,7 @@ export async function prepareNablaApproveTransaction( context: ExecutionContext, ): Promise { const { inputTokenType, inputAmount, pendulumEphemeralSeed, nablaApproveNonce, network } = state; - const { pendulumNode, renderEvent } = context; + const { pendulumNode } = context; const { ss58Format, api } = pendulumNode; // event attempting swap @@ -109,7 +109,7 @@ export async function prepareNablaApproveTransaction( if (response.type !== 'success') { const message = 'Could not load token allowance'; - renderEvent(message, EventStatus.Error); + console.log(message, EventStatus.Error); throw new Error(message); } @@ -118,7 +118,7 @@ export async function prepareNablaApproveTransaction( //maybe do allowance if (currentAllowance === undefined || currentAllowance.rawBalance.lt(Big(inputAmount.raw))) { try { - renderEvent(`Approving tokens: ${inputAmount.units} ${inputToken.pendulumAssetSymbol}`, EventStatus.Waiting); + console.log(`Approving tokens: ${inputAmount.units} ${inputToken.pendulumAssetSymbol}`, EventStatus.Waiting); return createAndSignApproveExtrinsic({ api: api, amount: inputAmount.raw, @@ -129,7 +129,7 @@ export async function prepareNablaApproveTransaction( nonce: nablaApproveNonce, }); } catch (e) { - renderEvent(`Could not approve token: ${e}`, EventStatus.Error); + console.log(`Could not approve token: ${e}`, EventStatus.Error); return Promise.reject('Could not approve token'); } } @@ -141,7 +141,7 @@ export async function prepareNablaApproveTransaction( // save any state for potential recovery. export async function nablaApprove(state: OfframpingState, context: ExecutionContext): Promise { const { transactions, inputAmount, inputTokenType, nablaApproveNonce, network } = state; - const { renderEvent, pendulumNode } = context; + const { pendulumNode } = context; const { api } = pendulumNode; @@ -166,14 +166,14 @@ export async function nablaApprove(state: OfframpingState, context: ExecutionCon } try { - renderEvent(`Approving tokens: ${inputAmount.units} ${inputToken.pendulumAssetSymbol}`, EventStatus.Waiting); + console.log(`Approving tokens: ${inputAmount.units} ${inputToken.pendulumAssetSymbol}`, EventStatus.Waiting); const approvalExtrinsic = decodeSubmittableExtrinsic(transactions.nablaApproveTransaction, api); const result = await submitExtrinsic(approvalExtrinsic); if (result.status.type === 'error') { - renderEvent(`Could not approve token: ${result.status.error.toString()}`, EventStatus.Error); + console.log(`Could not approve token: ${result.status.error.toString()}`, EventStatus.Error); return Promise.reject('Could not approve token'); } } catch (e) { @@ -186,7 +186,7 @@ export async function nablaApprove(state: OfframpingState, context: ExecutionCon } else { errorMessage = 'Something went wrong'; } - renderEvent(`Could not approve the required amount of token: ${errorMessage}`, EventStatus.Error); + console.log(`Could not approve the required amount of token: ${errorMessage}`, EventStatus.Error); return Promise.reject('Could not approve token'); } @@ -257,7 +257,6 @@ export async function prepareNablaSwapTransaction( nablaSwapNonce, network, } = state; - const { renderEvent } = context; // event attempting swap const inputToken = getInputTokenDetailsOrDefault(network, inputTokenType); @@ -280,7 +279,7 @@ export async function prepareNablaSwapTransaction( if (rawBalanceBefore.lt(Big(nablaHardMinimumOutputRaw))) { // Try swap try { - renderEvent( + console.log( `Swapping ${inputAmount.units} ${inputToken.pendulumAssetSymbol} to ${outputAmount.units} ${outputToken.stellarAsset.code.string} `, EventStatus.Waiting, ); @@ -315,7 +314,7 @@ export async function nablaSwap(state: OfframpingState, context: ExecutionContex nablaSoftMinimumOutputRaw, network, } = state; - const { renderEvent, pendulumNode } = context; + const { pendulumNode } = context; const { api, ss58Format } = pendulumNode; @@ -348,7 +347,7 @@ export async function nablaSwap(state: OfframpingState, context: ExecutionContex const rawBalanceBefore = Big(responseBalanceBefore?.free?.toString() ?? '0'); try { - renderEvent( + console.log( `Swapping ${inputAmount.units} ${inputToken.pendulumAssetSymbol} to ${outputAmount.units} ${outputToken.stellarAsset.code.string} `, EventStatus.Waiting, ); @@ -380,7 +379,7 @@ export async function nablaSwap(state: OfframpingState, context: ExecutionContex const result = await submitExtrinsic(swapExtrinsic); if (result.status.type === 'error') { - renderEvent(`Could not swap token: ${result.status.error.toString()}`, EventStatus.Error); + console.log(`Could not swap token: ${result.status.error.toString()}`, EventStatus.Error); return Promise.reject('Could not swap token'); } } catch (e) { @@ -393,7 +392,7 @@ export async function nablaSwap(state: OfframpingState, context: ExecutionContex } else { errorMessage = 'Something went wrong'; } - renderEvent(`Could not swap the required amount of token: ${errorMessage}`, EventStatus.Error); + console.log(`Could not swap the required amount of token: ${errorMessage}`, EventStatus.Error); return Promise.reject('Could not swap token'); } //verify token balance before releasing this process. @@ -403,7 +402,7 @@ export async function nablaSwap(state: OfframpingState, context: ExecutionContex const actualOfframpValueRaw = rawBalanceAfter.sub(rawBalanceBefore); const actualOfframpValue = multiplyByPowerOfTen(actualOfframpValueRaw, -outputToken.decimals); - renderEvent( + console.log( `Swap successful. Amount received: ${stringifyBigWithSignificantDecimals(actualOfframpValue, 2)}`, EventStatus.Success, ); diff --git a/src/services/phases/polkadot/index.tsx b/src/services/phases/polkadot/index.tsx index 27f3d1c4..fc9f0fe5 100644 --- a/src/services/phases/polkadot/index.tsx +++ b/src/services/phases/polkadot/index.tsx @@ -30,7 +30,7 @@ export async function prepareSpacewalkRedeemTransaction( state: OfframpingState, context: ExecutionContext, ): Promise { - const { pendulumNode, renderEvent } = context; + const { pendulumNode } = context; if (!pendulumNode) { throw new Error('Pendulum node not available'); @@ -57,7 +57,7 @@ export async function prepareSpacewalkRedeemTransaction( outputToken.stellarAsset.issuer.hex, outputAmount.raw, ); - renderEvent( + console.log( `Requesting redeem of ${outputAmount.units} tokens for vault ${prettyPrintVaultId(vaultService.vaultId)}`, EventStatus.Waiting, ); @@ -78,7 +78,7 @@ export async function executeSpacewalkRedeem( state: OfframpingState, context: ExecutionContext, ): Promise { - const { pendulumNode, renderEvent } = context; + const { pendulumNode } = context; if (!pendulumNode) { throw new Error('Pendulum node not available'); @@ -155,7 +155,7 @@ export async function executeSpacewalkRedeem( outputToken.stellarAsset.issuer.hex, outputAmount.raw, ); - renderEvent( + console.log( `Requesting redeem of ${outputAmount.units} tokens for vault ${prettyPrintVaultId(vaultService.vaultId)}`, EventStatus.Waiting, ); @@ -170,7 +170,7 @@ export async function executeSpacewalkRedeem( ); // Render event that the extrinsic passed, and we are now waiting for the execution of it - renderEvent( + console.log( `Redeem request passed, waiting up to ${maxWaitingTimeMinutes} minutes for redeem execution event...`, EventStatus.Waiting, ); @@ -182,7 +182,7 @@ export async function executeSpacewalkRedeem( // This is a potentially recoverable error (due to network delay) // in the future we should distinguish between recoverable and non-recoverable errors console.log(`Failed to wait for redeem execution: ${error}`); - renderEvent(`Failed to wait for redeem execution: Max waiting time exceeded`, EventStatus.Error); + console.log(`Failed to wait for redeem execution: Max waiting time exceeded`, EventStatus.Error); throw new Error(`Failed to wait for redeem execution`); } } catch (error) { @@ -197,7 +197,7 @@ export async function executeSpacewalkRedeem( } } - renderEvent('Redeem process completed, executing offramp transaction', EventStatus.Waiting); + console.log('Redeem process completed, executing offramp transaction', EventStatus.Waiting); return successorState; } From 0eb955c93125a56ba99849941c5d01fc7a2b778d Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Tue, 31 Dec 2024 12:16:55 -0300 Subject: [PATCH 18/32] save in state the proper raw input expected, use zustand selector hooks to read state --- src/hooks/offramp/useMainProcess.ts | 6 ++++-- .../offramp/useSEP24/useAnchorWindowHandler.ts | 13 +++++++++++-- src/services/offrampingFlow.ts | 6 +++++- src/services/phases/nabla.ts | 12 +++++++----- src/services/phases/polkadot/ephemeral.tsx | 6 +++--- src/stores/sep24Store.ts | 4 ++++ 6 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/hooks/offramp/useMainProcess.ts b/src/hooks/offramp/useMainProcess.ts index 090ba742..b681a038 100644 --- a/src/hooks/offramp/useMainProcess.ts +++ b/src/hooks/offramp/useMainProcess.ts @@ -9,7 +9,7 @@ import { useSubmitOfframp } from './useSubmitOfframp'; import { useOfframpEvents } from './useOfframpEvents'; import { useOfframpAdvancement } from './useOfframpAdvancement'; import { useOfframpActions, useOfframpState } from '../../stores/offrampStore'; -import { useSep24Store } from '../../stores/sep24Store'; +import { useFirstSep24Interval, useFirstSep24Response, useSep24Store } from '../../stores/sep24Store'; import { useSep24Actions } from '../../stores/sep24Store'; import { useAnchorWindowHandler } from './useSEP24/useAnchorWindowHandler'; export type SigningPhase = 'started' | 'approved' | 'signed' | 'finished'; @@ -27,7 +27,9 @@ export const useMainProcess = () => { const offrampState = useOfframpState(); // Sep 24 states - const { firstSep24Response, firstSep24Interval } = useSep24Store.getState(); + const firstSep24Response = useFirstSep24Response(); + const firstSep24Interval = useFirstSep24Interval(); + const { cleanupSep24State } = useSep24Actions(); // Custom hooks diff --git a/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts b/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts index 86190a33..b64e6fd1 100644 --- a/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts +++ b/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts @@ -11,7 +11,13 @@ import { showToast, ToastMessage } from '../../../helpers/notifications'; import { useTrackSEP24Events } from './useTrackSEP24Events'; import { usePendulumNode } from '../../../contexts/polkadotNode'; import { useOfframpActions } from '../../../stores/offrampStore'; -import { useSep24Store, useSep24Actions } from '../../../stores/sep24Store'; +import { + useSep24Store, + useSep24Actions, + useFirstSep24Response, + useAnchorSessionParams, + useExecutionInput, +} from '../../../stores/sep24Store'; import { useVortexAccount } from '../../useVortexAccount'; const handleAmountMismatch = (setOfframpingStarted: (started: boolean) => void): void => { @@ -31,7 +37,10 @@ export const useAnchorWindowHandler = () => { const { setOfframpStarted, updateOfframpHookStateFromState } = useOfframpActions(); const { address } = useVortexAccount(); - const { firstSep24Response, anchorSessionParams, executionInput } = useSep24Store.getState(); + const firstSep24Response = useFirstSep24Response(); + const anchorSessionParams = useAnchorSessionParams(); + + const executionInput = useExecutionInput(); const { cleanupSep24State } = useSep24Actions(); return useCallback(async () => { diff --git a/src/services/offrampingFlow.ts b/src/services/offrampingFlow.ts index c3bb454e..634f767f 100644 --- a/src/services/offrampingFlow.ts +++ b/src/services/offrampingFlow.ts @@ -92,6 +92,7 @@ export interface OfframpingState { inputTokenType: InputTokenType; outputTokenType: OutputTokenType; inputAmount: { units: string; raw: string }; + pendulumAmountRaw: string; outputAmount: { units: string; raw: string }; phase: OfframpingPhase | FinalOfframpingPhase; failure?: FailureType; @@ -186,11 +187,13 @@ export async function constructInitialState({ pendulumNode, ); - const inputTokenDecimals = getInputTokenDetailsOrDefault(network, inputTokenType).decimals; + const { decimals: inputTokenDecimals, pendulumDecimals } = getInputTokenDetailsOrDefault(network, inputTokenType); const outputTokenDecimals = OUTPUT_TOKEN_CONFIG[outputTokenType].decimals; const inputAmountBig = Big(amountIn); const inputAmountRaw = multiplyByPowerOfTen(inputAmountBig, inputTokenDecimals || 0).toFixed(); + const pendulumAmountRaw = multiplyByPowerOfTen(inputAmountBig, pendulumDecimals || 0).toFixed(); + const outputAmountRaw = multiplyByPowerOfTen(amountOut, outputTokenDecimals).toFixed(); const nablaHardMinimumOutput = amountOut.mul(1 - AMM_MINIMUM_OUTPUT_HARD_MARGIN); @@ -211,6 +214,7 @@ export async function constructInitialState({ inputTokenType, outputTokenType, inputAmount: { units: amountIn, raw: inputAmountRaw }, + pendulumAmountRaw, outputAmount: { units: amountOut.toFixed(2, 0), raw: outputAmountRaw }, phase: 'prepareTransactions', squidRouterReceiverId, diff --git a/src/services/phases/nabla.ts b/src/services/phases/nabla.ts index d5426d30..0a088c7d 100644 --- a/src/services/phases/nabla.ts +++ b/src/services/phases/nabla.ts @@ -79,7 +79,7 @@ export async function prepareNablaApproveTransaction( state: OfframpingState, context: ExecutionContext, ): Promise { - const { inputTokenType, inputAmount, pendulumEphemeralSeed, nablaApproveNonce, network } = state; + const { inputTokenType, inputAmount, pendulumAmountRaw, pendulumEphemeralSeed, nablaApproveNonce, network } = state; const { pendulumNode } = context; const { ss58Format, api } = pendulumNode; @@ -116,12 +116,12 @@ export async function prepareNablaApproveTransaction( const currentAllowance = parseContractBalanceResponse(inputToken.pendulumDecimals, response.value); //maybe do allowance - if (currentAllowance === undefined || currentAllowance.rawBalance.lt(Big(inputAmount.raw))) { + if (currentAllowance === undefined || currentAllowance.rawBalance.lt(Big(pendulumAmountRaw))) { try { console.log(`Approving tokens: ${inputAmount.units} ${inputToken.pendulumAssetSymbol}`, EventStatus.Waiting); return createAndSignApproveExtrinsic({ api: api, - amount: inputAmount.raw, + amount: pendulumAmountRaw, token: inputToken.pendulumErc20WrapperAddress, spender: NABLA_ROUTER, contractAbi: erc20ContractAbi, @@ -251,6 +251,7 @@ export async function prepareNablaSwapTransaction( inputTokenType, outputTokenType, inputAmount, + pendulumAmountRaw, outputAmount, nablaHardMinimumOutputRaw, pendulumEphemeralSeed, @@ -286,7 +287,7 @@ export async function prepareNablaSwapTransaction( return createAndSignSwapExtrinsic({ api: api, - amount: inputAmount.raw, + amount: pendulumAmountRaw, amountMin: nablaHardMinimumOutputRaw, tokenIn: inputToken.pendulumErc20WrapperAddress, tokenOut: outputToken.erc20WrapperAddress, @@ -307,6 +308,7 @@ export async function nablaSwap(state: OfframpingState, context: ExecutionContex transactions, inputAmount, inputTokenType, + pendulumAmountRaw, outputAmount, outputTokenType, pendulumEphemeralSeed, @@ -360,7 +362,7 @@ export async function nablaSwap(state: OfframpingState, context: ExecutionContex contractDeploymentAddress: NABLA_ROUTER, callerAddress: ephemeralKeypair.address, messageName: 'getAmountOut', - messageArguments: [inputAmount.raw, [inputToken.pendulumErc20WrapperAddress, outputToken.erc20WrapperAddress]], + messageArguments: [pendulumAmountRaw, [inputToken.pendulumErc20WrapperAddress, outputToken.erc20WrapperAddress]], limits: defaultReadLimits, }); diff --git a/src/services/phases/polkadot/ephemeral.tsx b/src/services/phases/polkadot/ephemeral.tsx index d09acab1..5e8228f2 100644 --- a/src/services/phases/polkadot/ephemeral.tsx +++ b/src/services/phases/polkadot/ephemeral.tsx @@ -229,7 +229,7 @@ export async function subsidizePreSwap(state: OfframpingState, context: Executio throw new Error('Invalid input token'); } - const requiredAmount = Big(state.inputAmount.raw).sub(currentBalance); + const requiredAmount = Big(state.pendulumAmountRaw).sub(currentBalance); if (requiredAmount.gt(Big(0))) { console.log('Subsidizing pre-swap with', requiredAmount.toString()); @@ -252,8 +252,8 @@ export async function subsidizePreSwap(state: OfframpingState, context: Executio await waitUntilTrue(async () => { console.log('waiting for input balance to be enough'); const currentBalance = await getRawInputBalance(state, context); - console.log('currentBalance', currentBalance, Big(state.inputAmount.raw)); - return currentBalance.gte(Big(state.inputAmount.raw)); + console.log('currentBalance', currentBalance, Big(state.pendulumAmountRaw)); + return currentBalance.gte(Big(state.pendulumAmountRaw)); }); } diff --git a/src/stores/sep24Store.ts b/src/stores/sep24Store.ts index 6a660e60..35a5b935 100644 --- a/src/stores/sep24Store.ts +++ b/src/stores/sep24Store.ts @@ -65,3 +65,7 @@ export const useSep24Store = create()((set, get) => ({ // Selector hooks export const useSep24Actions = () => useSep24Store((state) => state.actions); +export const useFirstSep24Response = () => useSep24Store((state) => state.firstSep24Response); +export const useFirstSep24Interval = () => useSep24Store((state) => state.firstSep24Interval); +export const useAnchorSessionParams = () => useSep24Store((state) => state.anchorSessionParams); +export const useExecutionInput = () => useSep24Store((state) => state.executionInput); From 997cafe5996402df8f7d2c14a0fc37e827ee34c0 Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Thu, 2 Jan 2025 17:49:38 +0100 Subject: [PATCH 19/32] refactor SigningBox component --- src/components/SigningBox/index.tsx | 82 ++++++++++++++++------------- 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/src/components/SigningBox/index.tsx b/src/components/SigningBox/index.tsx index bc416c8c..15ba93de 100644 --- a/src/components/SigningBox/index.tsx +++ b/src/components/SigningBox/index.tsx @@ -6,57 +6,67 @@ import { SigningPhase } from '../../hooks/offramp/useMainProcess'; import { isNetworkEVM, Networks, useNetwork } from '../../contexts/network'; import { Spinner } from '../Spinner'; -interface ProgressConfig { +type ProgressStep = { started: string; signed: string; finished: string; approved: string; -} +}; -function getProgressConfig(network: Networks): ProgressConfig { - if (isNetworkEVM(network)) { - return { - started: '25', - approved: '50', - signed: '75', - finished: '100', - }; - } else { - return { - started: '33', - finished: '100', - signed: '0', - approved: '0', - }; - } -} +type SignatureConfig = { + maxSignatures: number; + getSignatureNumber: (step: SigningPhase) => string; +}; -function getSignatureConfig(network: Networks): any { - if (isNetworkEVM(network)) { - return { - maxSignatures: 2, - getSignatureNumber: (step: SigningPhase) => (step === 'started' ? '1' : '2'), - }; - } else { - return { - maxSignatures: 1, - getSignatureNumber: () => '1', - }; - } -} +const EVM_PROGRESS_CONFIG: ProgressStep = { + started: '25', + approved: '50', + signed: '75', + finished: '100', +}; + +const NON_EVM_PROGRESS_CONFIG: ProgressStep = { + started: '33', + finished: '100', + signed: '0', + approved: '0', +}; + +const EVM_SIGNATURE_CONFIG: SignatureConfig = { + maxSignatures: 2, + getSignatureNumber: (step: SigningPhase) => (step === 'started' ? '1' : '2'), +}; + +const NON_EVM_SIGNATURE_CONFIG: SignatureConfig = { + maxSignatures: 1, + getSignatureNumber: () => '1', +}; + +const getProgressConfig = (network: Networks): ProgressStep => { + return isNetworkEVM(network) ? EVM_PROGRESS_CONFIG : NON_EVM_PROGRESS_CONFIG; +}; + +const getSignatureConfig = (network: Networks): SignatureConfig => { + return isNetworkEVM(network) ? EVM_SIGNATURE_CONFIG : NON_EVM_SIGNATURE_CONFIG; +}; interface SigningBoxProps { step?: SigningPhase; } +const isValidStep = (step: SigningPhase | undefined, network: Networks): step is SigningPhase => { + if (!step) return false; + if (!['started', 'approved', 'signed'].includes(step)) return false; + if (!isNetworkEVM(network) && (step === 'approved' || step === 'signed')) return false; + return true; +}; + export const SigningBox: FC = ({ step }) => { const { selectedNetwork } = useNetwork(); - if (!step) return null; - if (!['started', 'approved', 'signed'].includes(step)) return null; - if (!isNetworkEVM(selectedNetwork) && (step === 'approved' || step === 'signed')) return null; + if (!isValidStep(step, selectedNetwork)) return null; - const progressValue = getProgressConfig(selectedNetwork)[step] || '0'; + const progressValue = getProgressConfig(selectedNetwork)[step]; const { maxSignatures, getSignatureNumber } = getSignatureConfig(selectedNetwork); return ( From 135dd75db5478785f99823eebbe33cc89e043226 Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Thu, 2 Jan 2025 18:09:23 +0100 Subject: [PATCH 20/32] refactor useSwapForm --- src/components/Nabla/useSwapForm.tsx | 64 +++++++++++++--------------- 1 file changed, 29 insertions(+), 35 deletions(-) diff --git a/src/components/Nabla/useSwapForm.tsx b/src/components/Nabla/useSwapForm.tsx index 7cb73881..4d916484 100644 --- a/src/components/Nabla/useSwapForm.tsx +++ b/src/components/Nabla/useSwapForm.tsx @@ -15,50 +15,46 @@ import { storageService } from '../../services/storage/local'; import schema, { SwapFormValues } from './schema'; import { Networks, useNetwork } from '../../contexts/network'; -interface SwapSettings { +type SwapSettings = { from: string; to: string; -} +}; + +type TokenSelectType = 'from' | 'to'; + +type NetworkMap = Record, Networks>; + +const NETWORK_MAPPING: NetworkMap = { + assethub: Networks.AssetHub, + polygon: Networks.Polygon, + ethereum: Networks.Ethereum, + bsc: Networks.BSC, + arbitrum: Networks.Arbitrum, + base: Networks.Base, + avalanche: Networks.Avalanche, +}; const storageSet = debounce(storageService.set, 1000); const setStorageForSwapSettings = storageSet.bind(null, storageKeys.SWAP_SETTINGS); export function getCaseSensitiveNetwork(network: string): Networks { - const lowercasedNetwork = network.toLowerCase(); - - switch (lowercasedNetwork) { - case 'assethub': - return Networks.AssetHub; - case 'polygon': - return Networks.Polygon; - case 'ethereum': - return Networks.Ethereum; - case 'bsc': - return Networks.BSC; - case 'arbitrum': - return Networks.Arbitrum; - case 'base': - return Networks.Base; - case 'avalanche': - return Networks.Avalanche; - default: - console.warn('getCaseSensitiveNetwork: Invalid network type'); - return Networks.AssetHub; - } + const lowercasedNetwork = network.toLowerCase() as keyof typeof NETWORK_MAPPING; + return NETWORK_MAPPING[lowercasedNetwork] ?? Networks.AssetHub; } -// Helper function to merge values if they are defined -function mergeIfDefined(target: any, source: any) { - for (const key in source) { - if (source[key] !== undefined && source[key] !== null) { - target[key] = source[key]; +function mergeIfDefined(target: T, source: Nullable | undefined): void { + if (!source) return; + + Object.entries(source).forEach(([key, value]) => { + if (value != null) { + target[key as keyof T] = value as T[keyof T]; } - } + }); } export const useSwapForm = () => { const [isTokenSelectModalVisible, setIsTokenSelectModalVisible] = useState(false); - const [tokenSelectModalType, setTokenModalType] = useState<'from' | 'to'>('from'); + const [tokenSelectModalType, setTokenModalType] = useState('from'); const { selectedNetwork, setSelectedNetwork } = useNetwork(); const initialState = useMemo(() => { @@ -112,14 +108,13 @@ export const useSwapForm = () => { (tokenKey: string) => { const prev = getValues(); - const updated = { + const updated: SwapSettings = { from: tokenKey, to: prev?.to, }; setStorageForSwapSettings(updated); setValue('from', tokenKey as InputTokenType); - setIsTokenSelectModalVisible(false); }, [getValues, setValue], @@ -130,14 +125,13 @@ export const useSwapForm = () => { const prev = getValues(); if (!tokenKey) return; - const updated = { + const updated: SwapSettings = { to: tokenKey, from: prev?.from, }; setStorageForSwapSettings(updated); setValue('to', tokenKey as OutputTokenType); - setIsTokenSelectModalVisible(false); }, [getValues, setValue], @@ -157,7 +151,7 @@ export const useSwapForm = () => { } }, [fromAmountString]); - const openTokenSelectModal = useCallback((type: 'from' | 'to') => { + const openTokenSelectModal = useCallback((type: TokenSelectType) => { setTokenModalType(type); setIsTokenSelectModalVisible(true); }, []); From 7a837249e706fbbcdbd243e5ab86cfead8ceb8d6 Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Fri, 3 Jan 2025 13:48:33 +0100 Subject: [PATCH 21/32] refactor sep logic --- src/hooks/offramp/useMainProcess.ts | 11 +- .../useSEP24/useAnchorWindowHandler.ts | 2 +- src/hooks/offramp/useSubmitOfframp.ts | 26 +- src/services/anchor/index.ts | 308 ------------------ src/services/anchor/sep10/challenge.ts | 39 +++ src/services/anchor/sep10/index.ts | 85 +++++ src/services/anchor/sep10/utils.ts | 77 +++++ src/services/anchor/sep24/first.ts | 46 +++ src/services/anchor/sep24/second.ts | 68 ++++ src/services/offrampingFlow.ts | 2 +- src/services/phases/stellar/index.tsx | 2 +- src/services/stellar/index.ts | 28 ++ src/stores/sep24Store.ts | 53 ++- src/types/offramp.ts | 3 +- src/types/sep.ts | 28 ++ 15 files changed, 422 insertions(+), 356 deletions(-) delete mode 100644 src/services/anchor/index.ts create mode 100644 src/services/anchor/sep10/challenge.ts create mode 100644 src/services/anchor/sep10/index.ts create mode 100644 src/services/anchor/sep10/utils.ts create mode 100644 src/services/anchor/sep24/first.ts create mode 100644 src/services/anchor/sep24/second.ts create mode 100644 src/services/stellar/index.ts create mode 100644 src/types/sep.ts diff --git a/src/hooks/offramp/useMainProcess.ts b/src/hooks/offramp/useMainProcess.ts index b681a038..400bdfa9 100644 --- a/src/hooks/offramp/useMainProcess.ts +++ b/src/hooks/offramp/useMainProcess.ts @@ -9,7 +9,7 @@ import { useSubmitOfframp } from './useSubmitOfframp'; import { useOfframpEvents } from './useOfframpEvents'; import { useOfframpAdvancement } from './useOfframpAdvancement'; import { useOfframpActions, useOfframpState } from '../../stores/offrampStore'; -import { useFirstSep24Interval, useFirstSep24Response, useSep24Store } from '../../stores/sep24Store'; +import { useSep24UrlInterval, useSep24InitialResponse } from '../../stores/sep24Store'; import { useSep24Actions } from '../../stores/sep24Store'; import { useAnchorWindowHandler } from './useSEP24/useAnchorWindowHandler'; export type SigningPhase = 'started' | 'approved' | 'signed' | 'finished'; @@ -27,10 +27,10 @@ export const useMainProcess = () => { const offrampState = useOfframpState(); // Sep 24 states - const firstSep24Response = useFirstSep24Response(); - const firstSep24Interval = useFirstSep24Interval(); + const firstSep24Response = useSep24InitialResponse(); + const firstSep24Interval = useSep24UrlInterval(); - const { cleanupSep24State } = useSep24Actions(); + const { cleanup: cleanupSep24 } = useSep24Actions(); // Custom hooks const events = useOfframpEvents(); @@ -59,11 +59,10 @@ export const useMainProcess = () => { updateOfframpHookStateFromState(recoverFromFailure(offrampState)); }, handleOnAnchorWindowOpen: handleOnAnchorWindowOpen, - // @todo: why do we need this? maybeCancelSep24First: () => { if (firstSep24Interval !== undefined) { setOfframpStarted(false); - cleanupSep24State(); + cleanupSep24(); } }, }; diff --git a/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts b/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts index b64e6fd1..47a3a6e6 100644 --- a/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts +++ b/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts @@ -4,7 +4,7 @@ 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/first'; import { showToast, ToastMessage } from '../../../helpers/notifications'; diff --git a/src/hooks/offramp/useSubmitOfframp.ts b/src/hooks/offramp/useSubmitOfframp.ts index d7d7e48b..2fa75cec 100644 --- a/src/hooks/offramp/useSubmitOfframp.ts +++ b/src/hooks/offramp/useSubmitOfframp.ts @@ -8,7 +8,10 @@ import { useSiweContext } from '../../contexts/siwe'; import { calculateTotalReceive } from '../../components/FeeCollapse'; import { getInputTokenDetailsOrDefault, OUTPUT_TOKEN_CONFIG } from '../../constants/tokenConfig'; -import { createStellarEphemeralSecret, fetchTomlValues, 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 { ExecutionInput } from './useMainProcess'; @@ -23,8 +26,13 @@ export const useSubmitOfframp = () => { const offrampStarted = useOfframpStarted(); const offrampState = useOfframpState(); const { setOfframpStarted, setOfframpInitiating } = useOfframpActions(); - const { setAnchorSessionParams, setFirstSep24Response, setExecutionInput, cleanupSep24State, setFirstSep24Interval } = - useSep24Actions(); + const { + setAnchorSessionParams, + setExecutionInput, + setInitialResponse: setInitialResponseSEP24, + setUrlInterval: setUrlIntervalSEP24, + cleanup: cleanupSEP24, + } = useSep24Actions(); return useCallback( (executionInput: ExecutionInput) => { @@ -98,10 +106,10 @@ export const useSubmitOfframp = () => { const url = new URL(firstSep24Response.url); url.searchParams.append('callback', 'postMessage'); firstSep24Response.url = url.toString(); - setFirstSep24Response(firstSep24Response); + setInitialResponseSEP24(firstSep24Response); }; - setFirstSep24Interval(window.setInterval(fetchAndUpdateSep24Url, 20000)); + setUrlIntervalSEP24(window.setInterval(fetchAndUpdateSep24Url, 20000)); try { await fetchAndUpdateSep24Url(); @@ -109,7 +117,7 @@ export const useSubmitOfframp = () => { console.error('Error finalizing the initial state of the offramping process', error); setInitializeFailed(); setOfframpStarted(false); - cleanupSep24State(); + cleanupSEP24(); } finally { setOfframpInitiating(false); } @@ -139,9 +147,9 @@ export const useSubmitOfframp = () => { forceRefreshAndWaitForSignature, setExecutionInput, setAnchorSessionParams, - setFirstSep24Response, - setFirstSep24Interval, - cleanupSep24State, + setInitialResponseSEP24, + setUrlIntervalSEP24, + cleanupSEP24, switchChainAsync, ], ); diff --git a/src/services/anchor/index.ts b/src/services/anchor/index.ts deleted file mode 100644 index 93f9a73d..00000000 --- a/src/services/anchor/index.ts +++ /dev/null @@ -1,308 +0,0 @@ -import { Transaction, Keypair, Networks } from 'stellar-sdk'; -import { keccak256 } from 'viem/utils'; -import { Keyring } from '@polkadot/api'; - -import { fetchSep10Signatures, fetchSigningServiceAccountId, SignerServiceSep10Request } from '../signingService'; -import { OutputTokenDetails, OutputTokenType } from '../../constants/tokenConfig'; - -import { OUTPUT_TOKEN_CONFIG } from '../../constants/tokenConfig'; -import { SIGNING_SERVICE_URL } from '../../constants/constants'; -import { config } from '../../config'; - -interface TomlValues { - signingKey?: string; - webAuthEndpoint?: string; - sep24Url?: string; - sep6Url?: string; - kycServer?: string; -} - -export interface ISep24Intermediate { - url: string; - id: string; -} - -export interface IAnchorSessionParams { - token: string; - tomlValues: TomlValues; - tokenConfig: OutputTokenDetails; - offrampAmount: string; -} - -export interface SepResult { - amount: string; - memo: string; - memoType: string; - offrampingAccount: string; -} - -export function createStellarEphemeralSecret() { - const ephemeralKeys = Keypair.random(); - return ephemeralKeys.secret(); -} - -const exists = (value?: string | null): value is string => !!value && value?.length > 0; - -export const fetchTomlValues = async (TOML_FILE_URL: string): Promise => { - const response = await fetch(TOML_FILE_URL); - if (response.status !== 200) { - throw new Error(`Failed to fetch TOML file: ${response.statusText}`); - } - - const tomlFileContent = (await response.text()).split('\n'); - const findValueInToml = (key: string): string | undefined => { - const keyValue = tomlFileContent.find((line) => line.includes(key)); - return keyValue?.split('=')[1].trim().replaceAll('"', ''); - }; - - return { - signingKey: findValueInToml('SIGNING_KEY'), - webAuthEndpoint: findValueInToml('WEB_AUTH_ENDPOINT'), - sep24Url: findValueInToml('TRANSFER_SERVER_SEP0024'), - sep6Url: findValueInToml('TRANSFER_SERVER'), - kycServer: findValueInToml('KYC_SERVER'), - }; -}; - -// Returns the hash value for the address. If it's a polkadot address, it will return raw data of the address. -function getHashValueForAddress(address: string) { - if (address.startsWith('0x')) { - return address as `0x${string}`; - } else { - const keyring = new Keyring({ type: 'sr25519' }); - return keyring.decodeAddress(address); - } -} - -//A memo derivation. -async function deriveMemoFromAddress(address: string) { - const hashValue = getHashValueForAddress(address); - const hash = keccak256(hashValue); - return BigInt(hash).toString().slice(0, 15); -} - -// Return the URLSearchParams and the account (master/omnibus or ephemeral) that was used for SEP-10 -async function getUrlParams( - ephemeralAccount: string, - usesMemo: boolean, - supportsClientDomain: boolean, - address: string, -): Promise<{ urlParams: URLSearchParams; sep10Account: string }> { - let sep10Account: string; - const params = new URLSearchParams(); - - if (usesMemo) { - const response = await fetch(`${SIGNING_SERVICE_URL}/v1/stellar/sep10`); - - if (!response.ok) { - throw new Error('Failed to fetch client master SEP-10 public account.'); - } - - const { masterSep10Public } = await response.json(); - - if (!masterSep10Public) { - throw new Error('masterSep10Public not found in response.'); - } - - sep10Account = masterSep10Public; - params.append('account', sep10Account); - params.append('memo', await deriveMemoFromAddress(address)); - } else { - sep10Account = ephemeralAccount; - params.append('account', sep10Account); - } - - if (supportsClientDomain) { - params.append('client_domain', config.applicationClientDomain); - } - - return { - urlParams: params, - sep10Account, - }; -} - -const sep10SignaturesWithLoginRefresh = async ( - refreshFunction: () => Promise, - args: SignerServiceSep10Request, -) => { - try { - return await fetchSep10Signatures(args); - } catch (error: unknown) { - if (error instanceof Error && error.message === 'Invalid signature') { - await refreshFunction(); - return await fetchSep10Signatures(args); - } - throw new Error('Could not fetch sep 10 signatures from backend'); - } -}; - -export const sep10 = async ( - tomlValues: TomlValues, - stellarEphemeralSecret: string, - outputToken: OutputTokenType, - address: string, - checkAndWaitForSignature: () => Promise, - forceRefreshAndWaitForSignature: () => Promise, -): Promise<{ token: string; sep10Account: string }> => { - const { signingKey, webAuthEndpoint } = tomlValues; - - if (!exists(signingKey) || !exists(webAuthEndpoint)) { - throw new Error('sep10: Missing values in TOML file'); - } - const NETWORK_PASSPHRASE = Networks.PUBLIC; - const ephemeralKeys = Keypair.fromSecret(stellarEphemeralSecret); - const accountId = ephemeralKeys.publicKey(); - - const { usesMemo, supportsClientDomain } = OUTPUT_TOKEN_CONFIG[outputToken]; - - // will select either clientMaster or the ephemeral account - const { urlParams, sep10Account } = await getUrlParams(accountId, usesMemo, supportsClientDomain, address); - - const challenge = await fetch(`${webAuthEndpoint}?${urlParams.toString()}`); - if (challenge.status !== 200) { - throw new Error(`sep10: Failed to fetch SEP-10 challenge: ${challenge.statusText}`); - } - - const { transaction, network_passphrase } = await challenge.json(); - if (network_passphrase !== NETWORK_PASSPHRASE) { - throw new Error(`sep10: Invalid network passphrase: ${network_passphrase}`); - } - - const transactionSigned = new Transaction(transaction, NETWORK_PASSPHRASE); - if (transactionSigned.source !== signingKey) { - throw new Error(`sep10: Invalid source account: ${transactionSigned.source}`); - } - if (transactionSigned.sequence !== '0') { - throw new Error(`sep10: Invalid sequence number: ${transactionSigned.sequence}`); - } - - if (usesMemo) { - await checkAndWaitForSignature(); - } - - const { masterClientSignature, clientSignature, clientPublic } = await sep10SignaturesWithLoginRefresh( - forceRefreshAndWaitForSignature, - { - challengeXDR: transactionSigned.toXDR(), - outToken: outputToken, - clientPublicKey: sep10Account, - usesMemo, - address: address, - }, - ); - - if (supportsClientDomain) { - transactionSigned.addSignature(clientPublic, clientSignature); - } - - if (!usesMemo) { - transactionSigned.sign(ephemeralKeys); - } else { - transactionSigned.addSignature(sep10Account, masterClientSignature); - } - - const jwt = await fetch(webAuthEndpoint, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ transaction: transactionSigned.toXDR().toString() }), - }); - - if (jwt.status !== 200) { - throw new Error(`Failed to submit SEP-10 response: ${jwt.statusText}`); - } - - const { token } = await jwt.json(); - return { token, sep10Account }; -}; - -export async function sep24First( - sessionParams: IAnchorSessionParams, - sep10Account: string, - outputToken: OutputTokenType, -): Promise { - if (config.test.mockSep24) { - return { url: 'https://www.example.com', id: '1234' }; - } - - const { token, tomlValues } = sessionParams; - const { sep24Url } = tomlValues; - - const { usesMemo } = OUTPUT_TOKEN_CONFIG[outputToken]; - - let sep24Params; - if (usesMemo) { - sep24Params = new URLSearchParams({ - asset_code: sessionParams.tokenConfig.stellarAsset.code.string, - amount: sessionParams.offrampAmount, - account: sep10Account, // THIS is a particularity of Anclap. Should be able to work just with the epmhemeral account - // or at least the anchor should be able to get it from the JWT. - }); - } else { - sep24Params = new URLSearchParams({ - asset_code: sessionParams.tokenConfig.stellarAsset.code.string, - amount: sessionParams.offrampAmount, - }); - } - - const fetchUrl = `${sep24Url}/transactions/withdraw/interactive`; - const sep24Response = await fetch(fetchUrl, { - method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded', Authorization: `Bearer ${token}` }, - body: sep24Params.toString(), - }); - if (sep24Response.status !== 200) { - console.log(await sep24Response.json(), sep24Params.toString()); - throw new Error(`Failed to initiate SEP-24: ${sep24Response.statusText}`); - } - - const { type, url, id } = await sep24Response.json(); - if (type !== 'interactive_customer_info_needed') { - throw new Error(`Unexpected SEP-24 type: ${type}`); - } - - return { url, id }; -} - -export async function sep24Second( - sep24Values: ISep24Intermediate, - sessionParams: IAnchorSessionParams, -): Promise { - const { id } = sep24Values; - const { token, tomlValues } = sessionParams; - const { sep24Url } = tomlValues; - - if (config.test.mockSep24) { - // sleep 10 secs - await new Promise((resolve) => setTimeout(resolve, 10000)); - return { - amount: sessionParams.offrampAmount, - memo: 'MYK1722323689', - memoType: 'text', - offrampingAccount: (await fetchSigningServiceAccountId()).stellar.public, - }; - } - - let status; - do { - await new Promise((resolve) => setTimeout(resolve, 1000)); - const idParam = new URLSearchParams({ id }); - const statusResponse = await fetch(`${sep24Url}/transaction?${idParam.toString()}`, { - headers: { Authorization: `Bearer ${token}` }, - }); - - if (statusResponse.status !== 200) { - throw new Error(`Failed to fetch SEP-24 status: ${statusResponse.statusText}`); - } - - const { transaction } = await statusResponse.json(); - status = transaction; - } while (status.status !== 'pending_user_transfer_start'); - - return { - amount: status.amount_in, - memo: status.withdraw_memo, - memoType: status.withdraw_memo_type, - offrampingAccount: status.withdraw_anchor_account, - }; -} diff --git a/src/services/anchor/sep10/challenge.ts b/src/services/anchor/sep10/challenge.ts new file mode 100644 index 00000000..f3be5eee --- /dev/null +++ b/src/services/anchor/sep10/challenge.ts @@ -0,0 +1,39 @@ +import { Transaction, Networks, Memo, Operation, MemoType } from 'stellar-sdk'; + +interface Sep10Challenge { + transaction: string; + network_passphrase: string; +} + +async function validateChallenge( + transaction: Transaction, Operation[]>, + signingKey: string, + networkPassphrase: string, +): Promise { + if (transaction.source !== signingKey) { + throw new Error(`sep10: Invalid source account: ${transaction.source}`); + } + if (transaction.sequence !== '0') { + throw new Error(`sep10: Invalid sequence number: ${transaction.sequence}`); + } + if (networkPassphrase !== Networks.PUBLIC) { + throw new Error(`sep10: Invalid network passphrase: ${networkPassphrase}`); + } +} + +export async function fetchAndValidateChallenge( + webAuthEndpoint: string, + urlParams: URLSearchParams, + signingKey: string, +): Promise, Operation[]>> { + const challenge = await fetch(`${webAuthEndpoint}?${urlParams.toString()}`); + if (challenge.status !== 200) { + throw new Error(`sep10: Failed to fetch SEP-10 challenge: ${challenge.statusText}`); + } + + const { transaction, network_passphrase } = (await challenge.json()) as Sep10Challenge; + const transactionSigned = new Transaction(transaction, Networks.PUBLIC); + await validateChallenge(transactionSigned, signingKey, network_passphrase); + + return transactionSigned; +} diff --git a/src/services/anchor/sep10/index.ts b/src/services/anchor/sep10/index.ts new file mode 100644 index 00000000..7a1740e2 --- /dev/null +++ b/src/services/anchor/sep10/index.ts @@ -0,0 +1,85 @@ +import { Transaction, Keypair, Memo, Operation, MemoType } from 'stellar-sdk'; + +import { OUTPUT_TOKEN_CONFIG } from '../../../constants/tokenConfig'; +import { OutputTokenType } from '../../../constants/tokenConfig'; +import { TomlValues } from '../../../types/sep'; + +import { exists, getUrlParams, sep10SignaturesWithLoginRefresh } from './utils'; +import { fetchAndValidateChallenge } from './challenge'; + +interface Sep10Response { + token: string; + sep10Account: string; +} + +interface Sep10JwtResponse { + token: string; +} + +async function submitSignedTransaction( + webAuthEndpoint: string, + transaction: Transaction, Operation[]>, +): Promise { + const jwt = await fetch(webAuthEndpoint, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ transaction: transaction.toXDR().toString() }), + }); + + if (jwt.status !== 200) { + throw new Error(`Failed to submit SEP-10 response: ${jwt.statusText}`); + } + + const { token } = (await jwt.json()) as Sep10JwtResponse; + return token; +} + +export async function sep10( + tomlValues: TomlValues, + stellarEphemeralSecret: string, + outputToken: OutputTokenType, + address: string, + checkAndWaitForSignature: () => Promise, + forceRefreshAndWaitForSignature: () => Promise, +): Promise { + const { signingKey, webAuthEndpoint } = tomlValues; + + if (!exists(signingKey) || !exists(webAuthEndpoint)) { + throw new Error('sep10: Missing values in TOML file'); + } + + const ephemeralKeys = Keypair.fromSecret(stellarEphemeralSecret); + const accountId = ephemeralKeys.publicKey(); + const { usesMemo, supportsClientDomain } = OUTPUT_TOKEN_CONFIG[outputToken]; + + const { urlParams, sep10Account } = await getUrlParams(accountId, usesMemo, supportsClientDomain, address); + const transactionSigned = await fetchAndValidateChallenge(webAuthEndpoint, urlParams, signingKey); + + if (usesMemo) { + await checkAndWaitForSignature(); + } + + const { masterClientSignature, clientSignature, clientPublic } = await sep10SignaturesWithLoginRefresh( + forceRefreshAndWaitForSignature, + { + challengeXDR: transactionSigned.toXDR(), + outToken: outputToken, + clientPublicKey: sep10Account, + usesMemo, + address: address, + }, + ); + + if (supportsClientDomain) { + transactionSigned.addSignature(clientPublic, clientSignature); + } + + if (!usesMemo) { + transactionSigned.sign(ephemeralKeys); + } else { + transactionSigned.addSignature(sep10Account, masterClientSignature); + } + + const token = await submitSignedTransaction(webAuthEndpoint, transactionSigned); + return { token, sep10Account }; +} diff --git a/src/services/anchor/sep10/utils.ts b/src/services/anchor/sep10/utils.ts new file mode 100644 index 00000000..d8a64c72 --- /dev/null +++ b/src/services/anchor/sep10/utils.ts @@ -0,0 +1,77 @@ +import { Keyring } from '@polkadot/api'; +import { keccak256 } from 'viem/utils'; + +import { fetchSep10Signatures, SignerServiceSep10Request } from '../../signingService'; +import { SIGNING_SERVICE_URL } from '../../../constants/constants'; +import { config } from '../../../config'; + +// Returns the hash value for the address. +// If it's a polkadot address, it will return raw data of the address. +function getHashValueForAddress(address: string) { + if (address.startsWith('0x')) { + return address as `0x${string}`; + } else { + const keyring = new Keyring({ type: 'sr25519' }); + return keyring.decodeAddress(address); + } +} + +// A memo derivation. +async function deriveMemoFromAddress(address: string) { + const hashValue = getHashValueForAddress(address); + const hash = keccak256(hashValue); + return BigInt(hash).toString().slice(0, 15); +} + +export const exists = (value?: string | null): value is string => !!value && value?.length > 0; + +export async function sep10SignaturesWithLoginRefresh( + refreshFunction: () => Promise, + args: SignerServiceSep10Request, +) { + try { + return await fetchSep10Signatures(args); + } catch (error: unknown) { + if (error instanceof Error && error.message === 'Invalid signature') { + await refreshFunction(); + return await fetchSep10Signatures(args); + } + throw new Error('Could not fetch sep 10 signatures from backend'); + } +} + +// Return the URLSearchParams and the account (master/omnibus or ephemeral) that was used for SEP-10 +export async function getUrlParams( + ephemeralAccount: string, + usesMemo: boolean, + supportsClientDomain: boolean, + address: string, +): Promise<{ urlParams: URLSearchParams; sep10Account: string }> { + let sep10Account: string; + const params = new URLSearchParams(); + + if (usesMemo) { + const response = await fetch(`${SIGNING_SERVICE_URL}/v1/stellar/sep10`); + if (!response.ok) { + throw new Error('Failed to fetch client master SEP-10 public account.'); + } + + const { masterSep10Public } = await response.json(); + if (!masterSep10Public) { + throw new Error('masterSep10Public not found in response.'); + } + + sep10Account = masterSep10Public; + params.append('account', sep10Account); + params.append('memo', await deriveMemoFromAddress(address)); + } else { + sep10Account = ephemeralAccount; + params.append('account', sep10Account); + } + + if (supportsClientDomain) { + params.append('client_domain', config.applicationClientDomain); + } + + return { urlParams: params, sep10Account }; +} diff --git a/src/services/anchor/sep24/first.ts b/src/services/anchor/sep24/first.ts new file mode 100644 index 00000000..7d0fd652 --- /dev/null +++ b/src/services/anchor/sep24/first.ts @@ -0,0 +1,46 @@ +import { IAnchorSessionParams, ISep24Intermediate } from '../../../types/sep'; +import { OUTPUT_TOKEN_CONFIG } from '../../../constants/tokenConfig'; +import { OutputTokenType } from '../../../constants/tokenConfig'; +import { config } from '../../../config'; + +export async function sep24First( + sessionParams: IAnchorSessionParams, + ANCLAP_sep10Account: string, + outputToken: OutputTokenType, +): Promise { + if (config.test.mockSep24) { + return { url: 'https://www.example.com', id: '1234' }; + } + + const { token, tomlValues, offrampAmount } = sessionParams; + const { sep24Url } = tomlValues; + const { usesMemo } = OUTPUT_TOKEN_CONFIG[outputToken]; + const assetCode = sessionParams.tokenConfig.stellarAsset.code.string; + + const params = new URLSearchParams({ + asset_code: assetCode, + amount: offrampAmount, + ...(usesMemo && { account: ANCLAP_sep10Account }), + }); + + const response = await fetch(`${sep24Url}/transactions/withdraw/interactive`, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Authorization: `Bearer ${token}`, + }, + body: params.toString(), + }); + + if (response.status !== 200) { + console.log(await response.json(), params.toString()); + throw new Error(`Failed to initiate SEP-24: ${response.statusText}`); + } + + const { type, url, id } = await response.json(); + if (type !== 'interactive_customer_info_needed') { + throw new Error(`Unexpected SEP-24 type: ${type}`); + } + + return { url, id }; +} diff --git a/src/services/anchor/sep24/second.ts b/src/services/anchor/sep24/second.ts new file mode 100644 index 00000000..0c643e55 --- /dev/null +++ b/src/services/anchor/sep24/second.ts @@ -0,0 +1,68 @@ +import { IAnchorSessionParams, ISep24Intermediate, SepResult } from '../../../types/sep'; +import { fetchSigningServiceAccountId } from '../../signingService'; +import { config } from '../../../config'; + +interface Sep24TransactionStatus { + status: string; + amount_in: string; + withdraw_memo: string; + withdraw_memo_type: string; + withdraw_anchor_account: string; +} + +const POLLING_INTERVAL = 1000; + +async function fetchTransactionStatus(id: string, token: string, sep24Url: string): Promise { + const idParam = new URLSearchParams({ id }); + const statusResponse = await fetch(`${sep24Url}/transaction?${idParam.toString()}`, { + headers: { Authorization: `Bearer ${token}` }, + }); + + if (statusResponse.status !== 200) { + throw new Error(`Failed to fetch SEP-24 status: ${statusResponse.statusText}`); + } + + const { transaction } = await statusResponse.json(); + return transaction; +} + +async function pollTransactionStatus(id: string, sessionParams: IAnchorSessionParams): Promise { + const { token, tomlValues } = sessionParams; + let status: Sep24TransactionStatus; + + if (!tomlValues.sep24Url) { + throw new Error('Missing SEP-24 URL in TOML values'); + } + + do { + await new Promise((resolve) => setTimeout(resolve, POLLING_INTERVAL)); + status = await fetchTransactionStatus(id, token, tomlValues.sep24Url); + } while (status.status !== 'pending_user_transfer_start'); + + return status; +} + +export async function sep24Second( + sep24Values: ISep24Intermediate, + sessionParams: IAnchorSessionParams, +): Promise { + if (config.test.mockSep24) { + // sleep 10 secs + await new Promise((resolve) => setTimeout(resolve, 10000)); + return { + amount: sessionParams.offrampAmount, + memo: 'MYK1722323689', + memoType: 'text', + offrampingAccount: (await fetchSigningServiceAccountId()).stellar.public, + }; + } + + const status = await pollTransactionStatus(sep24Values.id, sessionParams); + + return { + amount: status.amount_in, + memo: status.withdraw_memo, + memoType: status.withdraw_memo_type, + offrampingAccount: status.withdraw_anchor_account, + }; +} diff --git a/src/services/offrampingFlow.ts b/src/services/offrampingFlow.ts index 634f767f..5675b0a1 100644 --- a/src/services/offrampingFlow.ts +++ b/src/services/offrampingFlow.ts @@ -10,7 +10,7 @@ import { u8aToHex } from '@polkadot/util'; import { SigningPhase } from '../hooks/offramp/useMainProcess'; import { TrackableEvent } from '../contexts/events'; import { isNetworkEVM, Networks } from '../contexts/network'; -import { SepResult } from './anchor'; +import { SepResult } from '../types/sep'; import { getInputTokenDetailsOrDefault, diff --git a/src/services/phases/stellar/index.tsx b/src/services/phases/stellar/index.tsx index c233fff1..e5c7e53b 100644 --- a/src/services/phases/stellar/index.tsx +++ b/src/services/phases/stellar/index.tsx @@ -22,7 +22,7 @@ import { } from '../../../constants/constants'; import { fetchSigningServiceAccountId } from '../../signingService'; import { OfframpingState } from '../../offrampingFlow'; -import { SepResult } from '../../anchor'; +import { SepResult } from '../../anchor/sep24/first'; const horizonServer = new Horizon.Server(HORIZON_URL); const NETWORK_PASSPHRASE = Networks.PUBLIC; diff --git a/src/services/stellar/index.ts b/src/services/stellar/index.ts new file mode 100644 index 00000000..a1ed29e9 --- /dev/null +++ b/src/services/stellar/index.ts @@ -0,0 +1,28 @@ +import { Keypair } from 'stellar-sdk'; +import { TomlValues } from '../../types/sep'; + +export function createStellarEphemeralSecret() { + const ephemeralKeys = Keypair.random(); + return ephemeralKeys.secret(); +} + +export const fetchTomlValues = async (TOML_FILE_URL: string): Promise => { + const response = await fetch(TOML_FILE_URL); + if (response.status !== 200) { + throw new Error(`Failed to fetch TOML file: ${response.statusText}`); + } + + const tomlFileContent = (await response.text()).split('\n'); + const findValueInToml = (key: string): string | undefined => { + const keyValue = tomlFileContent.find((line) => line.includes(key)); + return keyValue?.split('=')[1].trim().replaceAll('"', ''); + }; + + return { + signingKey: findValueInToml('SIGNING_KEY'), + webAuthEndpoint: findValueInToml('WEB_AUTH_ENDPOINT'), + sep24Url: findValueInToml('TRANSFER_SERVER_SEP0024'), + sep6Url: findValueInToml('TRANSFER_SERVER'), + kycServer: findValueInToml('KYC_SERVER'), + }; +}; diff --git a/src/stores/sep24Store.ts b/src/stores/sep24Store.ts index 35a5b935..139d2f93 100644 --- a/src/stores/sep24Store.ts +++ b/src/stores/sep24Store.ts @@ -1,23 +1,23 @@ import { create } from 'zustand'; -import { IAnchorSessionParams, ISep24Intermediate } from '../services/anchor'; +import { IAnchorSessionParams, ISep24Intermediate } from '../types/sep'; import { ExecutionInput } from '../hooks/offramp/useMainProcess'; export type ExtendedExecutionInput = ExecutionInput & { stellarEphemeralSecret: string }; export interface Sep24State { anchorSessionParams: IAnchorSessionParams | undefined; - firstSep24Response: ISep24Intermediate | undefined; + initialResponse: ISep24Intermediate | undefined; executionInput: ExtendedExecutionInput | undefined; - firstSep24Interval: number | undefined; + urlInterval: number | undefined; } export interface Sep24Actions { setAnchorSessionParams: (params: IAnchorSessionParams | undefined) => void; - setFirstSep24Response: (response: ISep24Intermediate | undefined) => void; + setInitialResponse: (response: ISep24Intermediate | undefined) => void; setExecutionInput: (input: ExtendedExecutionInput | undefined) => void; - setFirstSep24Interval: (interval: number | undefined) => void; - resetSep24State: () => void; - cleanupSep24State: () => void; + setUrlInterval: (interval: number | undefined) => void; + reset: () => void; + cleanup: () => void; } interface Sep24Store extends Sep24State { @@ -25,37 +25,33 @@ interface Sep24Store extends Sep24State { } export const useSep24Store = create()((set, get) => ({ - // Initial state anchorSessionParams: undefined, - firstSep24Response: undefined, + initialResponse: undefined, executionInput: undefined, - firstSep24Interval: undefined, + urlInterval: undefined, actions: { - // Setters setAnchorSessionParams: (params) => set({ anchorSessionParams: params }), - setFirstSep24Response: (response) => set({ firstSep24Response: response }), + setInitialResponse: (response) => set({ initialResponse: response }), setExecutionInput: (input) => set({ executionInput: input }), - setFirstSep24Interval: (interval) => set({ firstSep24Interval: interval }), + setUrlInterval: (interval) => set({ urlInterval: interval }), - // Reset all state - resetSep24State: () => + reset: () => set({ anchorSessionParams: undefined, - firstSep24Response: undefined, + initialResponse: undefined, executionInput: undefined, - firstSep24Interval: undefined, + urlInterval: undefined, }), - // Cleanup action - cleanupSep24State: () => { - const { firstSep24Interval } = get(); + cleanup: () => { + const { urlInterval } = get(); const actions = get().actions; - if (firstSep24Interval !== undefined) { - clearInterval(firstSep24Interval); - actions.setFirstSep24Interval(undefined); - actions.setFirstSep24Response(undefined); + if (urlInterval !== undefined) { + clearInterval(urlInterval); + actions.setUrlInterval(undefined); + actions.setInitialResponse(undefined); actions.setExecutionInput(undefined); actions.setAnchorSessionParams(undefined); } @@ -63,9 +59,8 @@ export const useSep24Store = create()((set, get) => ({ }, })); -// Selector hooks export const useSep24Actions = () => useSep24Store((state) => state.actions); -export const useFirstSep24Response = () => useSep24Store((state) => state.firstSep24Response); -export const useFirstSep24Interval = () => useSep24Store((state) => state.firstSep24Interval); -export const useAnchorSessionParams = () => useSep24Store((state) => state.anchorSessionParams); -export const useExecutionInput = () => useSep24Store((state) => state.executionInput); +export const useSep24InitialResponse = () => useSep24Store((state) => state.initialResponse); +export const useSep24UrlInterval = () => useSep24Store((state) => state.urlInterval); +export const useSep24AnchorSessionParams = () => useSep24Store((state) => state.anchorSessionParams); +export const useSep24ExecutionInput = () => useSep24Store((state) => state.executionInput); diff --git a/src/types/offramp.ts b/src/types/offramp.ts index 22587612..305299ff 100644 --- a/src/types/offramp.ts +++ b/src/types/offramp.ts @@ -1,8 +1,9 @@ import { StateUpdater } from 'preact/hooks'; import Big from 'big.js'; + import { OfframpingState } from '../services/offrampingFlow'; import { InputTokenType, OutputTokenType } from '../constants/tokenConfig'; -import { ISep24Intermediate, IAnchorSessionParams } from '../services/anchor'; +import { ISep24Intermediate, IAnchorSessionParams } from './sep'; export type OfframpSigningPhase = 'started' | 'approved' | 'signed' | 'finished'; diff --git a/src/types/sep.ts b/src/types/sep.ts new file mode 100644 index 00000000..fec4d053 --- /dev/null +++ b/src/types/sep.ts @@ -0,0 +1,28 @@ +import { OutputTokenDetails } from '../constants/tokenConfig'; + +export interface TomlValues { + signingKey?: string; + webAuthEndpoint?: string; + sep24Url?: string; + sep6Url?: string; + kycServer?: string; +} + +export interface ISep24Intermediate { + url: string; + id: string; +} + +export interface IAnchorSessionParams { + token: string; + tomlValues: TomlValues; + tokenConfig: OutputTokenDetails; + offrampAmount: string; +} + +export interface SepResult { + amount: string; + memo: string; + memoType: string; + offrampingAccount: string; +} From 425009477aeb0f05effb80b59f67c0f5730dd216 Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Fri, 3 Jan 2025 14:16:24 +0100 Subject: [PATCH 22/32] fix types --- .../useSEP24/useAnchorWindowHandler.ts | 26 +++++++++---------- src/services/phases/stellar/index.tsx | 2 +- src/stores/sep24Store.ts | 2 +- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts b/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts index 47a3a6e6..4949a669 100644 --- a/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts +++ b/src/hooks/offramp/useSEP24/useAnchorWindowHandler.ts @@ -4,7 +4,7 @@ import Big from 'big.js'; import { useNetwork } from '../../../contexts/network'; import { constructInitialState } from '../../../services/offrampingFlow'; -import { sep24Second } from '../../../services/anchor/sep24/first'; +import { sep24Second } from '../../../services/anchor/sep24/second'; import { showToast, ToastMessage } from '../../../helpers/notifications'; @@ -12,11 +12,10 @@ import { useTrackSEP24Events } from './useTrackSEP24Events'; import { usePendulumNode } from '../../../contexts/polkadotNode'; import { useOfframpActions } from '../../../stores/offrampStore'; import { - useSep24Store, useSep24Actions, - useFirstSep24Response, - useAnchorSessionParams, - useExecutionInput, + useSep24InitialResponse, + useSep24AnchorSessionParams, + useSep24ExecutionInput, } from '../../../stores/sep24Store'; import { useVortexAccount } from '../../useVortexAccount'; @@ -37,11 +36,11 @@ export const useAnchorWindowHandler = () => { const { setOfframpStarted, updateOfframpHookStateFromState } = useOfframpActions(); const { address } = useVortexAccount(); - const firstSep24Response = useFirstSep24Response(); - const anchorSessionParams = useAnchorSessionParams(); + const firstSep24Response = useSep24InitialResponse(); + const anchorSessionParams = useSep24AnchorSessionParams(); - const executionInput = useExecutionInput(); - const { cleanupSep24State } = useSep24Actions(); + const executionInput = useSep24ExecutionInput(); + const { cleanup: cleanupSep24State } = useSep24Actions(); return useCallback(async () => { if (!firstSep24Response || !anchorSessionParams || !executionInput) { @@ -83,17 +82,16 @@ export const useAnchorWindowHandler = () => { handleError(error, setOfframpStarted); } }, [ - address, - cleanupSep24State, firstSep24Response, anchorSessionParams, executionInput, pendulumNode, trackKYCStarted, - trackKYCCompleted, selectedNetwork, - setOfframpStarted, - updateOfframpHookStateFromState, + cleanupSep24State, address, + trackKYCCompleted, + updateOfframpHookStateFromState, + setOfframpStarted, ]); }; diff --git a/src/services/phases/stellar/index.tsx b/src/services/phases/stellar/index.tsx index e5c7e53b..2ae4f338 100644 --- a/src/services/phases/stellar/index.tsx +++ b/src/services/phases/stellar/index.tsx @@ -22,7 +22,7 @@ import { } from '../../../constants/constants'; import { fetchSigningServiceAccountId } from '../../signingService'; import { OfframpingState } from '../../offrampingFlow'; -import { SepResult } from '../../anchor/sep24/first'; +import { SepResult } from '../../../types/sep'; const horizonServer = new Horizon.Server(HORIZON_URL); const NETWORK_PASSPHRASE = Networks.PUBLIC; diff --git a/src/stores/sep24Store.ts b/src/stores/sep24Store.ts index 139d2f93..9520227f 100644 --- a/src/stores/sep24Store.ts +++ b/src/stores/sep24Store.ts @@ -24,7 +24,7 @@ interface Sep24Store extends Sep24State { actions: Sep24Actions; } -export const useSep24Store = create()((set, get) => ({ +const useSep24Store = create()((set, get) => ({ anchorSessionParams: undefined, initialResponse: undefined, executionInput: undefined, From 0b600976da81ced1c4319442c64ac19ce94d64a4 Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Fri, 3 Jan 2025 16:17:11 +0100 Subject: [PATCH 23/32] improve types, fix paths --- src/components/FeeComparison/index.tsx | 2 +- src/components/Nabla/useSwapForm.tsx | 20 +----- src/components/NetworkIcon/index.tsx | 11 ++-- src/components/NetworkSelector/index.tsx | 54 +++++++-------- src/components/SigningBox/index.tsx | 3 +- .../buttons/ConnectWalletButton/index.tsx | 3 +- .../buttons/SwapSubmitButton/index.tsx | 3 +- src/constants/tokenConfig.ts | 2 +- src/contexts/events.tsx | 5 +- src/contexts/network.tsx | 50 +------------- src/helpers/networks.ts | 65 +++++++++++++++++++ src/hooks/nabla/useTokenAmountOut.ts | 2 +- src/hooks/offramp/useOfframpAdvancement.ts | 6 +- .../offramp/useSEP24/useTrackSEP24Events.ts | 2 +- src/hooks/offramp/useSubmitOfframp.ts | 4 +- src/hooks/useGetNetworkIcon.tsx | 10 ++- src/hooks/useVortexAccount.ts | 9 +-- src/pages/progress/index.tsx | 60 +++++++++-------- src/pages/swap/helpers/swapConfirm/index.ts | 3 +- .../swapConfirm/performSwapInitialChecks.ts | 2 +- src/pages/swap/index.tsx | 19 +++--- src/services/offrampingFlow.ts | 2 +- src/services/phases/polkadot/ephemeral.tsx | 2 +- src/services/phases/signedTransactions.ts | 3 +- .../squidrouter/__tests__/route.test.tsx | 2 +- src/services/phases/squidrouter/config.ts | 2 +- src/services/phases/squidrouter/route.ts | 2 +- src/services/quotes/index.ts | 2 +- 28 files changed, 172 insertions(+), 178 deletions(-) create mode 100644 src/helpers/networks.ts diff --git a/src/components/FeeComparison/index.tsx b/src/components/FeeComparison/index.tsx index a08ee989..b8a7605f 100644 --- a/src/components/FeeComparison/index.tsx +++ b/src/components/FeeComparison/index.tsx @@ -5,7 +5,7 @@ import { useQuery } from '@tanstack/react-query'; import { ChevronDownIcon } from '@heroicons/react/20/solid'; import vortexIcon from '../../assets/logo/blue.svg'; -import { Networks } from '../../contexts/network'; +import { Networks } from '../../helpers/networks'; import { Skeleton } from '../Skeleton'; import { QuoteProvider, quoteProviders } from './quoteProviders'; import { OfframpingParameters, useEventsContext } from '../../contexts/events'; diff --git a/src/components/Nabla/useSwapForm.tsx b/src/components/Nabla/useSwapForm.tsx index 4d916484..02aae44c 100644 --- a/src/components/Nabla/useSwapForm.tsx +++ b/src/components/Nabla/useSwapForm.tsx @@ -13,7 +13,8 @@ import { import { debounce } from '../../helpers/function'; import { storageService } from '../../services/storage/local'; import schema, { SwapFormValues } from './schema'; -import { Networks, useNetwork } from '../../contexts/network'; +import { getCaseSensitiveNetwork } from '../../helpers/networks'; +import { useNetwork } from '../../contexts/network'; type SwapSettings = { from: string; @@ -22,26 +23,9 @@ type SwapSettings = { type TokenSelectType = 'from' | 'to'; -type NetworkMap = Record, Networks>; - -const NETWORK_MAPPING: NetworkMap = { - assethub: Networks.AssetHub, - polygon: Networks.Polygon, - ethereum: Networks.Ethereum, - bsc: Networks.BSC, - arbitrum: Networks.Arbitrum, - base: Networks.Base, - avalanche: Networks.Avalanche, -}; - const storageSet = debounce(storageService.set, 1000); const setStorageForSwapSettings = storageSet.bind(null, storageKeys.SWAP_SETTINGS); -export function getCaseSensitiveNetwork(network: string): Networks { - const lowercasedNetwork = network.toLowerCase() as keyof typeof NETWORK_MAPPING; - return NETWORK_MAPPING[lowercasedNetwork] ?? Networks.AssetHub; -} - function mergeIfDefined(target: T, source: Nullable | undefined): void { if (!source) return; diff --git a/src/components/NetworkIcon/index.tsx b/src/components/NetworkIcon/index.tsx index 2847f373..cfab662f 100644 --- a/src/components/NetworkIcon/index.tsx +++ b/src/components/NetworkIcon/index.tsx @@ -1,14 +1,15 @@ import { FC, HTMLAttributes } from 'preact/compat'; -import { useGetNetworkIcon, NetworkIconType } from '../../hooks/useGetNetworkIcon'; +import { useGetNetworkIcon } from '../../hooks/useGetNetworkIcon'; +import { getNetworkDisplayName, Networks } from '../../helpers/networks'; interface Props extends HTMLAttributes { - chainId: NetworkIconType; + network: Networks; } -export const NetworkIcon: FC = ({ chainId, ...props }) => { - const iconSrc = useGetNetworkIcon(chainId); +export const NetworkIcon: FC = ({ network, ...props }) => { + const iconSrc = useGetNetworkIcon(network); - if (iconSrc) return {chainId}; + if (iconSrc) return {getNetworkDisplayName(network)}; return <>; }; diff --git a/src/components/NetworkSelector/index.tsx b/src/components/NetworkSelector/index.tsx index bc72063d..c8c4e9da 100644 --- a/src/components/NetworkSelector/index.tsx +++ b/src/components/NetworkSelector/index.tsx @@ -1,23 +1,10 @@ -import { motion, AnimatePresence } from 'framer-motion'; import { ChevronDownIcon } from '@heroicons/react/20/solid'; -import { NetworkIcon } from '../NetworkIcon'; -import { NetworkIconType } from '../../hooks/useGetNetworkIcon'; -import { Networks, useNetwork } from '../../contexts/network'; import { useState, useRef, useEffect } from 'preact/hooks'; +import { motion, AnimatePresence } from 'framer-motion'; -const NETWORK_DISPLAY_NAMES: Record = { - [Networks.AssetHub]: 'Polkadot AssetHub', - [Networks.Polygon]: 'Polygon', - [Networks.Ethereum]: 'Ethereum', - [Networks.BSC]: 'BNB Smart Chain', - [Networks.Arbitrum]: 'Arbitrum One', - [Networks.Base]: 'Base', - [Networks.Avalanche]: 'Avalanche', -}; - -function networkToDisplayName(network: Networks): string { - return NETWORK_DISPLAY_NAMES[network] ?? network; -} +import { Networks, getNetworkDisplayName, getNetworkId } from '../../helpers/networks'; +import { useNetwork } from '../../contexts/network'; +import { NetworkIcon } from '../NetworkIcon'; interface NetworkButtonProps { selectedNetwork: Networks; @@ -36,8 +23,8 @@ const NetworkButton = ({ selectedNetwork, isOpen, onClick, disabled }: NetworkBu whileTap={{ scale: disabled ? 1 : 0.98 }} disabled={disabled} > - - {networkToDisplayName(selectedNetwork)} + + {getNetworkDisplayName(selectedNetwork)} void; + onNetworkSelect: (network: Networks) => void; disabled?: boolean; } @@ -65,16 +52,19 @@ const NetworkDropdown = ({ isOpen, onNetworkSelect, disabled }: NetworkDropdownP className="absolute z-50 w-48 p-2 mt-2 shadow-lg bg-base-100 rounded-box whitespace-nowrap" layout > - {Object.values(Networks).map((networkId) => ( - - ))} + {Object.values(Networks).map((network) => { + const networkId = getNetworkId(network); + return ( + + ); + })} )} @@ -100,8 +90,8 @@ export const NetworkSelector = ({ disabled }: { disabled?: boolean }) => { useClickOutside(dropdownRef, () => setIsOpen(false)); - const handleNetworkSelect = (networkId: NetworkIconType) => { - setSelectedNetwork(networkId); + const handleNetworkSelect = (network: Networks) => { + setSelectedNetwork(network); setIsOpen(false); }; diff --git a/src/components/SigningBox/index.tsx b/src/components/SigningBox/index.tsx index 15ba93de..f7f1e27a 100644 --- a/src/components/SigningBox/index.tsx +++ b/src/components/SigningBox/index.tsx @@ -3,7 +3,8 @@ import { FC } from 'preact/compat'; import accountBalanceWalletIcon from '../../assets/account-balance-wallet.svg'; import { SigningPhase } from '../../hooks/offramp/useMainProcess'; -import { isNetworkEVM, Networks, useNetwork } from '../../contexts/network'; +import { isNetworkEVM, Networks } from '../../helpers/networks'; +import { useNetwork } from '../../contexts/network'; import { Spinner } from '../Spinner'; type ProgressStep = { diff --git a/src/components/buttons/ConnectWalletButton/index.tsx b/src/components/buttons/ConnectWalletButton/index.tsx index 5faa5ff4..934c1a37 100644 --- a/src/components/buttons/ConnectWalletButton/index.tsx +++ b/src/components/buttons/ConnectWalletButton/index.tsx @@ -1,4 +1,5 @@ -import { isNetworkEVM, useNetwork } from '../../../contexts/network'; +import { isNetworkEVM } from '../../../helpers/networks'; +import { useNetwork } from '../../../contexts/network'; import { EVMWalletButton } from '../EVMWalletButton'; import { PolkadotWalletButton } from '../PolkadotWalletButton'; diff --git a/src/components/buttons/SwapSubmitButton/index.tsx b/src/components/buttons/SwapSubmitButton/index.tsx index a9167e31..32612b1a 100644 --- a/src/components/buttons/SwapSubmitButton/index.tsx +++ b/src/components/buttons/SwapSubmitButton/index.tsx @@ -3,7 +3,8 @@ import { Spinner } from '../../Spinner'; import { useAppKitAccount } from '@reown/appkit/react'; import { ConnectWalletButton } from '../ConnectWalletButton'; import { usePolkadotWalletState } from '../../../contexts/polkadotWallet'; -import { useNetwork, isNetworkEVM } from '../../../contexts/network'; +import { isNetworkEVM } from '../../../helpers/networks'; +import { useNetwork } from '../../../contexts/network'; interface SwapSubmitButtonProps { text: string; diff --git a/src/constants/tokenConfig.ts b/src/constants/tokenConfig.ts index 083e8583..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; diff --git a/src/contexts/events.tsx b/src/contexts/events.tsx index f369d4da..66e9c805 100644 --- a/src/contexts/events.tsx +++ b/src/contexts/events.tsx @@ -7,12 +7,11 @@ import { OfframpingState } from '../services/offrampingFlow'; import { calculateTotalReceive } from '../components/FeeCollapse'; import { QuoteService } from '../services/quotes'; import { useVortexAccount } from '../hooks/useVortexAccount'; -import { Networks } from './network'; +import { Networks } from '../helpers/networks'; declare global { interface Window { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - dataLayer: Record[]; + dataLayer: TrackableEvent[]; } } diff --git a/src/contexts/network.tsx b/src/contexts/network.tsx index c93a3fbe..bab19759 100644 --- a/src/contexts/network.tsx +++ b/src/contexts/network.tsx @@ -1,56 +1,10 @@ import { createContext } from 'preact'; import { useContext, useState, useEffect, useCallback } from 'preact/hooks'; import { useSwitchChain } from 'wagmi'; -import { polygon, bsc, arbitrum, base, avalanche, mainnet as ethereum } from '@reown/appkit/networks'; import { useLocalStorage, LocalStorageKeys } from '../hooks/useLocalStorage'; import { WALLETCONNECT_ASSETHUB_ID } from '../constants/constants'; -import { AssetHubChainId } from '../hooks/useVortexAccount'; import { useOfframpActions } from '../stores/offrampStore'; - -export enum Networks { - AssetHub = 'AssetHub', - Polygon = 'Polygon', - Ethereum = 'Ethereum', - BSC = 'BSC', - Arbitrum = 'Arbitrum', - Base = 'Base', - Avalanche = 'Avalanche', -} - -export function isNetworkEVM(network: Networks): boolean { - switch (network) { - case Networks.Polygon: - case Networks.Ethereum: - case Networks.BSC: - case Networks.Arbitrum: - case Networks.Base: - case Networks.Avalanche: - return true; - default: - return false; - } -} - -export function getNetworkId(network: Networks): number { - switch (network) { - case Networks.Polygon: - return polygon.id; - case Networks.Ethereum: - return ethereum.id; - case Networks.BSC: - return bsc.id; - case Networks.Arbitrum: - return arbitrum.id; - case Networks.Base: - return base.id; - case Networks.Avalanche: - return avalanche.id; - case Networks.AssetHub: - return AssetHubChainId; - default: - throw new Error('getNetworkId: unsupported network'); - } -} +import { getNetworkId, isNetworkEVM, Networks } from '../helpers/networks'; interface NetworkContextType { walletConnectPolkadotSelectedNetworkId: string; @@ -82,7 +36,7 @@ export const NetworkProvider = ({ children }: NetworkProviderProps) => { const [networkSelectorDisabled, setNetworkSelectorDisabled] = useState(false); const { resetOfframpState } = useOfframpActions(); - const { chains, switchChain } = useSwitchChain(); + const { switchChain } = useSwitchChain(); const setSelectedNetwork = useCallback( (network: Networks) => { diff --git a/src/helpers/networks.ts b/src/helpers/networks.ts new file mode 100644 index 00000000..ae59c09f --- /dev/null +++ b/src/helpers/networks.ts @@ -0,0 +1,65 @@ +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 { + // Polkadot networks + AssetHub = 'AssetHub', + // EVM networks + Arbitrum = 'Arbitrum', + Avalanche = 'Avalanche', + Base = 'Base', + BSC = 'BSC', + Ethereum = 'Ethereum', + Polygon = 'Polygon', +} + +type EVMNetworks = Exclude; + +const EVM_NETWORKS: Set = new Set([ + Networks.Polygon, + Networks.Ethereum, + Networks.BSC, + Networks.Arbitrum, + Networks.Base, + Networks.Avalanche, +]); + +export function isNetworkEVM(network: Networks): network is EVMNetworks { + return EVM_NETWORKS.has(network); +} + +const NETWORK_CONFIG: Record = { + [Networks.Polygon]: polygon, + [Networks.Ethereum]: ethereum, + [Networks.BSC]: bsc, + [Networks.Arbitrum]: arbitrum, + [Networks.Base]: base, + [Networks.Avalanche]: avalanche, + [Networks.AssetHub]: { id: ASSETHUB_CHAIN_ID }, +}; + +export function getNetworkId(network: Networks): number { + return NETWORK_CONFIG[network].id; +} + +export function getCaseSensitiveNetwork(network: string): Networks { + const normalized = network.toLowerCase(); + return Object.values(Networks).find((n) => n.toLowerCase() === normalized) ?? Networks.AssetHub; +} + +const NETWORK_DISPLAY_NAMES: Record = { + [Networks.AssetHub]: 'Polkadot AssetHub', + [Networks.Polygon]: 'Polygon', + [Networks.Ethereum]: 'Ethereum', + [Networks.BSC]: 'BNB Smart Chain', + [Networks.Arbitrum]: 'Arbitrum One', + [Networks.Base]: 'Base', + [Networks.Avalanche]: 'Avalanche', +}; + +export function getNetworkDisplayName(network: Networks): string { + return NETWORK_DISPLAY_NAMES[network] ?? network; +} diff --git a/src/hooks/nabla/useTokenAmountOut.ts b/src/hooks/nabla/useTokenAmountOut.ts index 3bf6165b..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; diff --git a/src/hooks/offramp/useOfframpAdvancement.ts b/src/hooks/offramp/useOfframpAdvancement.ts index 5fdbc818..2e5d8cbb 100644 --- a/src/hooks/offramp/useOfframpAdvancement.ts +++ b/src/hooks/offramp/useOfframpAdvancement.ts @@ -9,7 +9,8 @@ import { usePendulumNode } from '../../contexts/polkadotNode'; import { useEventsContext } from '../../contexts/events'; import { useOfframpActions, useOfframpState } from '../../stores/offrampStore'; -import { isNetworkEVM, useNetwork } from '../../contexts/network'; +import { useNetwork } from '../../contexts/network'; +import { isNetworkEVM } from '../../helpers/networks'; export const useOfframpAdvancement = () => { const { walletAccount } = usePolkadotWalletState(); @@ -65,5 +66,8 @@ export const useOfframpAdvancement = () => { pendulumNode, assetHubNode, walletAccount?.address, + wagmiConfig, + walletAccount, + setOfframpSigningPhase, ]); }; diff --git a/src/hooks/offramp/useSEP24/useTrackSEP24Events.ts b/src/hooks/offramp/useSEP24/useTrackSEP24Events.ts index 4b2065bf..f6e35f1c 100644 --- a/src/hooks/offramp/useSEP24/useTrackSEP24Events.ts +++ b/src/hooks/offramp/useSEP24/useTrackSEP24Events.ts @@ -1,7 +1,7 @@ import { createTransactionEvent, useEventsContext } from '../../../contexts/events'; import { calculateTotalReceive } from '../../../components/FeeCollapse'; import { OfframpingState } from '../../../services/offrampingFlow'; -import { Networks } from '../../../contexts/network'; +import { Networks } from '../../../helpers/networks'; import { getInputTokenDetailsOrDefault, OUTPUT_TOKEN_CONFIG } from '../../../constants/tokenConfig'; import { ExecutionInput } from '../useMainProcess'; diff --git a/src/hooks/offramp/useSubmitOfframp.ts b/src/hooks/offramp/useSubmitOfframp.ts index 2fa75cec..a4ffc881 100644 --- a/src/hooks/offramp/useSubmitOfframp.ts +++ b/src/hooks/offramp/useSubmitOfframp.ts @@ -1,8 +1,10 @@ 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 { getNetworkId, isNetworkEVM, useNetwork } from '../../contexts/network'; +import { useNetwork } from '../../contexts/network'; import { useEventsContext } from '../../contexts/events'; import { useSiweContext } from '../../contexts/siwe'; diff --git a/src/hooks/useGetNetworkIcon.tsx b/src/hooks/useGetNetworkIcon.tsx index 2a4978c8..c9b0d6cb 100644 --- a/src/hooks/useGetNetworkIcon.tsx +++ b/src/hooks/useGetNetworkIcon.tsx @@ -6,9 +6,9 @@ import ARBITRUM from '../assets/chains/arbitrum.svg'; import BASE from '../assets/chains/base.svg'; import AVALANCHE from '../assets/chains/avalanche.svg'; -import { Networks } from '../contexts/network'; +import { Networks } from '../helpers/networks'; -export const NETWORK_ICONS = { +export const NETWORK_ICONS: Record = { [Networks.AssetHub]: ASSET_HUB, [Networks.Polygon]: POLYGON, [Networks.Ethereum]: ETHEREUM, @@ -18,8 +18,6 @@ export const NETWORK_ICONS = { [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/useVortexAccount.ts b/src/hooks/useVortexAccount.ts index 06929b56..c2a04139 100644 --- a/src/hooks/useVortexAccount.ts +++ b/src/hooks/useVortexAccount.ts @@ -1,13 +1,10 @@ -import { isNetworkEVM, 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. -export 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. @@ -36,7 +33,7 @@ export const useVortexAccount = () => { const chainId = useMemo(() => { if (!isNetworkEVM(selectedNetwork)) { - return AssetHubChainId; + return ASSETHUB_CHAIN_ID; } else { return evmChainId; } diff --git a/src/pages/progress/index.tsx b/src/pages/progress/index.tsx index 73486907..c31cbed7 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 { isNetworkEVM, Networks, useNetwork } from '../../contexts/network'; +import { useNetwork } from '../../contexts/network'; +import { Networks, isNetworkEVM } 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, network: isEVMNetwork } = offrampingState; + + if (phase === 'success') return 'Transaction completed successfully'; + + const messages: Record = { + prepareTransactions: isEVMNetwork + ? `Bridging ${inputToken.assetSymbol} from Polygon --> Moonbeam` + : `Bridging ${inputToken.assetSymbol} from AssetHub --> Pendulum`, + squidRouter: isEVMNetwork + ? `Bridging ${inputToken.assetSymbol} from Polygon --> Moonbeam` + : `Bridging ${inputToken.assetSymbol} from AssetHub --> Pendulum`, + pendulumFundEphemeral: isEVMNetwork + ? `Bridging ${inputToken.assetSymbol} from Polygon --> 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 = { diff --git a/src/pages/swap/helpers/swapConfirm/index.ts b/src/pages/swap/helpers/swapConfirm/index.ts index 665254f5..bb61763b 100644 --- a/src/pages/swap/helpers/swapConfirm/index.ts +++ b/src/pages/swap/helpers/swapConfirm/index.ts @@ -1,4 +1,3 @@ -import { StateUpdater } from 'preact/hooks'; import { ApiPromise } from '@polkadot/api'; import Big from 'big.js'; @@ -11,7 +10,7 @@ import { import { ExecutionInput } from '../../../../hooks/offramp/useMainProcess'; import { TokenOutData } from '../../../../hooks/nabla/useTokenAmountOut'; -import { Networks } from '../../../../contexts/network'; +import { Networks } from '../../../../helpers/networks'; import { calculateSwapAmountsWithMargin } from './calculateSwapAmountsWithMargin'; import { performSwapInitialChecks } from './performSwapInitialChecks'; diff --git a/src/pages/swap/helpers/swapConfirm/performSwapInitialChecks.ts b/src/pages/swap/helpers/swapConfirm/performSwapInitialChecks.ts index 465fe6d0..d70c2cbc 100644 --- a/src/pages/swap/helpers/swapConfirm/performSwapInitialChecks.ts +++ b/src/pages/swap/helpers/swapConfirm/performSwapInitialChecks.ts @@ -3,7 +3,7 @@ import { ApiPromise } from '@polkadot/api'; import { InputTokenDetails, OutputTokenDetails } from '../../../../constants/tokenConfig'; import { getVaultsForCurrency } from '../../../../services/phases/polkadot/spacewalk'; import { testRoute } from '../../../../services/phases/squidrouter/route'; -import { Networks } from '../../../../contexts/network'; +import { Networks } from '../../../../helpers/networks'; export const performSwapInitialChecks = async ( api: ApiPromise, diff --git a/src/pages/swap/index.tsx b/src/pages/swap/index.tsx index eae9a09e..5355088f 100644 --- a/src/pages/swap/index.tsx +++ b/src/pages/swap/index.tsx @@ -28,12 +28,13 @@ import { import { config } from '../../config'; import { useEventsContext } from '../../contexts/events'; -import { isNetworkEVM, Networks, useNetwork } from '../../contexts/network'; +import { useNetwork } from '../../contexts/network'; import { usePendulumNode } from '../../contexts/polkadotNode'; import { useSiweContext } from '../../contexts/siwe'; import { multiplyByPowerOfTen, stringifyBigWithSignificantDecimals } from '../../helpers/contracts'; import { showToast, ToastMessage } from '../../helpers/notifications'; +import { isNetworkEVM, Networks } from '../../helpers/networks'; import { useInputTokenBalance } from '../../hooks/useInputTokenBalance'; import { useTokenOutAmount } from '../../hooks/nabla/useTokenAmountOut'; @@ -89,6 +90,13 @@ export const SwapPage = () => { } }, [pendulumNode]); + // Maybe go into a state of UI errors?? + const setInitializeFailed = useCallback((message?: string | null) => { + setInitializeFailedMessage( + message ?? 'Application initialization failed. Please reload, or try again later if the problem persists.', + ); + }, []); + useEffect(() => { const initialize = async () => { try { @@ -100,14 +108,7 @@ export const SwapPage = () => { }; initialize(); - }, []); - - // Maybe go into a state of UI errors?? - const setInitializeFailed = useCallback((message?: string | null) => { - setInitializeFailedMessage( - message ?? 'Application initialization failed. Please reload, or try again later if the problem persists.', - ); - }, []); + }, [setInitializeFailed]); // Main process hook const { diff --git a/src/services/offrampingFlow.ts b/src/services/offrampingFlow.ts index 5675b0a1..8ae11e94 100644 --- a/src/services/offrampingFlow.ts +++ b/src/services/offrampingFlow.ts @@ -9,7 +9,7 @@ import { u8aToHex } from '@polkadot/util'; import { SigningPhase } from '../hooks/offramp/useMainProcess'; import { TrackableEvent } from '../contexts/events'; -import { isNetworkEVM, Networks } from '../contexts/network'; +import { isNetworkEVM, Networks } from '../helpers/networks'; import { SepResult } from '../types/sep'; import { diff --git a/src/services/phases/polkadot/ephemeral.tsx b/src/services/phases/polkadot/ephemeral.tsx index 5e8228f2..d9bdec47 100644 --- a/src/services/phases/polkadot/ephemeral.tsx +++ b/src/services/phases/polkadot/ephemeral.tsx @@ -14,8 +14,8 @@ import { SIGNING_SERVICE_URL } from '../../../constants/constants'; import { multiplyByPowerOfTen } from '../../../helpers/contracts'; import { waitUntilTrue } from '../../../helpers/function'; +import { isNetworkEVM } from '../../../helpers/networks'; -import { isNetworkEVM } from '../../../contexts/network'; import { ExecutionContext, OfframpingState } from '../../offrampingFlow'; import { fetchSigningServiceAccountId } from '../../signingService'; import { isHashRegistered } from '../moonbeam'; diff --git a/src/services/phases/signedTransactions.ts b/src/services/phases/signedTransactions.ts index 8bdad1b1..fccaedb5 100644 --- a/src/services/phases/signedTransactions.ts +++ b/src/services/phases/signedTransactions.ts @@ -2,8 +2,7 @@ import { ApiPromise, Keyring } from '@polkadot/api'; import { Extrinsic } from '@pendulum-chain/api-solang'; import { Keypair } from 'stellar-sdk'; -import { isNetworkEVM } from '../../contexts/network'; - +import { isNetworkEVM } from '../../helpers/networks'; import { ExecutionContext, OfframpingState } from '../offrampingFlow'; import { fetchSigningServiceAccountId } from '../signingService'; import { storeDataInBackend } from '../storage/remote'; diff --git a/src/services/phases/squidrouter/__tests__/route.test.tsx b/src/services/phases/squidrouter/__tests__/route.test.tsx index 3f03291d..827ccf90 100644 --- a/src/services/phases/squidrouter/__tests__/route.test.tsx +++ b/src/services/phases/squidrouter/__tests__/route.test.tsx @@ -5,7 +5,7 @@ import { INPUT_TOKEN_CONFIG } from '../../../../constants/tokenConfig'; import { InputTokenDetails } from '../../../../constants/tokenConfig'; import { createSquidRouterHash } from '../../../../helpers/crypto'; import { createRandomString } from '../../../../helpers/crypto'; -import { Networks } from '../../../../contexts/network'; +import { Networks } from '../../../../helpers/networks'; // We need to add a delay to the beforeEach hook to ensure that the test does not run before the SquidRouter API is ready beforeEach(() => { diff --git a/src/services/phases/squidrouter/config.ts b/src/services/phases/squidrouter/config.ts index 48503be3..71064c12 100644 --- a/src/services/phases/squidrouter/config.ts +++ b/src/services/phases/squidrouter/config.ts @@ -1,4 +1,4 @@ -import { getNetworkId, Networks } from '../../../contexts/network'; +import { getNetworkId, Networks } from '../../../helpers/networks'; interface ConfigBase { toChainId: string; diff --git a/src/services/phases/squidrouter/route.ts b/src/services/phases/squidrouter/route.ts index c94a849f..d09134d1 100644 --- a/src/services/phases/squidrouter/route.ts +++ b/src/services/phases/squidrouter/route.ts @@ -5,7 +5,7 @@ import squidReceiverABI from '../../../../mooncontracts/splitReceiverABI.json'; import { InputTokenDetails, isEvmInputTokenDetails } from '../../../constants/tokenConfig'; import erc20ABI from '../../../contracts/ERC20'; import { getSquidRouterConfig, squidRouterConfigBase } from './config'; -import { Networks } from '../../../contexts/network'; +import { Networks } from '../../../helpers/networks'; interface RouteParams { fromAddress: string; diff --git a/src/services/quotes/index.ts b/src/services/quotes/index.ts index 698d4c83..a3912649 100644 --- a/src/services/quotes/index.ts +++ b/src/services/quotes/index.ts @@ -2,7 +2,7 @@ import { polygon } from 'wagmi/chains'; import Big from 'big.js'; import { SIGNING_SERVICE_URL } from '../../constants/constants'; -import { isNetworkEVM, Networks } from '../../contexts/network'; +import { isNetworkEVM, Networks } from '../../helpers/networks'; const QUOTE_ENDPOINT = `${SIGNING_SERVICE_URL}/v1/quotes`; From 0c29c6507aaa8b3ec3f0f39ea08583e395f49717 Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Fri, 3 Jan 2025 17:15:29 +0100 Subject: [PATCH 24/32] improve Navbar responsiveness, improve EVMWalletButton structure --- src/components/Navbar/index.tsx | 14 +- .../buttons/EVMWalletButton/index.tsx | 154 ++++++++++-------- 2 files changed, 92 insertions(+), 76 deletions(-) diff --git a/src/components/Navbar/index.tsx b/src/components/Navbar/index.tsx index f70e73f1..bdd27fbb 100644 --- a/src/components/Navbar/index.tsx +++ b/src/components/Navbar/index.tsx @@ -19,8 +19,12 @@ interface MobileMenuProps { } const MobileMenu: FC = ({ onClick }) => ( - ); @@ -59,7 +63,7 @@ const MobileMenuList: FC = ({ showMenu, closeMenu }) => ( exit="exit" variants={menuVariants} > -