import { useCallback, useEffect, useRef, useState } from 'react'; import { StyleSheet, View } from 'react-native'; import { ThemeText } from '../../../../functions/CustomElements'; import { CENTER, FONT, SIZES } from '../../../../constants'; import { useTranslation } from 'react-i18next'; import { INSET_WINDOW_WIDTH } from '../../../../constants/theme'; import { useNavigation } from '@react-navigation/native'; import { useKeysContext } from '../../../../../context-store/keys'; import { useActiveCustodyAccount } from '../../../../../context-store/activeAccount'; import { updatePoolLocal } from '../../../../functions/pools/poolsStorage'; import { derivePoolWallet } from '../../../../functions/pools/derivePoolWallet'; import { getSparkBalance, sendSparkPayment, getSparkAddress, initializeSparkWallet, } from '../../../../functions/spark'; import { updatePoolInDatabase } from '../../../../../db'; import CustomButton from '../../../../functions/CustomElements/button'; import FullLoadingScreen from '../../../../functions/CustomElements/loadingScreen'; import { useSparkWallet } from '../../../../../context-store/sparkContext'; import { bulkUpdateSparkTransactions } from '../../../../functions/spark/transactions'; export default function ClosePoolConfirmation({ pool, setContentHeight, handleBackPressFunction, autoStart, }) { const navigate = useNavigation(); const { accountMnemoinc } = useKeysContext(); const { currentWalletMnemoinc } = useActiveCustodyAccount(); const { sparkInformation } = useSparkWallet(); const isMounted = useRef(true); const { t } = useTranslation(); const [isLoading, setIsLoading] = useState(false); const [loadingMessage, setLoadingMessage] = useState(''); useEffect(() => { setContentHeight(400); return () => { isMounted.current = false; }; }, []); const getBalanceWithRetry = useCallback(async poolMnemonic => { const delays = [5000, 7000, 8000, 8000]; let attempt = 0; setLoadingMessage(t('wallet.pools.gettingBalance')); const isCorrectBalance = result => { if (!result?.didWork) return false; const balance = Number(result.balance); return autoStart ? balance > 0 : balance >= pool.currentAmount; }; let result = await getSparkBalance(poolMnemonic); if (isCorrectBalance(result)) { return result; } for (const delay of delays) { if (!isMounted.current) break; attempt++; setLoadingMessage( t('wallet.pools.checkingBalance', { attempt, total: delays.length }), ); await new Promise(res => setTimeout(res, delay)); result = await getSparkBalance(poolMnemonic); if (isCorrectBalance(result)) { return result; } } return result; }, []); const handleClosePool = useCallback(async () => { if (!pool) return; try { setIsLoading(true); setLoadingMessage(t('wallet.pools.preparingTransfer')); // 1. Re-derive pool mnemonic from account mnemonic + derivation index const derivedWallet = await derivePoolWallet( accountMnemoinc, pool.derivationIndex, ); const poolMnemonic = derivedWallet.mnemonic; // 2. Initialize the pool wallet setLoadingMessage(t('wallet.pools.connectingToPool')); const initResult = await initializeSparkWallet(poolMnemonic, false, { maxRetries: 4, }); if (!initResult) { throw new Error(t('wallet.pools.unableToConnect')); } // 3. Get pool wallet balance with retry logic const balanceResponse = await getBalanceWithRetry(poolMnemonic); if (!isMounted.current) return; if (!balanceResponse?.didWork) { throw new Error(t('wallet.pools.unableToGetBalance')); } const poolBalance = Number(balanceResponse.balance); if (poolBalance <= 0) { if (autoStart) { handleBackPressFunction(() => { navigate.replace('ErrorScreen', { errorMessage: t('wallet.pools.noFundsFound'), }); }); return; } // No funds to transfer — just close the pool setLoadingMessage(t('wallet.pools.closingPool')); const closedPool = { ...pool, status: 'closed', closedAt: Date.now(), transferTxId: null, }; await updatePoolInDatabase(closedPool); await updatePoolLocal(closedPool.poolId, closedPool); setIsLoading(false); handleBackPressFunction(); return; } // 4. Get creator's main spark address setLoadingMessage(t('wallet.pools.gettingAddress')); const mainAddressResponse = await getSparkAddress(currentWalletMnemoinc); if (!mainAddressResponse?.didWork) { throw new Error(t('wallet.pools.unableToGetAddress')); } const mainSparkAddress = mainAddressResponse.response; // 5. Transfer all funds from pool wallet to creator's main wallet setLoadingMessage(t('wallet.pools.transferringFunds')); const transferResponse = await sendSparkPayment({ receiverSparkAddress: mainSparkAddress, amountSats: poolBalance, mnemonic: poolMnemonic, }); if (!transferResponse?.didWork) { throw new Error( transferResponse?.error || t('wallet.pools.failedToTransfer'), ); } const transferTxId = transferResponse.response?.id || null; // 6. Update pool status to closed setLoadingMessage(t('wallet.pools.closingPool')); const closedPool = { ...pool, status: 'closed', closedAt: Date.now(), transferTxId, }; // When rechecking pool for a balance, if we do find a balance // we need to make sure to add it to the current pool balance // and not make it the balance itself if (autoStart) { closedPool.currentAmount = pool.currentAmount + poolBalance; } else { closedPool.currentAmount = poolBalance; } await updatePoolInDatabase(closedPool); await updatePoolLocal(closedPool.poolId, closedPool); let tx = { id: transferTxId, paymentStatus: 'completed', paymentType: 'spark', accountId: sparkInformation.identityPubKey, details: { fee: 0, totalFee: 0, supportFee: 0, amount: poolBalance, address: mainSparkAddress, time: new Date().getTime(), direction: 'INCOMING', isPoolPayment: true, description: t( `wallet.pools.${ autoStart ? 'late_pool_contribution_label' : 'closing_pool_label' }`, { poolName: pool.poolTitle, }, ), senderIdentityPublicKey: transferResponse.response.receiverIdentityPublicKey, }, }; await bulkUpdateSparkTransactions([tx], 'fullUpdate-waitBalance'); setIsLoading(false); handleBackPressFunction(); } catch (err) { console.log('Error closing pool:', err); setIsLoading(false); setLoadingMessage(''); navigate.navigate('ErrorScreen', { errorMessage: err.message }); } }, [ pool, accountMnemoinc, currentWalletMnemoinc, navigate, getBalanceWithRetry, sparkInformation, ]); useEffect(() => { if (autoStart) handleClosePool(); }, [autoStart]); if (isLoading) { return ( ); } if (!pool) { return ( ); } return ( ); } const styles = StyleSheet.create({ container: { flex: 1, width: INSET_WINDOW_WIDTH, ...CENTER, }, centerContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', }, title: { fontSize: SIZES.xLarge, fontFamily: FONT.Title_Regular, marginBottom: 12, }, description: { fontSize: SIZES.smedium, opacity: 0.6, marginBottom: 24, lineHeight: 22, }, detailsBox: { width: '100%', borderRadius: 16, padding: 20, gap: 16, marginBottom: 32, }, detailRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, detailLabel: { fontSize: SIZES.medium, opacity: 0.6, }, detailValue: { fontSize: SIZES.medium, fontFamily: FONT.Title_Bold, maxWidth: '60%', textAlign: 'right', }, buttonContainer: { width: '100%', gap: 12, marginTop: 'auto', }, closeButton: { ...CENTER, }, cancelButton: { width: '100%', opacity: 0.6, }, });