Files
com.blitzwallet/app/components/admin/homeComponents/pools/closePoolConfirmation.js
T

340 lines
9.1 KiB
JavaScript

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 (
<FullLoadingScreen
containerStyles={styles.container}
textStyles={{ textAlign: 'center' }}
text={loadingMessage}
/>
);
}
if (!pool) {
return (
<View style={styles.centerContainer}>
<ThemeText content={t('wallet.pools.notFound')} />
</View>
);
}
return (
<View style={styles.container}>
<ThemeText
styles={styles.title}
content={t('wallet.pools.closePoolTitle')}
/>
<ThemeText
styles={styles.description}
content={t('wallet.pools.closePoolWarning')}
/>
<View style={styles.buttonContainer}>
<CustomButton
buttonStyles={styles.closeButton}
textContent={t('wallet.pools.closePool')}
actionFunction={handleClosePool}
/>
</View>
</View>
);
}
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,
},
});