diff --git a/src/components/Claim/Link/Initial.view.tsx b/src/components/Claim/Link/Initial.view.tsx index 0701a3fb4..2930ffd60 100644 --- a/src/components/Claim/Link/Initial.view.tsx +++ b/src/components/Claim/Link/Initial.view.tsx @@ -20,7 +20,12 @@ import { Popover } from '@headlessui/react' import { useAuth } from '@/context/authContext' import { ActionType, estimatePoints } from '@/components/utils/utils' import { CrispButton } from '@/components/CrispChat' -import { MAX_CASHOUT_LIMIT, MIN_CASHOUT_LIMIT, optimismChainId, usdcAddressOptimism } from '@/components/Offramp/Offramp.consts' +import { + MAX_CASHOUT_LIMIT, + MIN_CASHOUT_LIMIT, + optimismChainId, + usdcAddressOptimism, +} from '@/components/Offramp/Offramp.consts' export const InitialClaimLinkView = ({ onNext, @@ -296,6 +301,7 @@ export const InitialClaimLinkView = ({ Number(claimLinkData.tokenAmount) * Math.pow(10, claimLinkData.tokenDecimals) ).toString() + // TODO: this is duplicate with src/utils/fetchRouteRaw const route = await getSquidRouteRaw({ squidRouterUrl: 'https://apiplus.squidrouter.com/v2/route', fromChain: claimLinkData.chainId.toString(), @@ -634,8 +640,9 @@ export const InitialClaimLinkView = ({ <> {errorState.errorMessage === 'No route found for the given token pair.' && ( <> - - {' '} + {' '} { @@ -654,14 +661,16 @@ export const InitialClaimLinkView = ({ {errorState.errorMessage === 'offramp_lt_minimum' && ( <> )} {errorState.errorMessage === 'offramp_mt_maximum' && ( <> )} diff --git a/src/components/Offramp/Confirm.view.tsx b/src/components/Offramp/Confirm.view.tsx index 6fd502b5b..f52215859 100644 --- a/src/components/Offramp/Confirm.view.tsx +++ b/src/components/Offramp/Confirm.view.tsx @@ -93,7 +93,7 @@ export const OfframpConfirmView = ({ ////////////////////// // functions for cashout offramps // TODO: they need to be refactored to a separate file - + // TODO: this function is a clusterfuck const fetchNecessaryDetails = useCallback(async () => { if (!user || !selectedChainID || !selectedTokenAddress) { throw new Error('Missing user or token information') @@ -137,7 +137,8 @@ export const OfframpConfirmView = ({ } }, [user, selectedChainID, selectedTokenAddress, offrampForm]) - const handleConfirm = async () => { + // For cashout offramps + const handleCashoutConfirm = async () => { setLoadingState('Loading') setErrorState({ showError: false, errorMessage: '' }) @@ -145,7 +146,6 @@ export const OfframpConfirmView = ({ if (!preparedCreateLinkWrapperResponse) return // Fetch all necessary details before creating the link - // (and make sure we have all the data we need) const { crossChainDetails, peanutAccount, @@ -154,26 +154,10 @@ export const OfframpConfirmView = ({ allLiquidationAddresses, } = await fetchNecessaryDetails() - const link = await createLinkWrapper(preparedCreateLinkWrapperResponse) - setCreatedLink(link) - console.log(`created claimlink: ${link}`) - - // Save link temporarily in localStorage with TEMP tag - const tempKey = `TEMP_CASHOUT_LINK_${Date.now()}` - localStorage.setItem( - tempKey, - JSON.stringify({ - link, - createdAt: Date.now(), - }) - ) - console.log(`Temporarily saved link in localStorage with key: ${tempKey}`) - - const claimLinkData = await getLinkDetails({ link: link }) - // Process link details and determine if cross-chain transfer is needed + // TODO: type safety const { tokenName, chainName, xchainNeeded, liquidationAddress } = await processLinkDetails( - claimLinkData, + preparedCreateLinkWrapperResponse.linkDetails, crossChainDetails as CrossChainDetails[], allLiquidationAddresses, bridgeCustomerId, @@ -189,12 +173,38 @@ export const OfframpConfirmView = ({ const chainId = utils.getChainIdFromBridgeChainName(chainName) ?? '' const tokenAddress = utils.getTokenAddressFromBridgeTokenName(chainId ?? '10', tokenName) ?? '' + // Now that we have all the necessary information, create the link + const link = await createLinkWrapper(preparedCreateLinkWrapperResponse) + setCreatedLink(link) + console.log(`created claimlink: ${link}`) + + // Save link temporarily in localStorage with TEMP tag + const tempKey = `TEMP_CASHOUT_LINK_${Date.now()}` + localStorage.setItem( + tempKey, + JSON.stringify({ + link, + createdAt: Date.now(), + }) + ) + console.log(`Temporarily saved link in localStorage with key: ${tempKey}`) + + const claimLinkData = await getLinkDetails({ link: link }) + + const srcChainId = claimLinkData.chainId + const destChainId = chainId + const isSameChain = srcChainId === destChainId const { sourceTxHash, destinationTxHash } = await claimAndProcessLink( xchainNeeded, liquidationAddress.address, claimLinkData, chainId, - tokenAddress + tokenAddress, + isSameChain + ) + + console.log( + `finalized claimAndProcessLink, sourceTxHash: ${sourceTxHash}, destinationTxHash: ${destinationTxHash}` ) localStorage.removeItem(tempKey) @@ -215,7 +225,6 @@ export const OfframpConfirmView = ({ console.log('Transaction hash:', destinationTxHash) onNext() - setLoadingState('Idle') } catch (error) { handleError(error) } finally { @@ -277,13 +286,13 @@ export const OfframpConfirmView = ({ } const route = await utils.fetchRouteRaw( - claimLinkData.tokenAddress, - claimLinkData.chainId.toString(), + claimLinkData.tokenAddress!, + claimLinkData.chainId!.toString(), usdcAddressOptimism, optimismChainId, - claimLinkData.tokenDecimals, - claimLinkData.tokenAmount, - claimLinkData.senderAddress + claimLinkData.tokenDecimals!, + claimLinkData.tokenAmount!, + claimLinkData.senderAddress ?? '0x9647BB6a598c2675310c512e0566B60a5aEE6261' ) if (route === undefined) { @@ -301,8 +310,9 @@ export const OfframpConfirmView = ({ address: string, claimLinkData: any, // TODO: fix type chainId: string, - tokenAddress: string - ) => { + tokenAddress: string, + isSameChain?: boolean // e.g. for opt ETH -> opt USDC + ): Promise<{ sourceTxHash: string; destinationTxHash: string }> => { if (xchainNeeded) { const sourceTxHash = await claimLinkXchain({ address, @@ -310,34 +320,48 @@ export const OfframpConfirmView = ({ destinationChainId: chainId, destinationToken: tokenAddress, }) + setLoadingState('Executing transaction') // claimLinkXchain sets loading state to idle after it finishes. pls no. - // Wait for the destination transaction - const destinationTxHash = await new Promise((resolve) => { - let retryCount = 0 - let intervalId = setInterval(async () => { - if (retryCount >= 10) { - clearInterval(intervalId) - resolve(sourceTxHash) - return - } + if (isSameChain) { + return { + sourceTxHash, + destinationTxHash: sourceTxHash, + } + } + + const maxAttempts = 15 + let attempts = 0 + + while (attempts < maxAttempts) { + try { const status = await checkTransactionStatus(sourceTxHash) if (status.squidTransactionStatus === 'success') { - clearInterval(intervalId) - resolve(status.toChain.transactionId) + return { + sourceTxHash, + destinationTxHash: status.toChain.transactionId, + } } - retryCount++ - }, 1000) - }) + } catch (error) { + console.warn('Error checking transaction status:', error) + } + + attempts++ + if (attempts < maxAttempts) { + await new Promise((resolve) => setTimeout(resolve, 2000)) + } + } + console.warn('Transaction status check timed out. Using sourceTxHash as destinationTxHash.') return { sourceTxHash, - destinationTxHash: destinationTxHash, + destinationTxHash: sourceTxHash, } } else { const txHash = await claimLink({ address, link: claimLinkData.link, }) + setLoadingState('Executing transaction') // claimLink return { sourceTxHash: txHash, destinationTxHash: txHash } } } @@ -383,7 +407,7 @@ export const OfframpConfirmView = ({ } const handleError = (error: unknown) => { - console.error('Error in handleConfirm:', error) + console.error('Error in handleCashoutConfirm:', error) setErrorState({ showError: true, errorMessage: @@ -428,9 +452,8 @@ export const OfframpConfirmView = ({ } ////////////////////// - // functions for claim link offramps + // functions for claim link offramps (not self-cashout) // TODO: they need to be refactored to a separate file - const handleSubmitTransfer = async () => { if (claimLinkData && tokenPrice && estimatedPoints && attachment && recipientType) { try { @@ -743,7 +766,7 @@ export const OfframpConfirmView = ({ onClick={() => { switch (offrampType) { case OfframpType.CASHOUT: { - handleConfirm() + handleCashoutConfirm() break } case OfframpType.CLAIM: { diff --git a/src/components/utils/utils.ts b/src/components/utils/utils.ts index 0935ba66b..d119c811f 100644 --- a/src/components/utils/utils.ts +++ b/src/components/utils/utils.ts @@ -76,7 +76,7 @@ export async function checkTransactionStatus(txHash: string): Promise { try { const _tokenAmount = BigInt(Math.floor(Number(tokenAmount) * Math.pow(10, tokenDecimals))).toString() @@ -537,12 +539,11 @@ export const fetchRouteRaw = async ( fromChain: fromChain, fromToken: fromToken.toLowerCase(), fromAmount: _tokenAmount, + fromAddress: fromAddress ?? '0x9647BB6a598c2675310c512e0566B60a5aEE6261', // placeholder address just to get a route sample + toAddress: '0x04B5f21facD2ef7c7dbdEe7EbCFBC68616adC45C', // placeholder address just to get a route sample toChain: toChain, toToken: toToken, slippage: 1, - fromAddress: senderAddress, - - toAddress: '0x04B5f21facD2ef7c7dbdEe7EbCFBC68616adC45C', }) return route } catch (error) {