diff --git a/output.log b/output.log new file mode 100644 index 0000000..b21a667 Binary files /dev/null and b/output.log differ diff --git a/package.json b/package.json index 9acbe3c..210aded 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minibits_wallet", - "version": "0.1.9-beta.30", + "version": "0.1.9-beta.31", "private": true, "scripts": { "android:clean": "cd android && ./gradlew clean", diff --git a/src/models/Mint.ts b/src/models/Mint.ts index 90bbd14..30f2ab4 100644 --- a/src/models/Mint.ts +++ b/src/models/Mint.ts @@ -15,9 +15,7 @@ import { MintUnit, MintUnits } from '../services/wallet/currency' import { getRootStore } from './helpers/getRootStore' import { generateId } from '../utils/utils' import { ProofV3 } from '../services/cashu/cashuUtils' -import { RootStoreModel } from './RootStore' -import { MintsStoreModel } from './MintsStore' -import { WalletStoreModel } from './WalletStore' +import { Proof } from './Proof' export type MintBalance = { @@ -367,7 +365,7 @@ export const MintModel = types self.proofsCounters = cast(self.proofsCounters) }, - getMintFeeReserve(proofs: ProofV3[]): number { + getMintFeeReserve(proofs: ProofV3[] | Proof[]): number { // Find the corresponding keyset for each proof and sum the input fees const totalInputFees = proofs.reduce((sum, proof) => { const keyset = self.keysets.find(k => k.id === proof.id) diff --git a/src/models/TransactionsStore.ts b/src/models/TransactionsStore.ts index 0b8f4aa..3999903 100644 --- a/src/models/TransactionsStore.ts +++ b/src/models/TransactionsStore.ts @@ -304,7 +304,7 @@ export const TransactionsStoreModel = types transactionsMap: prunedTransactionsMap } - console.log('[postProcessSnapshot]', {prunedSnapshot}) + // console.log('[postProcessSnapshot]', {prunedSnapshot}) return prunedSnapshot }) diff --git a/src/models/helpers/setupRootStore.ts b/src/models/helpers/setupRootStore.ts index fef2c5f..a5f3cb2 100644 --- a/src/models/helpers/setupRootStore.ts +++ b/src/models/helpers/setupRootStore.ts @@ -59,7 +59,7 @@ export async function setupRootStore(rootStore: RootStore) { const mmkvLoaded = performance.now() const dataSize = Buffer.byteLength(JSON.stringify(restoredState), 'utf8') - log.trace({restoredState}) + // log.trace({restoredState}) log.trace('[setupRootStore]', `Loading ${dataSize.toLocaleString()} bytes of state from MMKV took ${(mmkvLoaded - start).toLocaleString()} ms.`) applySnapshot(rootStore, restoredState) @@ -83,11 +83,12 @@ export async function setupRootStore(rootStore: RootStore) { } // track changes & save snapshot to the storage not more then once per second - const saveSnapshot = debounce((snapshot) => { + const saveSnapshot = debounce((snapshot) => { MMKVStorage.save(ROOT_STORAGE_KEY, snapshot) }, 1000) - _disposer = onSnapshot(rootStore, snapshot => { + _disposer = onSnapshot(rootStore, snapshot => { + // log.trace('[setupRootStore] onSnapshot *** MMKV SHOULD SAVE ***') saveSnapshot(snapshot) // log.trace('[setupRootStore] saved', {walletStore: snapshot.walletStore}) }) diff --git a/src/screens/TranHistoryScreen.tsx b/src/screens/TranHistoryScreen.tsx index 8b453b5..adf6130 100644 --- a/src/screens/TranHistoryScreen.tsx +++ b/src/screens/TranHistoryScreen.tsx @@ -267,7 +267,7 @@ export const TranHistoryScreen: FC> /> {} @@ -98,8 +100,8 @@ export const WalletScreen: FC = observer( const [defaultMintUrl, setDefaultMintUrl] = useState(MINIBITS_MINT_URL) const [error, setError] = useState() const [isLoading, setIsLoading] = useState(false) - const [lastClaimCheck, setLastClaimCheck] = useState(getUnixTime(new Date())) - const [lastPendingCheck, setLastPendingCheck] = useState(getUnixTime(new Date())) + const [lastClaimCheck, setLastClaimCheck] = useState(0) + const [lastPendingCheck, setLastPendingCheck] = useState(0) const [isMintsModalVisible, setIsMintsModalVisible] = useState(false) const [isUpdateAvailable, setIsUpdateAvailable] = useState(false) const [isUpdateModalVisible, setIsUpdateModalVisible] = useState(false) @@ -165,17 +167,12 @@ export const WalletScreen: FC = observer( if(groupedMints.length === 0) { await addMint() } - - // check lnaddress claims on app start and set timestamp to trigger focus updates - WalletTask.handleClaim().catch(e => setInfo(e.message)) - // Auto-recover inflight proofs - do only on startup and before checkPendingReceived to prevent conflicts + + // Only once on startup - auto-recover inflight proofs WalletTask.handleInFlight().catch(e => false) - // Create websocket subscriptions to receive tokens or payment requests by NOSTR DMs - WalletTask.receiveEventsFromRelays().catch(e => false) - // Get exchange rate - if(userSettingsStore.exchangeCurrency) { - walletStore.refreshExchangeRate(userSettingsStore.exchangeCurrency!) - } + // Only once on startup - Create websocket subscriptions to receive tokens or payment requests by NOSTR DMs + WalletTask.receiveEventsFromRelays().catch(e => false) + // Set wallet tab to preferred unit const preferredUnit: MintUnit = userSettingsStore.preferredUnit const preferredTabIndex = routes.findIndex(route => route.key === preferredUnit) @@ -253,16 +250,18 @@ export const WalletScreen: FC = observer( } const nowInSec = getUnixTime(new Date()) - // log.trace('[useFocusEffect]', {nowInSec, lastClaimCheck, delay: lastClaimCheck ? nowInSec - lastClaimCheck : undefined}) + log.trace('[useFocusEffect] Start', {secsFromLastPending: nowInSec - lastPendingCheck, secsFromLastClaim: nowInSec - lastClaimCheck}) - if(lastPendingCheck && nowInSec - lastPendingCheck > 10) { + // On startup and on re-focus if some secs passed + if(nowInSec - lastPendingCheck > PENDING_CHECK_INTERVAL) { + setLastPendingCheck(nowInSec) WalletTask.syncPendingStateWithMints().catch(e => false) WalletTask.handlePendingTopups().catch(e => false) } else { log.trace('[useFocusEffect] Skipping pending checks...') } - if(lastClaimCheck && nowInSec - lastClaimCheck > 60) { + if(nowInSec - lastClaimCheck > CLAIM_CHECK_INTERVAL) { setLastClaimCheck(nowInSec) WalletTask.handleClaim().catch(e => false) @@ -276,7 +275,7 @@ export const WalletScreen: FC = observer( } - }, [lastClaimCheck, isInternetReachable, userSettingsStore.exchangeCurrency]) + }, [lastPendingCheck, lastClaimCheck, isInternetReachable, userSettingsStore.exchangeCurrency]) ) @@ -303,16 +302,17 @@ export const WalletScreen: FC = observer( const nowInSec = getUnixTime(new Date()) - // log.trace('[appState]', { nowInSec, lastClaimCheck, delay: lastClaimCheck ? nowInSec - lastClaimCheck : undefined }); + log.trace('[handleAppStateChange] Start', {secsFromLastPending: nowInSec - lastPendingCheck, secsFromLastClaim: nowInSec - lastClaimCheck}) - if (lastPendingCheck && nowInSec - lastPendingCheck > 10) { + if (nowInSec - lastPendingCheck > PENDING_CHECK_INTERVAL) { + setLastPendingCheck(nowInSec) WalletTask.syncPendingStateWithMints().catch(e => false) WalletTask.handlePendingTopups().catch(e => false) } else { log.trace('[handleAppStateChange] Skipping pending checks...') } - if(lastClaimCheck && nowInSec - lastClaimCheck > 60) { + if(nowInSec - lastClaimCheck > CLAIM_CHECK_INTERVAL) { setLastClaimCheck(nowInSec) WalletTask.handleClaim().catch(e => false) @@ -336,7 +336,7 @@ export const WalletScreen: FC = observer( return () => { subscription.remove(); // Ensure cleanup to avoid multiple listeners }; - }, [lastClaimCheck, lastPendingCheck, isInternetReachable, userSettingsStore.exchangeCurrency]) + }, [lastPendingCheck, lastClaimCheck, isInternetReachable, userSettingsStore.exchangeCurrency]) const toggleMintsModal = () => { diff --git a/src/services/mmkvStorage.ts b/src/services/mmkvStorage.ts index e054e17..3bfec34 100644 --- a/src/services/mmkvStorage.ts +++ b/src/services/mmkvStorage.ts @@ -212,7 +212,7 @@ const save = function (key: string, value: any): boolean { const end = performance.now() // const dataSize = Buffer.byteLength(JSON.stringify(value), 'utf8') - // log.trace(`[mmkvStorage.save] Took ${end - start} ms to save ${dataSize} bytes.`) + // log.trace(`[mmkvStorage.save] *** MMKV REAL SAVE *** Took ${end - start} ms to save ${dataSize} bytes.`) return true } catch (e: any) { diff --git a/src/services/wallet/sendTask.ts b/src/services/wallet/sendTask.ts index 1dfffd1..8c5dfcb 100644 --- a/src/services/wallet/sendTask.ts +++ b/src/services/wallet/sendTask.ts @@ -140,7 +140,7 @@ export const sendTask = async function ( log.trace('[send] totalBalance after', balanceAfter) - // Start polling for accepted payment is it is not offline send + // Start polling for accepted payment it is not an offline send if(selectedProofs.length === 0) { const proofsToSync = proofsStore.getByMint(mintUrl, {isPending: true}) @@ -214,7 +214,7 @@ export const sendFromMintSync = async function ( ) } - const proofsFromMint = proofsStore.getByMint(mintUrl, {isPending: false, unit}) as Proof[] + const proofsFromMint = proofsStore.getByMint(mintUrl, {isPending: false, unit}) log.debug('[sendFromMintSync]', 'proofsFromMint count', {mintBalance: mintBalance.balances[unit], amountToSend}) @@ -399,7 +399,7 @@ export const sendFromMintSync = async function ( // release lock lockedProofsCounter.resetInFlight(transactionId) - } else if (returnedAmount === 0) { + } else { /* * SWAP is NOT needed, we've found denominations that match exact amount * @@ -416,9 +416,7 @@ export const sendFromMintSync = async function ( proofsToSend = [...proofsToSendFrom] - } else { - throw new AppError(Err.VALIDATION_ERROR, 'Amount to keep can not be negative') - } + } // remove used proofs and move sent proofs to pending proofsStore.removeProofs(proofsToSendFrom) diff --git a/src/services/wallet/transferTask.ts b/src/services/wallet/transferTask.ts index 03b7058..d92d3ae 100644 --- a/src/services/wallet/transferTask.ts +++ b/src/services/wallet/transferTask.ts @@ -97,7 +97,7 @@ export const transferTask = async function ( } // calculate fees charged by mint for melt transaction to prepare enough proofs - const proofsFromMint = proofsStore.getByMint(mintUrl, {isPending: false, unit}) as Proof[] + const proofsFromMint = proofsStore.getByMint(mintUrl, {isPending: false, unit}) let proofsToMelt = CashuUtils.getProofsToSend( amountToTransfer + meltQuote.fee_reserve, @@ -120,9 +120,14 @@ export const transferTask = async function ( [], transaction.id, ) + + const { + proofs: proofsToPay, + mintFeePaid, + mintFeeReserve, + isSwapNeeded + } = swapResult - proofsToPay = swapResult.proofs - const {mintFeePaid, mintFeeReserve, isSwapNeeded} = swapResult proofsToPayAmount = CashuUtils.getProofsAmount(proofsToPay) // TODO in case of swap from inactive keysets, different meltFees might apply than above calculated meltFeeReserve @@ -135,6 +140,7 @@ export const transferTask = async function ( status: TransactionStatus.PREPARED, mintFeeReserve, mintFeePaid, + proofsToPayAmount, isSwapNeeded, createdAt: new Date(), }) @@ -182,28 +188,14 @@ export const transferTask = async function ( lockedProofsCounter.decreaseProofsCounter(countOfInFlightProofs) - // update transaction status and proofs state based on sync with the mint - // proofsToPay are clean ProofV3, not Proof models so we send all pending from state - /* const proofsToSync = proofsStore.getByMint(mintUrl, {isPending: true}) - const { completedTransactionIds, transactionStateUpdates } = await WalletTask.syncStateWithMintSync( - { - proofsToSync, - mintUrl, - isPending: true - } - ) - - if(!completedTransactionIds.includes(transaction.id)) { - // silent - log.warn('[transfer] payLightningMelt call suceeded but proofs were not spent by mint', {transactionStateUpdates}) - }*/ - - // If real fees were less then estimated, cash the returned savings. - if (state === MeltQuoteState.PAID) { + log.trace('[transfer] Invoice PAID', {state, preimage}) + // Spend pending proofs that were used to settle the lightning invoice + proofsStore.removeProofs(proofsToPay as Proof[], true, false) + let lightningFeePaid = meltQuote.fee_reserve - if (feeSavedProofs.length) { + if (feeSavedProofs.length > 0) { const {addedAmount: feeSaved} = WalletUtils.addCashuProofs( mintUrl, feeSavedProofs, diff --git a/src/services/walletService.ts b/src/services/walletService.ts index 8276926..b539924 100644 --- a/src/services/walletService.ts +++ b/src/services/walletService.ts @@ -434,7 +434,7 @@ const syncPendingStateWithMints = async function (): Promise { } const isPending = true - // const maxBatchSize = MAX_CHECK_INPUT_SIZE + const maxBatchSize = MAX_SYNC_INPUT_SIZE // group proofs by mint so that we do max one call per mint for (const mint of mintsStore.allMints) { @@ -445,8 +445,17 @@ const syncPendingStateWithMints = async function (): Promise { } const proofsToSync = proofsStore.getByMint(mint.mintUrl, {isPending}) - const totalProofs = proofsToSync.length - syncStateWithMint({ proofsToSync, mintUrl: mint.mintUrl, isPending }) + const totalProofsCount = proofsToSync.length + + if (totalProofsCount > maxBatchSize) { + for (let i = 0; i < totalProofsCount; i += maxBatchSize) { + const batch = proofsToSync.slice(i, i + maxBatchSize) + syncStateWithMint({ proofsToSync: batch, mintUrl: mint.mintUrl, isPending }) + } + } else { + // If the length is less than or equal to 100, run syncStateWithMint with all proofs. + syncStateWithMint({ proofsToSync, mintUrl: mint.mintUrl, isPending }); + } } } @@ -473,7 +482,7 @@ const syncSpendableStateWithMints = async function (): Promise { if (totalProofsCount > maxBatchSize) { for (let i = 0; i < totalProofsCount; i += maxBatchSize) { const batch = proofsToSync.slice(i, i + maxBatchSize) - syncStateWithMint({ proofsToSync: batch, mintUrl: mint.mintUrl, isPending }); + syncStateWithMint({ proofsToSync: batch, mintUrl: mint.mintUrl, isPending }) } } else { // If the length is less than or equal to 100, run syncStateWithMint with all proofs. @@ -495,7 +504,7 @@ const syncStateWithMint = async function ( } ): Promise { const {mintUrl, isPending, proofsToSync} = options - log.trace('[syncStateWithMint] start', {mintUrl, isPending}) + log.trace('[syncStateWithMint] start', {mintUrl, isPending, proofsToSyncCount: proofsToSync.length}) const now = new Date().getTime() return SyncQueue.addTask( @@ -516,7 +525,7 @@ const syncStateWithMintSync = async function ( ): Promise { const {mintUrl, isPending, proofsToSync} = options - log.trace('[syncStateWithMintSync] start', {mintUrl, isPending, proofsToSyncCount: proofsToSync?.length}) + log.trace('[syncStateWithMintSync] start', {mintUrl, isPending, proofsToSyncCount: proofsToSync.length}) return await _syncStateWithMintTask({proofsToSync, mintUrl, isPending}) } @@ -636,7 +645,7 @@ const _syncStateWithMintTask = async function ( if (tx) { // spent amount does not cover matched tx amount - // means that some spent proofs were used as inputs into the send + // means that some spent proofs were used as inputs into the swap / melt if(spentByMintTxAmount < tx.amount) { errorTransactionIds.push(Number(tId)) @@ -679,7 +688,8 @@ const _syncStateWithMintTask = async function ( return { tId: Number(tId), - updatedStatus: TransactionStatus.ERROR + updatedStatus: TransactionStatus.ERROR, + message: 'Could not find transaction in the database.' } as TransactionStateUpdate })