fun wallet optimizations after sending

This commit is contained in:
Blake Kaufman
2026-01-08 09:30:53 -05:00
parent 2828b98674
commit ff05795815
7 changed files with 345 additions and 4 deletions
File diff suppressed because one or more lines are too long
+7 -1
View File
@@ -246,6 +246,12 @@ export default function getFormattedHomepageTxsForSpark(props) {
paymentDetails.LRC20Token !== USDB_TOKEN_ID
)
continue;
if (
paymentDetails.senderIdentityPublicKey ===
process.env.SPARK_IDENTITY_PUBKEY
)
continue;
if (shownTxs.has(currentTransaction.sparkID)) continue;
if (isLRC20Payment && !hasSavedTokenData) continue;
if (paymentStatus === TRANSACTION_CONSTANTS.FAILED) continue;
@@ -588,7 +594,7 @@ export const UserTransaction = memo(function UserTransaction({
: transaction.details.amount
}
useCustomLabel={isLRC20Payment}
customLabel={token?.tokenTicker?.slice(0, 3)}
customLabel={token?.tokenTicker}
useMillionDenomination={true}
/>
)}
+1 -1
View File
@@ -44,7 +44,7 @@ const getMnemonicHash = mnemonic => {
return mnemonicHashCache.get(mnemonic);
};
const getWallet = async mnemonic => {
export const getWallet = async mnemonic => {
const hash = getMnemonicHash(mnemonic);
let wallet = sparkWallet[hash];
+257
View File
@@ -0,0 +1,257 @@
import { AppState } from 'react-native';
import { getWallet, selectSparkRuntime } from './index';
import sha256Hash from '../hash';
import {
sendWebViewRequestGlobal,
OPERATION_TYPES,
} from '../../../context-store/webViewContext';
// Global state for optimization
const optimizationState = {
isLeafOptimizationRunning: false,
isTokenOptimizationRunning: false,
controller: null,
timeout: null,
};
/**
* Abort any running optimization immediately
*/
export const abortOptimization = async mnemonic => {
console.log('Aborting optimization immediately...');
const runtime = await selectSparkRuntime(mnemonic);
if (runtime === 'native') {
if (optimizationState.controller) {
optimizationState.controller.abort();
optimizationState.controller = null;
}
if (optimizationState.timeout) {
clearTimeout(optimizationState.timeout);
optimizationState.timeout = null;
}
optimizationState.isLeafOptimizationRunning = false;
optimizationState.isTokenOptimizationRunning = false;
} else {
await sendWebViewRequestGlobal(OPERATION_TYPES.abortOptimization, {
mnemonic,
});
}
};
/**
* Check if any optimization is currently running
*/
export const isOptimizationRunning = async mnemonic => {
const runtime = await selectSparkRuntime(mnemonic);
if (runtime === 'native') {
return (
optimizationState.isLeafOptimizationRunning ||
optimizationState.isTokenOptimizationRunning
);
} else {
const result = await sendWebViewRequestGlobal(
OPERATION_TYPES.isOptimizationRunning,
{ mnemonic },
);
console.log('is opimization running result');
return result;
}
};
/**
* Check if optimization is needed for a wallet
*/
export const checkIfOptimizationNeeded = async mnemonic => {
try {
const runtime = await selectSparkRuntime(mnemonic);
if (runtime === 'native') {
const wallet = await getWallet(mnemonic);
if (!wallet) return false;
const [isLeafOptInProgress, isTokenOptInProgress] = await Promise.all([
wallet.isOptimizationInProgress(),
wallet.isTokenOptimizationInProgress(),
]);
// If already in progress, don't start another
if (isLeafOptInProgress || isTokenOptInProgress) {
return false;
}
const leaves = await wallet.getLeaves();
console.log(leaves);
return false;
} else {
const result = await sendWebViewRequestGlobal(
OPERATION_TYPES.checkIfOptimizationNeeded,
{ mnemonic },
);
return result?.needed || false;
}
} catch (error) {
console.error('Error checking if optimization needed:', error);
return false;
}
};
/**
* Run leaf optimization
*/
export const runLeafOptimization = async (mnemonic, identityPubKey) => {
if (await isOptimizationRunning(mnemonic)) {
console.log('Optimization already running, skipping');
return { didWork: false, reason: 'already_running' };
}
if (AppState.currentState !== 'active') {
console.log('App not active, skipping optimization');
return { didWork: false, reason: 'app_not_active' };
}
if (!identityPubKey) {
console.log('No identity pub key, skipping optimization');
return { didWork: false, reason: 'no_identity' };
}
try {
optimizationState.isLeafOptimizationRunning = true;
const runtime = await selectSparkRuntime(mnemonic);
console.log('Starting leaf optimization...');
if (runtime === 'native') {
const wallet = await getWallet(mnemonic);
if (!wallet) {
throw new Error('Wallet not initialized');
}
for await (const progress of wallet.optimizeLeaves()) {
// Store controller for abortion
optimizationState.controller = progress.controller;
console.log(
`Optimization progress: ${progress.step}/${progress.total}`,
);
// Check if we should abort
if (!optimizationState.isLeafOptimizationRunning) {
console.log('Optimization aborted by external signal');
progress.controller.abort();
break;
}
}
} else {
// WebView optimization
await sendWebViewRequestGlobal(OPERATION_TYPES.runLeafOptimization, {
mnemonic,
});
}
console.log('Leaf optimization complete');
return { didWork: true };
} catch (error) {
console.error('Error during leaf optimization:', error);
return { didWork: false, error: error.message };
} finally {
optimizationState.isLeafOptimizationRunning = false;
optimizationState.controller = null;
}
};
/**
* Run token optimization
*/
export const runTokenOptimization = async (mnemonic, identityPubKey) => {
if (await isOptimizationRunning(mnemonic)) {
console.log('Optimization already running, skipping');
return { didWork: false, reason: 'already_running' };
}
if (AppState.currentState !== 'active') {
console.log('App not active, skipping token optimization');
return { didWork: false, reason: 'app_not_active' };
}
if (!identityPubKey) {
console.log('No identity pub key, skipping token optimization');
return { didWork: false, reason: 'no_identity' };
}
try {
optimizationState.isTokenOptimizationRunning = true;
const runtime = await selectSparkRuntime(mnemonic);
console.log('Starting token optimization...');
if (runtime === 'native') {
const wallet = await getWallet(mnemonic);
if (!wallet) {
throw new Error('Wallet not initialized');
}
await wallet.optimizeTokenOutputs();
} else {
await sendWebViewRequestGlobal(OPERATION_TYPES.runTokenOptimization, {
mnemonic,
});
}
console.log('Token optimization complete');
return { didWork: true };
} catch (error) {
console.error('Error during token optimization:', error);
return { didWork: false, error: error.message };
} finally {
optimizationState.isTokenOptimizationRunning = false;
}
};
/**
* Schedule optimization to run after a delay
*/
export const scheduleOptimization = async (
mnemonic,
identityPubKey,
delayMs = 5000,
) => {
// Clear any existing scheduled optimization
if (optimizationState.timeout) {
clearTimeout(optimizationState.timeout);
optimizationState.timeout = null;
}
console.log(`Scheduling optimization in ${delayMs}ms...`);
optimizationState.timeout = setTimeout(async () => {
if (AppState.currentState !== 'active') {
console.log('App not active, skipping scheduled optimization');
return;
}
if (!identityPubKey) {
console.log('No identity pub key, skipping scheduled optimization');
return;
}
const needed = await checkIfOptimizationNeeded(mnemonic);
if (needed) {
console.log('Running scheduled optimization...');
// Run leaf optimization first, then token optimization
const leafResult = await runLeafOptimization(mnemonic, identityPubKey);
if (leafResult.didWork) {
await runTokenOptimization(mnemonic, identityPubKey);
}
} else {
console.log('Optimization not needed at this time');
}
}, delayMs);
};
/**
* Cleanup optimization state (call on logout/unmount)
*/
export const cleanupOptimization = mnemoinc => {
abortOptimization(mnemoinc);
};
+19
View File
@@ -33,6 +33,11 @@ import {
payLightningWithToken,
USD_ASSET_ADDRESS,
} from './flashnet';
import {
abortOptimization,
isOptimizationRunning,
scheduleOptimization,
} from '../spark/optimization';
import { setFlashnetTransfer } from './handleFlashnetTransferIds';
import {
addSingleUnpaidSparkLightningTransaction,
@@ -65,6 +70,15 @@ export const sparkPaymenWrapper = async ({
}) => {
try {
console.log('Begining spark payment');
if (!getFee && (await isOptimizationRunning(mnemonic))) {
console.log(
'Optimization in progress, aborting immediately for payment...',
);
await abortOptimization(mnemonic);
// Small delay to ensure abort completes
await new Promise(resolve => setTimeout(resolve, 100));
}
// if (!sparkWallet[sha256Hash(mnemonic)])
// throw new Error('sparkWallet not initialized');
const supportFee = 0;
@@ -516,6 +530,11 @@ export const sparkPaymenWrapper = async ({
if (sparkInformation.identityPubKey) {
await bulkUpdateSparkTransactions([response], 'paymentWrapperTx', 0);
}
if (!getFee) {
console.log('Scheduling post-payment optimization...');
scheduleOptimization(mnemonic, sparkInformation.identityPubKey, 2000);
}
return {
didWork: true,
response,
+49
View File
@@ -67,6 +67,12 @@ import {
createRestorePoller,
} from '../app/functions/pollingManager';
import { USDB_TOKEN_ID } from '../app/constants';
import {
cleanupOptimization,
checkIfOptimizationNeeded,
runLeafOptimization,
runTokenOptimization,
} from '../app/functions/spark/optimization';
export const isSendingPayingEventEmiiter = new EventEmitter();
export const SENDING_PAYMENT_EVENT_NAME = 'SENDING_PAYMENT_EVENT';
@@ -642,6 +648,13 @@ const SparkWalletProvider = ({ children }) => {
const details = parsedTx?.details;
if (
details.senderIdentityPublicKey === process.env.SPARK_IDENTITY_PUBKEY
) {
console.log('Refund from Spark, do not show tosat here');
return;
}
if (new Date(details.time).getTime() < sessionTimeRef.current) {
console.log(
'created before session time was set, skipping confirm tx page navigation',
@@ -978,10 +991,46 @@ const SparkWalletProvider = ({ children }) => {
currentPollingMnemonicRef.current = null;
};
// optimizations for leaves and tokens
useEffect(() => {
if (!sparkInformation.didConnect) return;
if (!sparkInformation.identityPubKey) return;
if (!didGetToHomepage) return;
if (AppState.currentState !== 'active') return;
const runInitialOptimizationCheck = async () => {
if (!sparkInfoRef.current.identityPubKey) return;
if (AppState.currentState !== 'active') return;
const needed = await checkIfOptimizationNeeded(
currentMnemonicRef.current,
);
if (needed) {
console.log('Running initial optimization check...');
await runLeafOptimization(
currentMnemonicRef.current,
sparkInfoRef.current.identityPubKey,
);
await runTokenOptimization(
currentMnemonicRef.current,
sparkInfoRef.current.identityPubKey,
);
}
};
runInitialOptimizationCheck();
}, [
sparkInformation.didConnect,
sparkInformation.identityPubKey,
didGetToHomepage,
]);
const resetSparkState = useCallback(async (internalRefresh = false) => {
// Reset refs to initial values
await removeListeners(true);
clearMnemonicCache();
cleanupOptimization(prevAccountMnemoincRef.current);
prevAccountMnemoincRef.current = null;
isRunningAddListeners.current = false;
if (depositAddressIntervalRef.current) {
+10
View File
@@ -77,6 +77,13 @@ export const OPERATION_TYPES = {
checkClawbackStatus: 'checkClawbackStatus',
requestBatchClawback: 'requestBatchClawback',
listClawbackableTransfers: 'listClawbackableTransfers',
// Wallet optimizations
abortOptimization: 'abortOptimization',
isOptimizationRunning: 'isOptimizationRunning',
checkIfOptimizationNeeded: 'checkIfOptimizationNeeded',
runLeafOptimization: 'runLeafOptimization',
runTokenOptimization: 'runTokenOptimization',
};
const longOperations = [
@@ -94,6 +101,8 @@ const longOperations = [
OPERATION_TYPES.swapTokenToBitcoin,
OPERATION_TYPES.payLightningWithToken,
OPERATION_TYPES.requestClawback,
OPERATION_TYPES.runLeafOptimization,
OPERATION_TYPES.runTokenOptimization,
];
const mediumOperations = [
@@ -111,6 +120,7 @@ const mediumOperations = [
OPERATION_TYPES.setPrivacyEnabled,
OPERATION_TYPES.simulateSwap,
OPERATION_TYPES.requestBatchClawback,
OPERATION_TYPES.checkIfOptimizationNeeded,
];
const rejectIfNotConnectedToInternet = [