Files

938 lines
28 KiB
JavaScript

import React, {
createContext,
useContext,
useReducer,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import {
BTC_ASSET_ADDRESS,
findBestPool,
USD_ASSET_ADDRESS,
swapBitcoinToToken,
getUserSwapHistory,
minFlashnetSwapAmounts,
checkClawbackEligibility,
requestManualClawback,
dollarsToSats,
currentPriceAinBToPriceDollars,
addActiveSwap,
removeActiveSwap,
savePendingSwapConfirmation,
completeSwapConfirmation,
retryPendingSwapConfirmations,
SEND_AMOUNT_INCREASE_BUFFER,
} from '../app/functions/spark/flashnet';
import { useAppStatus } from './appStatus';
import { useSparkWallet } from './sparkContext';
import { useActiveCustodyAccount } from './activeAccount';
import {
getSingleTxDetails,
getSparkPaymentStatus,
initializeFlashnet,
} from '../app/functions/spark';
import { USDB_TOKEN_ID } from '../app/constants';
import {
bulkUpdateSparkTransactions,
deleteUnpaidSparkLightningTransaction,
flashnetAutoSwapsEventListener,
getPendingAutoSwaps,
getSingleSparkLightningRequest,
HANDLE_FLASHNET_AUTO_SWAP,
updateSparkTransactionDetails,
} from '../app/functions/spark/transactions';
import {
isFlashnetTransfer,
loadSavedTransferIds,
setFlashnetTransfer,
} from '../app/functions/spark/handleFlashnetTransferIds';
import { useToast } from './toastManager';
import { useAuthContext } from './authContext';
import { listClawbackableTransfers } from '../app/functions/spark/flashnet';
import { getLocalStorageItem, setLocalStorageItem } from '../app/functions';
const FlashnetContext = createContext(null);
const MAX_SWAP_RETRIES = 10;
// Only these pool fields are consumed anywhere in the app; ignore the rest
// (updatedAt/reserves/tvl/volume change every poll and would force needless
// re-renders of all ~30 consumers).
const POOL_WATCHED_KEYS = ['lpPublicKey', 'currentPriceAInB', 'lpFeeBps'];
const poolInfoChanged = (next, prev) =>
POOL_WATCHED_KEYS.some(k => next?.[k] !== prev?.[k]);
export function FlashnetProvider({ children }) {
const { showToast } = useToast();
const { currentWalletMnemoinc } = useActiveCustodyAccount();
const { appState } = useAppStatus();
const { sparkInformation, sparkInfoRef, setSparkInformation } =
useSparkWallet();
const [poolInfo, setPoolInfo] = useState({});
const [swapLimits, setSwapLimits] = useState({ usd: 1.03, bitcoin: 1030 });
const swapLimitsRef = useRef(swapLimits);
const poolInfoRef = useRef({});
const poolIntervalRef = useRef(null);
const currentWalletMnemoincRef = useRef(currentWalletMnemoinc);
const triggeredSwapsRef = useRef(new Set());
const refundMonitorIntervalRef = useRef(null);
const swapMonitorIntervalRef = useRef(null);
const retryTimeoutRef = useRef(null);
const flashnetRetryIntervalRef = useRef(null);
const flashnetRetryDelayRef = useRef(5_000);
const { authResetkey } = useAuthContext();
const authResetKeyRef = useRef(authResetkey);
const isInitialLoad = useRef(true);
const REFUND_MONITOR_INTERVAL = 25_000;
const SWAP_MONITOR_INTERVAL = 30_000;
useEffect(() => {
authResetKeyRef.current = authResetkey;
}, [authResetkey]);
useEffect(() => {
currentWalletMnemoincRef.current = currentWalletMnemoinc;
}, [currentWalletMnemoinc]);
useEffect(() => {
poolInfoRef.current = poolInfo;
}, [poolInfo]);
useEffect(() => {
swapLimitsRef.current = swapLimits;
}, [swapLimits]);
const togglePoolInfo = poolInfo => {
setPoolInfo(poolInfo);
};
const refreshPool = async () => {
if (!sparkInformation.didConnectToFlashnet) return;
if (appState !== 'active') return;
const result = await findBestPool(
currentWalletMnemoincRef.current,
BTC_ASSET_ADDRESS,
USD_ASSET_ADDRESS,
);
if (
result?.didWork &&
result.pool &&
poolInfoChanged(result.pool, poolInfoRef.current)
) {
setLocalStorageItem('swapPoolInfo', JSON.stringify(result.pool));
setPoolInfo(result.pool);
}
};
useEffect(() => {
async function loadSavedPoolInfo() {
const savedPoolInfo = JSON.parse(
await getLocalStorageItem('swapPoolInfo'),
);
console.log('saved pool info', savedPoolInfo);
if (savedPoolInfo) {
setPoolInfo(savedPoolInfo);
}
}
if (Object.keys(poolInfo).length) return;
if (appState !== 'active') return;
loadSavedPoolInfo();
}, [appState]);
useEffect(() => {
if (retryTimeoutRef.current) {
clearTimeout(retryTimeoutRef.current);
retryTimeoutRef.current = null;
}
if (
appState === 'active' &&
sparkInformation.didConnectToFlashnet &&
currentWalletMnemoincRef.current &&
poolInfo?.lpPublicKey
) {
// Delay to ensure everything is initialized
const capturedAuthKey = authResetKeyRef.current;
retryTimeoutRef.current = setTimeout(() => {
// Check if auth has changed before executing
if (capturedAuthKey !== authResetKeyRef.current) {
console.log('[Flashnet] Auth changed, skipping retry confirmations');
return;
}
retryPendingSwapConfirmations(
currentWalletMnemoincRef.current,
sparkInfoRef,
);
}, 3000);
}
return () => {
if (retryTimeoutRef.current) {
clearTimeout(retryTimeoutRef.current);
retryTimeoutRef.current = null;
}
};
}, [appState, sparkInformation.didConnectToFlashnet, poolInfo?.lpPublicKey]);
const handleAutoSwap = async (sparkRequestID, retryCount = 0) => {
let outboundTransferId;
try {
const MAX_RETRIES = 7;
const RETRY_DELAY = 2000;
console.log('Auto-swap triggered for sparkRequestID:', sparkRequestID);
if (triggeredSwapsRef.current.has(sparkRequestID)) {
console.warn(
'Auto-swap already triggered for sparkRequestID:',
sparkRequestID,
);
return;
} else {
triggeredSwapsRef.current.add(sparkRequestID);
}
await updateSparkTransactionDetails(sparkRequestID, {
swapExecuting: true,
lastSwapAttempt: Date.now(),
});
// Get the lightning request
const lightningRequest = await getSingleSparkLightningRequest(
sparkRequestID,
);
console.log(
'found saved lightning requset invoice for',
lightningRequest,
);
if (!lightningRequest) {
console.error('Lightning request not found');
triggeredSwapsRef.current.delete(sparkRequestID);
await updateSparkTransactionDetails(sparkRequestID, {
swapExecuting: false,
});
return;
}
// Check if we have the finalSparkID
if (!lightningRequest.details?.finalSparkID) {
console.error('No finalSparkID found in lightning request');
triggeredSwapsRef.current.delete(sparkRequestID);
await updateSparkTransactionDetails(sparkRequestID, {
swapExecuting: false,
});
return;
}
// Get the transaction details
const txDetails = await getSingleTxDetails(
currentWalletMnemoincRef.current,
lightningRequest.details.finalSparkID,
);
if (!txDetails) {
console.error('Transaction details not found');
triggeredSwapsRef.current.delete(sparkRequestID);
await updateSparkTransactionDetails(sparkRequestID, {
swapExecuting: false,
});
return;
}
const status = getSparkPaymentStatus(txDetails.status);
// Check if payment is settled
if (status !== 'completed') {
console.log('Payment not yet settled, status:', status);
// Retry logic
if (retryCount < MAX_RETRIES) {
setTimeout(() => {
triggeredSwapsRef.current.delete(sparkRequestID);
handleAutoSwap(sparkRequestID, retryCount + 1);
}, RETRY_DELAY);
return;
} else {
console.error('Max retries reached, payment still not settled');
triggeredSwapsRef.current.delete(sparkRequestID);
await updateSparkTransactionDetails(sparkRequestID, {
swapExecuting: false,
});
return;
}
}
const userRequest = txDetails.userRequest;
const invoice = userRequest ? userRequest.invoice?.encodedInvoice : '';
// Get the amount in sats
const amountSats = txDetails.totalValue;
if (
!amountSats ||
amountSats <= 0 ||
amountSats < swapLimitsRef.current.bitcoin
) {
console.error('Invalid amount for swap');
triggeredSwapsRef.current.delete(sparkRequestID);
deleteUnpaidSparkLightningTransaction(sparkRequestID);
const tx = {
id: lightningRequest.details?.finalSparkID,
accountId: sparkInfoRef.current.identityPubKey,
paymentStatus: 'pending',
paymentType: 'lightning',
details: {
performSwaptoUSD: false,
},
};
await bulkUpdateSparkTransactions([tx]);
return;
}
// Check if we have pool info
const currentPoolInfo = poolInfoRef.current;
if (!currentPoolInfo || !currentPoolInfo.lpPublicKey) {
console.error('Pool info not available');
triggeredSwapsRef.current.delete(sparkRequestID);
await updateSparkTransactionDetails(sparkRequestID, {
swapExecuting: false,
});
return;
}
console.log('Executing auto-swap:', {
amount: amountSats,
poolId: currentPoolInfo.lpPublicKey,
});
await updateSparkTransactionDetails(sparkRequestID, {
swapInitiated: true,
swapAmount: amountSats,
});
// Execute the swap
const result = await swapBitcoinToToken(
currentWalletMnemoincRef.current,
{
tokenAddress: USD_ASSET_ADDRESS,
amountSats: amountSats,
poolId: currentPoolInfo.lpPublicKey,
},
);
if (result.didWork && result.swap) {
outboundTransferId = result.swap.outboundTransferId;
addActiveSwap(result.swap.outboundTransferId);
savePendingSwapConfirmation(sparkRequestID, outboundTransferId);
await updateSparkTransactionDetails(lightningRequest.sparkID, {
completedSwaptoUSD: true,
swapExecuting: false,
swapRetryCount: 0,
});
console.log('Auto-swap completed successfully:', {
amountOut: result.swap.amountOut,
executionPrice: result.swap.executionPrice,
});
const success = await completeSwapConfirmation(
sparkRequestID,
outboundTransferId,
lightningRequest,
txDetails,
result,
invoice,
currentWalletMnemoincRef.current,
sparkInfoRef,
);
if (!success) {
console.warn(
'[Auto-swap] Confirmation failed, will retry on next load',
);
}
} else {
console.error('Auto-swap failed:', result.error);
if (outboundTransferId) {
removeActiveSwap(sparkRequestID);
}
// Mark as failed but not executing, so it can be retried
await updateSparkTransactionDetails(sparkRequestID, {
swapExecuting: false,
});
triggeredSwapsRef.current.delete(sparkRequestID);
}
} catch (error) {
console.error('Error in auto-swap handler:', error);
if (outboundTransferId) {
removeActiveSwap(outboundTransferId);
}
// Mark as failed but not executing
try {
await updateSparkTransactionDetails(sparkRequestID, {
swapExecuting: false,
});
} catch (updateError) {
console.error('Failed to update swap status:', updateError);
}
triggeredSwapsRef.current.delete(sparkRequestID);
}
};
// Monitor for stuck/failed swaps that need retry
const runPendingSwapsMonitor = async () => {
try {
if (appState !== 'active') return;
if (!sparkInformation.didConnectToFlashnet) return;
if (!currentWalletMnemoincRef.current) return;
if (!poolInfoRef.current?.lpPublicKey) return;
console.log('[Pending Swaps Monitor] Checking for stuck swaps...');
const pendingSwaps = await getPendingAutoSwaps();
if (!pendingSwaps || pendingSwaps.length === 0) {
return;
}
console.log(
`[Pending Swaps Monitor] Found ${pendingSwaps.length} pending swap(s)`,
);
for (const swapRequest of pendingSwaps) {
const sparkID = swapRequest.sparkID;
const details = swapRequest.details || {};
const finalSparkID = details.finalSparkID;
// Skip if already in our triggered set (currently processing)
if (triggeredSwapsRef.current.has(sparkID)) {
console.log(
`[Pending Swaps Monitor] Swap ${sparkID} already processing`,
);
continue;
}
// Handle payments already swapped but have not been removed from db yet
if (details.completedSwaptoUSD || isFlashnetTransfer(finalSparkID)) {
console.warn(
`[Pending Swaps Monitor] Blocking already completed swaps`,
);
continue;
}
const currentRetries = details.swapRetryCount || 0;
if (currentRetries >= MAX_SWAP_RETRIES) {
console.error(
`[Pending Swaps Monitor] Swap ${sparkID} exceeded max retries (${MAX_SWAP_RETRIES}), cleaning up`,
);
// Delete the lightning request
await deleteUnpaidSparkLightningTransaction(sparkID);
// Update the final transaction to remove swap flags if it exists
if (finalSparkID) {
const tx = {
id: finalSparkID,
accountId: sparkInfoRef.current.identityPubKey,
paymentStatus: 'completed',
paymentType: 'lightning',
details: {
performSwaptoUSD: false,
},
};
await bulkUpdateSparkTransactions([tx]);
}
continue;
}
// Handle swaps marked as executing (potential orphaned state from app closure)
if (details.swapExecuting) {
const lastAttempt = details.lastSwapAttempt || 0;
const timeSinceAttempt = Date.now() - lastAttempt;
const EXECUTION_TIMEOUT = 2 * 60 * 1000; // 2 minutes
console.log(
`[Pending Swaps Monitor] Swap ${sparkID} marked as executing`,
);
if (isFlashnetTransfer(finalSparkID)) {
console.log(
`[Pending Swaps Monitor] Swap ${sparkID} was already completed, marking as such`,
);
await updateSparkTransactionDetails(sparkID, {
completedSwaptoUSD: true,
swapExecuting: false,
});
continue;
}
// If it's been executing for too long, it's orphaned - reset and retry
if (timeSinceAttempt > EXECUTION_TIMEOUT) {
console.log(
`[Pending Swaps Monitor] Swap ${sparkID} execution timeout, resetting and retrying`,
);
await updateSparkTransactionDetails(sparkID, {
swapExecuting: false,
swapRetryCount: currentRetries + 1,
});
} else {
// Still within execution window, skip for now
console.log(
`[Pending Swaps Monitor] Swap ${sparkID} still within execution window`,
);
continue;
}
} else {
await updateSparkTransactionDetails(sparkID, {
swapRetryCount: currentRetries + 1,
});
}
console.log(`[Pending Swaps Monitor] Retrying swap ${sparkID}`);
// Trigger the swap
handleAutoSwap(sparkID);
// Add delay between retries to avoid overwhelming the system
await new Promise(res => setTimeout(res, 1000));
}
} catch (err) {
console.error('[Pending Swaps Monitor] error:', err);
}
};
const startPendingSwapsMonitor = () => {
if (swapMonitorIntervalRef.current) return;
const capturedAuthKey = authResetKeyRef.current;
runPendingSwapsMonitor(); // immediate pass
swapMonitorIntervalRef.current = setInterval(async () => {
// Check if auth has changed
if (capturedAuthKey !== authResetKeyRef.current) {
console.log('[Pending Swaps Monitor] Auth changed, stopping monitor');
stopPendingSwapsMonitor();
return;
}
await runPendingSwapsMonitor();
}, SWAP_MONITOR_INTERVAL);
};
const stopPendingSwapsMonitor = () => {
if (!swapMonitorIntervalRef.current) return;
clearInterval(swapMonitorIntervalRef.current);
swapMonitorIntervalRef.current = null;
};
const runRefundMonitor = async () => {
try {
// Hard guards — never trust effects alone
if (appState !== 'active') return;
if (!sparkInformation.didConnectToFlashnet) return;
if (!currentWalletMnemoincRef.current) return;
const refundableTransfers = await listClawbackableTransfers(
currentWalletMnemoincRef.current,
50,
);
if (
!refundableTransfers?.didWork ||
!refundableTransfers.resposne?.transfers.length
)
return;
const uniqueTransfers = Array.from(
new Map(
refundableTransfers.resposne.transfers.map(t => [t.id, t]),
).values(),
);
for (const transfer of uniqueTransfers) {
const transferId = transfer.id;
const eligibility = await checkClawbackEligibility(
currentWalletMnemoincRef.current,
transferId,
);
if (eligibility.didWork && eligibility.response) {
console.warn('[Flashnet Refund Monitor] Refundable', transferId);
const response = await requestManualClawback(
currentWalletMnemoincRef.current,
transferId,
transfer.lpIdentityPublicKey,
);
if (response.didWork && response.accepted) {
await new Promise(res => setTimeout(res, 500));
}
}
}
} catch (err) {
console.error('[Flashnet Refund Monitor] error:', err);
}
};
const startRefundMonitor = () => {
if (refundMonitorIntervalRef.current) return;
const capturedAuthKey = authResetKeyRef.current;
refundMonitorIntervalRef.current = setInterval(async () => {
// Check if auth has changed
if (capturedAuthKey !== authResetKeyRef.current) {
console.log('[Refund Monitor] Auth changed, stopping monitor');
stopRefundMonitor();
return;
}
await runRefundMonitor();
}, REFUND_MONITOR_INTERVAL);
};
const stopRefundMonitor = () => {
if (!refundMonitorIntervalRef.current) return;
clearInterval(refundMonitorIntervalRef.current);
refundMonitorIntervalRef.current = null;
};
// Set up the auto-swap event listener
useEffect(() => {
flashnetAutoSwapsEventListener.on(
HANDLE_FLASHNET_AUTO_SWAP,
handleAutoSwap,
);
loadSavedTransferIds();
return () => {
flashnetAutoSwapsEventListener.off(
HANDLE_FLASHNET_AUTO_SWAP,
handleAutoSwap,
);
};
}, []);
useEffect(() => {
if (
appState === 'active' &&
sparkInformation.didConnectToFlashnet &&
poolInfo?.lpPublicKey
) {
startPendingSwapsMonitor();
} else {
stopPendingSwapsMonitor();
}
return stopPendingSwapsMonitor;
}, [appState, sparkInformation.didConnectToFlashnet, poolInfo?.lpPublicKey]);
useEffect(() => {
let refundMonitorTimeout;
if (appState === 'active' && sparkInformation.didConnectToFlashnet) {
const capturedAuthKey = authResetKeyRef.current;
refundMonitorTimeout = setTimeout(() => {
// Check if auth has changed before executing
if (capturedAuthKey !== authResetKeyRef.current) {
console.log('[Refund Monitor] Auth changed, skipping initial run');
return;
}
runRefundMonitor();
}, 10_000);
startRefundMonitor();
} else {
stopRefundMonitor();
}
return () => {
if (refundMonitorTimeout) {
clearTimeout(refundMonitorTimeout);
}
stopRefundMonitor();
};
}, [appState, sparkInformation.didConnectToFlashnet]);
useEffect(() => {
const INITIAL_RETRY_DELAY = 5_000; // 5 seconds
const MAX_RETRY_DELAY = 120_000; // 2 minutes
const attemptFlashnetConnection = async () => {
try {
if (
sparkInformation.didConnect === true &&
!sparkInformation.didConnectToFlashnet &&
appState === 'active' &&
currentWalletMnemoincRef.current
) {
console.log(
`[Flashnet Retry] Attempting to initialize Flashnet... (delay: ${flashnetRetryDelayRef.current}ms)`,
);
const result = await initializeFlashnet(
currentWalletMnemoincRef.current,
);
if (result === true) {
console.log('[Flashnet Retry] Successfully initialized Flashnet');
setSparkInformation(prev => ({
...prev,
didConnectToFlashnet: true,
}));
// Reset delay on success
flashnetRetryDelayRef.current = INITIAL_RETRY_DELAY;
if (flashnetRetryIntervalRef.current) {
clearTimeout(flashnetRetryIntervalRef.current);
flashnetRetryIntervalRef.current = null;
}
} else {
console.warn(
`[Flashnet Retry] Failed to initialize, will retry in ${
flashnetRetryDelayRef.current / 1000
}s...`,
);
// Schedule next retry with exponential backoff
if (flashnetRetryIntervalRef.current) {
clearTimeout(flashnetRetryIntervalRef.current);
}
const capturedAuthKey = authResetKeyRef.current;
flashnetRetryIntervalRef.current = setTimeout(() => {
// Check if auth has changed
if (capturedAuthKey !== authResetKeyRef.current) {
console.log('[Flashnet Retry] Auth changed, aborting retry');
return;
}
attemptFlashnetConnection();
}, flashnetRetryDelayRef.current);
// Double the delay for next time, capped at MAX_RETRY_DELAY
flashnetRetryDelayRef.current = Math.min(
flashnetRetryDelayRef.current * 2,
MAX_RETRY_DELAY,
);
}
}
} catch (error) {
console.error('[Flashnet Retry] Error during initialization:', error);
// Schedule retry on error as well
if (flashnetRetryIntervalRef.current) {
clearTimeout(flashnetRetryIntervalRef.current);
}
const capturedAuthKey = authResetKeyRef.current;
flashnetRetryIntervalRef.current = setTimeout(() => {
// Check if auth has changed
if (capturedAuthKey !== authResetKeyRef.current) {
console.log(
'[Flashnet Retry] Auth changed, aborting retry after error',
);
return;
}
attemptFlashnetConnection();
}, flashnetRetryDelayRef.current);
flashnetRetryDelayRef.current = Math.min(
flashnetRetryDelayRef.current * 2,
MAX_RETRY_DELAY,
);
}
};
if (
sparkInformation.didConnect === true &&
!sparkInformation.didConnectToFlashnet &&
appState === 'active'
) {
// Reset delay when starting fresh
flashnetRetryDelayRef.current = INITIAL_RETRY_DELAY;
// Immediate first attempt
attemptFlashnetConnection();
} else {
// Clean up timeout if conditions no longer met
if (flashnetRetryIntervalRef.current) {
clearTimeout(flashnetRetryIntervalRef.current);
flashnetRetryIntervalRef.current = null;
}
// Reset delay when stopping
flashnetRetryDelayRef.current = INITIAL_RETRY_DELAY;
}
return () => {
if (flashnetRetryIntervalRef.current) {
clearTimeout(flashnetRetryIntervalRef.current);
flashnetRetryIntervalRef.current = null;
}
};
}, [
sparkInformation.didConnect,
sparkInformation.didConnectToFlashnet,
appState,
]);
useEffect(() => {
// Stop any existing interval first
if (poolIntervalRef.current) {
clearInterval(poolIntervalRef.current);
poolIntervalRef.current = null;
}
// Only start polling when conditions are correct
if (!sparkInformation.didConnectToFlashnet) return;
if (appState !== 'active') return;
// Start interval
const capturedAuthKey = authResetKeyRef.current;
poolIntervalRef.current = setInterval(() => {
// Check if auth has changed
if (capturedAuthKey !== authResetKeyRef.current) {
console.log('[Pool Refresh] Auth changed, stopping refresh');
if (poolIntervalRef.current) {
clearInterval(poolIntervalRef.current);
poolIntervalRef.current = null;
}
return;
}
refreshPool();
}, 30_000);
// Run immediately on activation
const refreshPoolTimeout = setTimeout(() => {
// Check if auth has changed
if (capturedAuthKey !== authResetKeyRef.current) {
console.log('[Pool Refresh] Auth changed, skipping initial refresh');
return;
}
refreshPool();
}, 2000);
return () => {
if (refreshPoolTimeout) {
clearTimeout(refreshPoolTimeout);
}
if (poolIntervalRef.current) {
clearInterval(poolIntervalRef.current);
poolIntervalRef.current = null;
}
};
}, [appState, sparkInformation.didConnectToFlashnet]);
useEffect(() => {
if (!sparkInformation.didConnectToFlashnet) return;
async function getLimits() {
const [usdLimits, bitconLimits] = await Promise.all([
minFlashnetSwapAmounts(
currentWalletMnemoincRef.current,
USD_ASSET_ADDRESS,
),
minFlashnetSwapAmounts(
currentWalletMnemoincRef.current,
BTC_ASSET_ADDRESS,
),
]);
if (usdLimits.didWork && bitconLimits.didWork) {
setSwapLimits({
usd: parseFloat(
(
(Number(usdLimits.assetData) / 1000000) *
SEND_AMOUNT_INCREASE_BUFFER
).toFixed(2),
),
bitcoin: Math.round(
Number(bitconLimits.assetData) * SEND_AMOUNT_INCREASE_BUFFER,
),
});
}
}
getLimits();
}, [sparkInformation.didConnectToFlashnet]);
const swapUSDPriceDollars = useMemo(() => {
return currentPriceAinBToPriceDollars(poolInfo?.currentPriceAInB);
}, [poolInfo?.currentPriceAInB]);
// Clean up all intervals and timeouts when auth resets
useEffect(() => {
if (isInitialLoad.current) {
isInitialLoad.current = false;
return;
}
console.log('[Flashnet] Auth reset detected, cleaning up all processes');
stopRefundMonitor();
stopPendingSwapsMonitor();
if (poolIntervalRef.current) {
clearInterval(poolIntervalRef.current);
poolIntervalRef.current = null;
}
if (flashnetRetryIntervalRef.current) {
clearTimeout(flashnetRetryIntervalRef.current);
flashnetRetryIntervalRef.current = null;
}
if (retryTimeoutRef.current) {
clearTimeout(retryTimeoutRef.current);
retryTimeoutRef.current = null;
}
// Clear triggered swaps set
triggeredSwapsRef.current.clear();
// Reset retry delay
flashnetRetryDelayRef.current = 5_000;
}, [authResetkey]);
const contextValue = useMemo(() => {
return {
poolInfo,
togglePoolInfo,
poolInfoRef: poolInfoRef.current,
swapLimits,
swapUSDPriceDollars,
};
}, [poolInfo, togglePoolInfo, swapLimits, swapUSDPriceDollars]);
return (
<FlashnetContext.Provider value={contextValue}>
{children}
</FlashnetContext.Provider>
);
}
export const useFlashnet = () => useContext(FlashnetContext);