diff --git a/index.js b/index.js
index cf050d99..b40ad98b 100644
--- a/index.js
+++ b/index.js
@@ -8,7 +8,14 @@ import notifee from '@notifee/react-native'
import messaging from '@react-native-firebase/messaging'
import {AppRegistry} from 'react-native'
import { rootStoreInstance, setupRootStore } from './src/models'
-import {WalletTask, NotificationService, NWC_CHANNEL_ID, TASK_QUEUE_CHANNEL_ID} from './src/services'
+import {
+ WalletTask,
+ NotificationService,
+ SWAP_ALL_TASK,
+ TEST_TASK,
+ SYNC_STATE_WITH_ALL_MINTS_TASK,
+ HANDLE_NWC_REQUEST_TASK
+} from './src/services'
import { log } from './src/services/logService'
import App from './src/App'
import {name as appName} from './app.json'
@@ -17,38 +24,44 @@ function BootstrapApp() {
return
}
-// Process nwc commands as long running headless tasks
+// long running tasks
notifee.registerForegroundService(async (notification) => {
return new Promise(async (resolve) => {
log.trace('[registerForegroundService] Foreground service running for task:', notification)
try {
- if(notification.data.task === 'handleNwcRequestFromNotification') {
+ if(notification.data.task === HANDLE_NWC_REQUEST_TASK) {
+ log.debug(`[registerForegroundService] Submitting task ${HANDLE_NWC_REQUEST_TASK} to the queue.`)
+
const {nwcStore} = rootStoreInstance
-
+ // if an app is in killed state, state is not loaded
if(nwcStore.all.length === 0) {
await setupRootStore(rootStoreInstance)
- }
+ }
- nwcStore.handleNwcRequestFromNotification(notification.data.data).catch((e) => {
- log.error('[registerForegroundService]', e.message)
- })
+ WalletTask.handleNwcRequestQueue({requestEvent: notification.data.data})
}
- if(notification.data.task === 'syncSpendableStateWithMints') {
- WalletTask.syncSpendableStateWithMints()
+ if(notification.data.task === SYNC_STATE_WITH_ALL_MINTS_TASK) {
+ log.debug(`[registerForegroundService] Submitting task ${SYNC_STATE_WITH_ALL_MINTS_TASK} to the queue.`)
+
+ WalletTask.syncStateWithAllMintsQueue({isPending: false})
}
- if(notification.data.task === 'sendAll') {
- WalletTask.sendAll()
+
+ if(notification.data.task === SWAP_ALL_TASK) {
+ log.debug(`[registerForegroundService] Submitting task ${SWAP_ALL_TASK} to the queue.`)
+
+ WalletTask.swapAllQueue()
}
- /*if(notification.android.channelId === TASK_QUEUE_CHANNEL_ID) {
- const tasksArray = notification.data.tasksToRun.split('|')
-
- }*/
+ if(notification.data.task === TEST_TASK) {
+ log.debug(`[registerForegroundService] Submitting task ${TEST_TASK} to the queue.`)
+
+ WalletTask.testQueue()
+ }
} catch(e) {
log.error('[registerForegroundService]', e.message)
diff --git a/output.log b/output.log
index b21a667b..46b134b1 100644
Binary files a/output.log and b/output.log differ
diff --git a/package.json b/package.json
index 588f9d63..5fcce1a6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "minibits_wallet",
- "version": "0.1.10-beta.20",
+ "version": "0.1.10-beta.21",
"private": true,
"scripts": {
"android:clean": "cd android && ./gradlew clean",
@@ -18,7 +18,7 @@
"adb:inspect": "adb shell input keyevent 82",
"postinstall": "patch-package --exclude 'nothing'"
},
- "dependencies": {
+ "dependencies": {
"@cashu/cashu-ts": "minibits-cash/cashu-ts#2b51a7ba7a8461a0040359eecda37b98b603692c",
"@fortawesome/fontawesome-svg-core": "^6.6.0",
"@fortawesome/free-brands-svg-icons": "^6.6.0",
@@ -76,11 +76,11 @@
"react-native-svg": "^15.6.0",
"react-native-tab-view": "^3.5.2",
"react-native-url-polyfill": "^2.0.0",
- "simple-js-task-queue": "^0.2.2",
+ "taskon": "^0.3.0",
"text-encoding-polyfill": "^0.6.7",
"util": "^0.12.5"
},
- "devDependencies": {
+ "devDependencies": {
"@babel/core": "^7.20.0",
"@babel/plugin-proposal-export-namespace-from": "^7.18.9",
"@babel/preset-env": "^7.20.0",
diff --git a/src/components/Loading.tsx b/src/components/Loading.tsx
index b6eee651..c393bb1b 100644
--- a/src/components/Loading.tsx
+++ b/src/components/Loading.tsx
@@ -18,8 +18,6 @@ export function Loading(props: ViewProps & { statusMessage?: string, textStyle?:
const $loading = (shiftedUp = false) => ({
flex: 1,
- width: '100%',
- height: '100%',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: colors.dark.background,
diff --git a/src/models/NwcStore.ts b/src/models/NwcStore.ts
index 8b042e11..d7cd67a8 100644
--- a/src/models/NwcStore.ts
+++ b/src/models/NwcStore.ts
@@ -18,6 +18,7 @@ import {
NostrClient,
NostrEvent,
NostrUnsignedEvent,
+ SyncQueue,
TransactionTaskResult,
WalletTask
} from '../services'
@@ -35,6 +36,8 @@ import { PaymentRequest } from './PaymentRequest'
import { PaymentRequests } from './PaymentRequestsStore'
import { MINIBITS_MINT_URL } from '@env'
import { MintBalance } from './Mint'
+import { transferTask } from '../services/wallet/transferTask'
+import { topupTask } from '../services/wallet/topupTask'
type NwcError = {
result_type: string,
@@ -169,6 +172,7 @@ export const NwcConnectionModel = types.model('NwcConnection', {
}
// notify errors
+
if((nwcResponse as NwcError).error) {
let body = ''
if(nwcResponse.result_type === 'pay_invoice') {
@@ -189,147 +193,27 @@ export const NwcConnectionModel = types.model('NwcConnection', {
if(nwcResponse.result_type === 'lookup_invoice') {
body = 'Lookup invoice error: '
- }
+ }
+
+ yield NotificationService.createLocalNotification(
+ `${self.name} - Nostr Wallet Connect`,
+ body + (nwcResponse as NwcError).error.message,
+ nwcPngUrl
+ )
+
+ }
- const enabled = yield NotificationService.areNotificationsEnabled()
- if(enabled) {
- yield NotificationService.createLocalNotification(
- `${self.name} - Nostr Wallet Connect`,
- body + (nwcResponse as NwcError).error.message,
- nwcPngUrl
- )
- }
- }
-
- NostrClient.publish(
+ yield NostrClient.publish(
responseEvent,
self.connectionRelays
)
- NotificationService.stopForegroundService()
-
+ yield NotificationService.stopForegroundService()
}),
}))
-.actions(self => ({
- handleTransferTaskResult: flow(function* handleTransferTaskResult(result: TransactionTaskResult) {
- log.debug('[handleTransferTaskResult] Got transfer task result', {
- connection: self.name,
- meltQuote: result.meltQuote?.quote,
- txId: result.transaction?.id,
- })
-
- if(result.meltQuote?.quote === self.lastMeltQuoteId) {
- log.error('Meltquote has been already handled, skipping...', {
- meltQuoteId: self.lastMeltQuoteId,
- caller: 'handleTransferTaskResult'
- })
- return
- }
-
- self.setLastMeltQuoteId(result.meltQuote?.quote)
-
- let nwcResponse: NwcResponse | NwcError
-
- if(result.transaction?.status === TransactionStatus.COMPLETED) {
- const updatedLimit = self.remainingDailyLimit -
- (result.transaction.amount + result.transaction.fee)
-
- nwcResponse = {
- result_type: 'pay_invoice',
- result: {
- preimage: result.preimage,
- }
- } as NwcResponse
-
- log.trace('[handleTransferTaskResult] Updating remainingLimit', {
- connection: self.name,
- beforeUpdate: self.remainingDailyLimit,
- afterUpdate: updatedLimit
- })
-
- self.setRemainingDailyLimit(updatedLimit)
-
- // notify completed payment
- const enabled = yield NotificationService.areNotificationsEnabled()
- if(enabled) {
- yield NotificationService.createLocalNotification(
- `${self.name} - Nostr Wallet Connect`,
- `Paid ${result.transaction.amount} SAT${result.transaction.fee > 0 ? ', fee ' + result.transaction.fee + ' SAT' : ''}. Remaining today's limit is ${self.remainingDailyLimit} SAT`,
- nwcPngUrl
- )
- }
- } else {
- nwcResponse = {
- result_type: 'pay_invoice',
- error: { code: 'INTERNAL', message: result.message}
- } as NwcError
- }
-
- if(!result.nwcEvent) {
- log.error('Missing nwcEvent.', {caller: 'handleTransferTaskResult'})
- NotificationService.stopForegroundService()
- return
- }
-
- yield self.sendResponse(nwcResponse, result.nwcEvent)
- }),
- handleTopupTaskResult: flow(function* handleTopupTaskResult(result: TransactionTaskResult) {
- log.debug('Got topup task result', {
- connection: self.name,
- encodedInvoice: result.encodedInvoice,
- caller: 'handleTopupTaskResult'
- })
-
- if(!result.nwcEvent) {
- log.error('Missing nwcEvent.', {caller: 'handleTopupTaskResult'})
- return
- }
-
- let nwcResponse: NwcResponse | NwcError
-
- if(result.paymentRequest) {
- const pr: PaymentRequest = result.paymentRequest
-
- nwcResponse = {
- result_type: 'make_invoice',
- result: {
- type: 'incoming',
- invoice: pr.encodedInvoice,
- description: pr.description,
- payment_hash: pr.paymentHash,
- amount: pr.invoicedAmount * 1000,
- fees_paid: 0,
- created_at: Math.floor(pr.createdAt!.getTime() / 1000),
- expires_at: Math.floor(pr.expiresAt!.getTime() / 1000),
- preimage: null,
- settled_at: null
- } as NwcTransaction
- } as NwcResponse
-
- // notify created topup
- const enabled = yield NotificationService.areNotificationsEnabled()
- if(enabled) {
- yield NotificationService.stopForegroundService()
-
- yield NotificationService.createLocalNotification(
- `${self.name} - Nostr Wallet Connect`,
- `Invoice for ${result.paymentRequest.invoicedAmount} SATS has been created.`,
- nwcPngUrl
- )
- }
- } else {
- nwcResponse = {
- result_type: 'make_invoice',
- error: { code: 'INTERNAL', message: result.message}
- } as NwcError
- }
-
- yield self.sendResponse(nwcResponse, result.nwcEvent)
- })
-}))
.actions(self => ({
handleGetInfo (nwcRequest: NwcRequest): NwcResponse {
- const nwcResponse = {
+ const nwcResponse: NwcResponse = {
result_type: nwcRequest.method,
result: {
alias: 'Minibits',
@@ -369,7 +253,7 @@ export const NwcConnectionModel = types.model('NwcConnection', {
} as NwcTransaction
})
- const nwcResponse = {
+ const nwcResponse: NwcResponse = {
result_type: nwcRequest.method,
result: {
transactions
@@ -389,7 +273,7 @@ export const NwcConnectionModel = types.model('NwcConnection', {
resultBalanceMsat = 0
}
- const nwcResponse = {
+ const nwcResponse: NwcResponse = {
result_type: nwcRequest.method,
result: {
balance: resultBalanceMsat
@@ -417,14 +301,56 @@ export const NwcConnectionModel = types.model('NwcConnection', {
} as NwcError
}
- yield WalletTask.topup(
+ const result = yield topupTask(
mintBalance,
roundUp(amontMsat / 1000, 0),
'sat',
description,
undefined,
requestEvent
- )
+ )
+
+ log.debug('Got topup task result', {
+ connection: self.name,
+ encodedInvoice: result.encodedInvoice,
+ caller: 'handleTopupTaskResult'
+ })
+
+ let nwcResponse: NwcResponse | NwcError
+
+ if(result.paymentRequest) {
+ const pr: PaymentRequest = result.paymentRequest
+
+ nwcResponse = {
+ result_type: 'make_invoice',
+ result: {
+ type: 'incoming',
+ invoice: pr.encodedInvoice,
+ description: pr.description,
+ payment_hash: pr.paymentHash,
+ amount: pr.invoicedAmount * 1000,
+ fees_paid: 0,
+ created_at: Math.floor(pr.createdAt!.getTime() / 1000),
+ expires_at: Math.floor(pr.expiresAt!.getTime() / 1000),
+ preimage: null,
+ settled_at: null
+ } as NwcTransaction
+ } as NwcResponse
+
+ yield NotificationService.createLocalNotification(
+ `${self.name} - Nostr Wallet Connect`,
+ `Invoice for ${result.paymentRequest.invoicedAmount} SATS has been created.`,
+ nwcPngUrl
+ )
+
+ } else {
+ nwcResponse = {
+ result_type: 'make_invoice',
+ error: { code: 'INTERNAL', message: result.message}
+ } as NwcError
+ }
+
+ return nwcResponse
}),
handleLookupInvoice(nwcRequest: NwcRequest) {
const paymentRequestsStore = self.getPaymentRequestsStore()
@@ -490,13 +416,11 @@ export const NwcConnectionModel = types.model('NwcConnection', {
const feeReserve = Math.max(MIN_LIGHTNING_FEE, amountToPay * LIGHTNING_FEE_PERCENT / 100)
const totalAmountToPay = amountToPay + feeReserve
- let mintBalance: MintBalance | undefined = undefined
- let isMinibitsMintSelected: boolean = false
+ let mintBalance: MintBalance | undefined = undefined
const minibitsBalance = proofsStore.getMintBalance(MINIBITS_MINT_URL)
if(minibitsBalance && minibitsBalance.balances.sat! >= totalAmountToPay) {
- mintBalance = minibitsBalance
- isMinibitsMintSelected = true
+ mintBalance = minibitsBalance
} else {
mintBalance = proofsStore.getMintBalanceWithMaxBalance('sat')
}
@@ -525,7 +449,7 @@ export const NwcConnectionModel = types.model('NwcConnection', {
encoded,
)
- WalletTask.transfer(
+ const result = yield transferTask(
mintBalance,
amountToPay,
'sat',
@@ -536,11 +460,51 @@ export const NwcConnectionModel = types.model('NwcConnection', {
requestEvent
)
+ if(result.meltQuote?.quote === self.lastMeltQuoteId) {
+ throw new AppError(Err.ALREADY_EXISTS_ERROR, 'Already processed', {quote: result.meltQuote?.quote})
+ }
+
+ self.setLastMeltQuoteId(result.meltQuote?.quote)
+
+ let nwcResponse: NwcResponse | NwcError
+
+ if(result.transaction?.status === TransactionStatus.COMPLETED) {
+ const updatedLimit = self.remainingDailyLimit -
+ (result.transaction.amount + result.transaction.fee)
+
+ nwcResponse = {
+ result_type: 'pay_invoice',
+ result: {
+ preimage: result.preimage,
+ }
+ } as NwcResponse
+
+ log.trace('[handleTransferTaskResult] Updating remainingLimit', {
+ connection: self.name,
+ beforeUpdate: self.remainingDailyLimit,
+ afterUpdate: updatedLimit
+ })
+
+ self.setRemainingDailyLimit(updatedLimit)
+
+ yield NotificationService.createLocalNotification(
+ `${self.name} - Nostr Wallet Connect`,
+ `Paid ${result.transaction.amount} SAT${result.transaction.fee > 0 ? ', fee ' + result.transaction.fee + ' SAT' : ''}. Remaining today's limit is ${self.remainingDailyLimit} SAT`,
+ nwcPngUrl
+ )
+
+ } else {
+ nwcResponse = {
+ result_type: 'pay_invoice',
+ error: { code: 'INTERNAL', message: result.message}
+ } as NwcError
+ }
+
+ return nwcResponse
+
} catch (e: any) {
log.error(`[NwcConnection.handlePayInvoice] ${e.message}`)
-
- yield NotificationService.stopForegroundService()
-
+
return {
result_type: nwcRequest.method,
error: { code: 'INTERNAL', message: e.message}
@@ -549,7 +513,7 @@ export const NwcConnectionModel = types.model('NwcConnection', {
})
}))
.actions(self => ({
- handleRequest: flow(function* handleRequest(requestEvent: NostrEvent, decryptedNwcRequest?: NwcRequest) {
+ handleNwcRequestTask: flow(function* handleNwcRequestTask(requestEvent: NostrEvent, decryptedNwcRequest?: NwcRequest) {
let nwcRequest: NwcRequest
if(!decryptedNwcRequest) {
const decryptedContent = yield NostrClient.decryptNip04(requestEvent.pubkey, requestEvent.content)
@@ -575,25 +539,12 @@ export const NwcConnectionModel = types.model('NwcConnection', {
break
case 'make_invoice':
nwcResponse = yield self.handleMakeInvoice(nwcRequest, requestEvent) as Promise
-
- // no early error, topup initiated, register topup result event handler and exit
- if(!nwcResponse) {
- EventEmitter.on('ev_topupTask_result', self.handleTopupTaskResult)
- return
- }
break
case 'lookup_invoice':
nwcResponse = self.handleLookupInvoice(nwcRequest)
break
- case 'pay_invoice':
- // only early errors are immediately returned
+ case 'pay_invoice':
nwcResponse = yield self.handlePayInvoice(nwcRequest, requestEvent) as Promise
-
- // no early error, transfer initiated, register transfer result event handler and exit
- if(!nwcResponse) {
- EventEmitter.on('ev_transferTask_result', self.handleTransferTaskResult)
- return
- }
break
default:
const message = `NWC method ${nwcRequest.method} is unknown or not yet supported.`
@@ -711,7 +662,7 @@ export const NwcStoreModel = types
log.debug('[remove]', 'Connection removed from NwcStore')
}
},
- receiveNwcEvents () {
+ receiveNwcEventsQueue () {
log.trace('[receiveNwcEvents] start listening for NWC events', {
walletPubkey: self.walletPubkey
})
@@ -754,8 +705,13 @@ export const NwcStoreModel = types
if(!targetConnection) {
throw new AppError(Err.VALIDATION_ERROR, 'Missing connection matching event pubkey', {pubkey: event.pubkey})
}
- // dispatch to correct connection
- targetConnection.handleRequest(event)
+
+ // dispatch to correct connection, process over sync queue
+ const now = new Date().getTime()
+ SyncQueue.addTask(
+ `handleNwcRequestTask-${now}`,
+ async () => await targetConnection.handleNwcRequestTask(event)
+ )
},
oneose() {
log.trace('[receiveNwcEvents]', `Eose: Got ${eventsBatch.length} NWC events`)
@@ -768,7 +724,7 @@ export const NwcStoreModel = types
return
}
},
- handleNwcRequestFromNotification: flow(function* handleNwcRequestFromNotification(event: NostrEvent, decryptedNwcRequest: NwcRequest) {
+ handleNwcRequestTask: flow(function* handleNwcRequestTask(event: NostrEvent, decryptedNwcRequest?: NwcRequest) {
// find connection the nwc request is sent to
const targetConnection = self.nwcConnections.find(c =>
c.connectionPubkey === event.pubkey
@@ -776,21 +732,31 @@ export const NwcStoreModel = types
if(!targetConnection) {
log.error('[handleNwcRequestFromNotification] Missing connection matching event pubkey.', {pubkey: event.pubkey})
- // notify created topup
- const enabled = yield NotificationService.areNotificationsEnabled()
- if(enabled) {
- yield NotificationService.stopForegroundService()
-
- yield NotificationService.createLocalNotification(
- `Nostr Wallet Connect error`,
- `Your wallet has received a NWC command, but could not find related NWC connection to handle it.`,
- nwcPngUrl
- )
- }
+
+ yield NotificationService.stopForegroundService()
+ yield NotificationService.createLocalNotification(
+ `Nostr Wallet Connect error`,
+ `Your wallet has received a NWC command, but could not find related NWC connection to handle it.`,
+ nwcPngUrl
+ )
+
return
}
- yield targetConnection.handleRequest(event, decryptedNwcRequest)
+ if(!event) {
+ log.error('[handleNwcRequestFromNotification] Missing connection matching event pubkey.')
+
+ yield NotificationService.stopForegroundService()
+ yield NotificationService.createLocalNotification(
+ `Nostr Wallet Connect error`,
+ `Your wallet has received a NWC command, but could not retrieve the required data.`,
+ nwcPngUrl
+ )
+
+ return
+ }
+
+ yield targetConnection.handleNwcRequestTask(event, decryptedNwcRequest)
})
}))
.views(self => ({
diff --git a/src/screens/ExportBackupScreen.tsx b/src/screens/ExportBackupScreen.tsx
index 48b5ef25..82ee747b 100644
--- a/src/screens/ExportBackupScreen.tsx
+++ b/src/screens/ExportBackupScreen.tsx
@@ -1,4 +1,3 @@
-import {observer} from 'mobx-react-lite'
import React, {FC, useState, useEffect} from 'react'
import {
TextStyle,
@@ -36,13 +35,11 @@ import { ProofsStoreSnapshot } from '../models/ProofsStore'
import { getSnapshot } from 'mobx-state-tree'
import { ContactsStoreSnapshot } from '../models/ContactsStore'
import { MintsStoreSnapshot } from '../models/MintsStore'
-import { Database, NotificationService, TASK_QUEUE_CHANNEL_ID, TASK_QUEUE_CHANNEL_NAME, TransactionTaskResult, WalletTask, WalletTaskResult } from '../services'
-import { Transaction, TransactionStatus } from '../models/Transaction'
+import { SWAP_ALL_TASK, TASK_QUEUE_CHANNEL_ID, TASK_QUEUE_CHANNEL_NAME, WalletTask, WalletTaskResult } from '../services'
+import { TransactionStatus } from '../models/Transaction'
import { ResultModalInfo } from './Wallet/ResultModalInfo'
import { verticalScale } from '@gocodingnow/rn-size-matters'
import { Token, getDecodedToken, getEncodedToken } from '@cashu/cashu-ts'
-import { encodeCBOR } from '@cashu/cashu-ts/src/cbor'
-import { encodeUint8toBase64Url } from '@cashu/cashu-ts/src/base64'
import { minibitsPngIcon } from '../components/MinibitsIcon'
interface ExportBackupScreenProps extends SettingsStackScreenProps<'ExportBackup'> {}
@@ -50,581 +47,528 @@ interface ExportBackupScreenProps extends SettingsStackScreenProps<'ExportBackup
const OPTIMIZE_FROM_PROOFS_COUNT = 5
export const ExportBackupScreen: FC =
- function ExportBackup(_props) {
+ function ExportBackup(_props) {
- const { navigation } = _props
- const {
- mintsStore,
- contactsStore,
- proofsStore
- } = useStores()
+ const { navigation } = _props
+ const {
+ mintsStore,
+ contactsStore,
+ proofsStore
+ } = useStores()
- useHeader({
- leftIcon: 'faArrowLeft',
- onLeftPress: () => navigation.goBack(),
- })
+ useHeader({
+ leftIcon: 'faArrowLeft',
+ onLeftPress: () => navigation.goBack(),
+ })
- const [info, setInfo] = useState('')
- const [error, setError] = useState()
- const [orphanedProofs, setOrphanedProofs] = useState([])
- const [isLoading, setIsLoading] = useState(false)
- const [isSendAllSentToQueue, setIsSendAllSentToQueue] = useState(false)
- const [isReceiveBatchSentToQueue, setIsReceiveBatchSentToQueue] = useState(false)
- const [totalProofsCount, setTotalProofsCount] = useState(0)
- const [totalSentProofsCount, setTotalSentProofsCount] = useState(0)
- const [totalReceiveErrorCount, setTotalReceiveErrorCount] = useState(0)
- const [totalReceiveCompleteCount, setTotalReceiveCompleteCount] = useState(0)
- const [isEcashInBackup, setIsEcashInBackup] = useState(true)
- const [isMintsInBackup, setIsMintsInBackup] = useState(true)
- const [isContactsInBackup, setIsContactsInBackup] = useState(true)
- const [isResultModalVisible, setIsResultModalVisible] = useState(false)
- const [resultModalInfo, setResultModalInfo] = useState<
- {status: TransactionStatus; title?: string, message: string} | undefined
- >()
-
-
- useEffect(() => {
- const loadProofs = async () => {
- setIsLoading(true)
- try {
- // full refresh of proofs from DB in case the state is broken
- await proofsStore.loadProofsFromDatabase()
-
- const orphaned = proofsStore.allProofs.filter(proof => !proof.mintUrl)
+ const [info, setInfo] = useState('')
+ const [error, setError] = useState()
+ const [orphanedProofs, setOrphanedProofs] = useState([])
+ const [isLoading, setIsLoading] = useState(false)
+ const [isSwapAllSentToQueue, setIsSwapAllSentToQueue] = useState(false)
+ const [totalProofsCount, setTotalProofsCount] = useState(0)
+ const [isEcashInBackup, setIsEcashInBackup] = useState(true)
+ const [isMintsInBackup, setIsMintsInBackup] = useState(true)
+ const [isContactsInBackup, setIsContactsInBackup] = useState(true)
+ const [isResultModalVisible, setIsResultModalVisible] = useState(false)
+ const [resultModalInfo, setResultModalInfo] = useState<
+ {status: TransactionStatus; title?: string, message: string} | undefined
+ >()
+
+
+ useEffect(() => {
+ const loadProofs = async () => {
+ setIsLoading(true)
+ try {
+ // full refresh of proofs from DB in case the state is broken
+ await proofsStore.loadProofsFromDatabase()
+
+ const orphaned = proofsStore.allProofs.filter(proof => !proof.mintUrl)
- if (orphaned.length > 0) {
- setOrphanedProofs(orphaned)
- setInfo(`Found ${orphaned.length} orphaned proofs not belonging to any active mint. Those won't be included to the backup, but you can copy them separately.`)
- }
+ if (orphaned.length > 0) {
+ setOrphanedProofs(orphaned)
+ setInfo(`Found ${orphaned.length} orphaned proofs not belonging to any active mint. Those won't be included to the backup, but you can copy them separately.`)
+ }
- // log.trace('[loadProofs]', {refreshedProofs: proofsStore.proofs})
- setTotalProofsCount(proofsStore.proofsCount)
- setIsLoading(false)
- } catch (e: any) {
- handleError(e)
- }
- }
-
- loadProofs()
- return () => {}
- }, [])
-
-
- useEffect(() => {
- const handleSendAllResult = async (result: TransactionTaskResult) => {
- log.trace('[handleSendAllResults] event handler triggered')
-
- if (!isSendAllSentToQueue) { return false }
-
- // runs per each mint and unit
- if (result.transaction && result.transaction.status === TransactionStatus.PENDING) {
-
- // now we batch receive the pending encoded token
- // this forces the proofs swap with the mint for standard denomination amounts
- const encodedTokenToReceive: string = result.encodedTokenToSend
- const tokenToReceive = getDecodedToken(encodedTokenToReceive)
- const tokenAmount = CashuUtils.getProofsAmount(tokenToReceive.proofs)
- const proofsCount = tokenToReceive.proofs.length
-
- setTotalSentProofsCount(prev => prev + proofsCount)
- setIsReceiveBatchSentToQueue(true)
-
- WalletTask.receiveBatch(
- tokenToReceive,
- tokenAmount,
- tokenToReceive.memo as string,
- encodedTokenToReceive
- )
- }
+ // log.trace('[loadProofs]', {refreshedProofs: proofsStore.proofs})
+ setTotalProofsCount(proofsStore.proofsCount)
+ setIsLoading(false)
+ } catch (e: any) {
+ handleError(e)
+ }
}
-
- if(isSendAllSentToQueue) {
- EventEmitter.on('ev_sendTask_result', handleSendAllResult)
- }
-
- return () => {
- EventEmitter.off('ev_sendTask_result', handleSendAllResult)
- }
- }, [isSendAllSentToQueue])
+
+ loadProofs()
+ return () => {}
+ }, [])
useEffect(() => {
// runs for every receive in a batch
- const handleReceiveTaskResult = async (result: TransactionTaskResult) => {
- log.trace('handleReceiveTaskResult event handler triggered')
-
- const {error} = result
-
- if (error) {
- setTotalReceiveErrorCount(prev => prev + 1)
- } else {
- setTotalReceiveCompleteCount(prev => prev + 1)
+ const handleSwapAllTaskResult = async (result: WalletTaskResult) => {
+ if(!isSwapAllSentToQueue) {
+ return
}
+
+ setIsLoading(false)
+ setResultModalInfo({
+ status: result.errors.length > 0 ? TransactionStatus.ERROR : TransactionStatus.COMPLETED,
+ message: result.message
+ })
+
+ if(result.finalProofsCount && result.finalProofsCount > 0) {
+ setTotalProofsCount(result.finalProofsCount)
+ }
+
+ setIsResultModalVisible(true)
}
- if(isReceiveBatchSentToQueue) {
- EventEmitter.on('ev_receiveTask_result', handleReceiveTaskResult)
+ if(isSwapAllSentToQueue) {
+ EventEmitter.on(`ev_${SWAP_ALL_TASK}_result`, handleSwapAllTaskResult)
}
return () => {
- EventEmitter.off('ev_receiveTask_result', handleReceiveTaskResult)
- NotificationService.stopForegroundService()
+ EventEmitter.off(`ev_${SWAP_ALL_TASK}_result`, handleSwapAllTaskResult)
}
- }, [isReceiveBatchSentToQueue])
+ }, [isSwapAllSentToQueue])
- useEffect(() => {
- // runs for every receive in a batch
- const showProofOptimizationResult = async () => {
- log.trace('handleReceiveTaskResult event handler triggered')
-
- setIsLoading(false)
-
- const currentProofsCount = proofsStore.proofsCount
-
- let message = `Original proofs count: ${totalSentProofsCount}, Optimized proofs count: ${currentProofsCount}`
-
- if (totalReceiveErrorCount > 0) {
- message += `, Errors: ${totalReceiveErrorCount}`
- }
-
- setResultModalInfo({
- status: totalReceiveErrorCount > 0 ? TransactionStatus.ERROR : TransactionStatus.COMPLETED,
- message,
- })
-
- setIsResultModalVisible(true)
- }
-
- if(totalReceiveCompleteCount > 0 || totalReceiveErrorCount > 0) {
- showProofOptimizationResult()
- }
-
- }, [totalReceiveErrorCount, totalReceiveCompleteCount])
-
-
- const toggleResultModal = () => {
- setIsResultModalVisible(previousState => !previousState)
- WalletTask.syncPendingStateWithMints()
- }
-
-
-
- const toggleBackupEcashSwitch = () =>
- setIsEcashInBackup(previousState => !previousState)
-
-
- const toggleBackupMintsSwitch = () =>
- setIsMintsInBackup(previousState => !previousState)
-
-
- const toggleBackupContanctsSwitch = () =>
- setIsContactsInBackup(previousState => !previousState)
-
-
- const optimizeProofAmountsStart = function () {
- Alert.alert(
- 'Optimize ecash proofs',
- 'Do you want to swap your wallet ecash for proofs with optimal denominations? The size of your backup will decrease.',
- [
- {
- text: translate('common.cancel'),
- style: 'cancel',
- onPress: () => { /* Action canceled */ },
- },
- {
- text: translate('common.confirm'),
- onPress: async () => {
- // Moves all wallet proofs to pending in transactions
- // split by mints and by units and in offline mode.
- // Supports batching in case proofs count is above limit.
- setIsLoading(true)
- setIsSendAllSentToQueue(true)
-
- // long task background processing
- await notifee.displayNotification({
- title: TASK_QUEUE_CHANNEL_NAME,
- body: 'Processing pending transactions...',
- android: {
- channelId: TASK_QUEUE_CHANNEL_ID,
- asForegroundService: true,
- largeIcon: minibitsPngIcon,
- importance: AndroidImportance.HIGH,
- progress: {
- indeterminate: true,
- }
- },
- data: {task: 'sendAll'}
- })
- // WalletTask.sendAll()
- }
- }
- ]
- )
- }
-
- const copyBackup = function () {
- try {
- setIsLoading(true)
-
- let exportedProofsStore: ProofsStoreSnapshot = {
- proofs: [],
- pendingProofs: [],
- pendingByMintSecrets: []
- }
-
- let exportedMintsStore: MintsStoreSnapshot = {
- mints: [],
- blockedMintUrls: [],
- counterBackups: []
- }
-
- let exportedContactsStore: ContactsStoreSnapshot = {
- contacts: [],
- publicPubkey: undefined,
- selectedContact: undefined,
- receivedEventIds: [],
- lastPendingReceivedCheck: undefined
- }
-
- if(isEcashInBackup) {
- // proofsStore is emptied in snapshot postprocess!
- const proofsSnapshot = getSnapshot(proofsStore.proofs)
-
- // Do not include orphaned proofs as they can not be imported without mintUrl
- const cleaned = proofsSnapshot.filter(p => p.mintUrl && p.mintUrl.length > 0)
-
- exportedProofsStore = {
- proofs: cleaned,
- pendingProofs: getSnapshot(proofsStore.pendingProofs) || [],
- pendingByMintSecrets: getSnapshot(proofsStore.pendingByMintSecrets)
- }
- }
-
- if(isMintsInBackup) {
- exportedMintsStore = JSON.parse(JSON.stringify(getSnapshot(mintsStore)))
-
- exportedMintsStore.mints.forEach((mint: any) => {
- mint.keys = [];
- })
-
- //log.trace({exportedMintsStore})
- }
-
- if(isContactsInBackup) {
- exportedContactsStore = getSnapshot(contactsStore)
- }
-
- const exportedSnapshot = {
- proofsStore: exportedProofsStore,
- mintsStore: exportedMintsStore,
- contactsStore: exportedContactsStore,
- }
-
- log.trace({exportedSnapshot})
-
- const prefix = 'minibits'
- const version = 'A'
-
- // CBOR - WIP, not working
- // const encodedData = encodeCBOR(exportedSnapshot)
- // const base64Data = encodeUint8toBase64Url(encodedData)
-
- // Simple BASE64
- const base64Data = btoa(JSON.stringify(exportedSnapshot))
-
- const base64Encoded = prefix + version + base64Data
-
- Clipboard.setString(base64Encoded)
- setIsLoading(false)
-
- } catch (e: any) {
- setInfo(`Could not encode and copy wallet backup: ${e.message}`)
- setIsLoading(false)
- }
- }
-
-
- const groupProofsByMint = function (proofs: Proof[]) {
- return proofs.reduce((acc: Record, proof) => {
-
- const proofMint = CashuUtils.getMintFromProof(proof, mintsStore.allMints)
- // Check if there's already an array for this keyset, if not, create one
- if(!proofMint) {
- return acc
- }
- if (!acc[proofMint.mintUrl]) {
- acc[proofMint.mintUrl] = []
- }
-
- // Push the object into the array corresponding to its keyset
- acc[proofMint.mintUrl].push(proof)
- return acc
- }, {})
- }
+ const toggleResultModal = () => {
+ setIsResultModalVisible(previousState => !previousState)
+ WalletTask.syncStateWithAllMintsQueue({isPending: true})
+ }
- const groupProofsByKeysets = function (proofsByMint: Proof[]) {
- return proofsByMint.reduce((acc: Record, proof) => {
- // Check if there's already an array for this keyset, if not, create one
- if (!acc[proof.id as string]) {
- acc[proof.id] = []
- }
-
- // Push the object into the array corresponding to its keyset
- acc[proof.id].push(proof)
- return acc;
- }, {})
- }
+
+ const toggleBackupEcashSwitch = () =>
+ setIsEcashInBackup(previousState => !previousState)
+
+
+ const toggleBackupMintsSwitch = () =>
+ setIsMintsInBackup(previousState => !previousState)
+
+
+ const toggleBackupContanctsSwitch = () =>
+ setIsContactsInBackup(previousState => !previousState)
- const copyEncodedTokens = function () {
- try {
+ const optimizeProofAmountsStart = function () {
+ Alert.alert(
+ 'Optimize ecash proofs',
+ 'Do you want to swap your wallet ecash for proofs with optimal denominations? The size of your backup will decrease.',
+ [
+ {
+ text: translate('common.cancel'),
+ style: 'cancel',
+ onPress: () => { /* Action canceled */ },
+ },
+ {
+ text: translate('common.confirm'),
+ onPress: async () => {
+ // Moves all wallet proofs to pending in transactions
+ // split by mints and by units and in offline mode.
+ // Supports batching in case proofs count is above limit.
+
setIsLoading(true)
- const encodedTokens: string[] = []
+ setIsSwapAllSentToQueue(true)
+
+ await notifee.displayNotification({
+ title: TASK_QUEUE_CHANNEL_NAME,
+ body: 'Optimizing ecash proofs denominations...',
+ android: {
+ channelId: TASK_QUEUE_CHANNEL_ID,
+ asForegroundService: true,
+ largeIcon: minibitsPngIcon,
+ importance: AndroidImportance.HIGH,
+ progress: {
+ indeterminate: true,
+ }
+ },
+ data: {task: SWAP_ALL_TASK}
+ })
+
+ /* await notifee.displayNotification({
+ title: TEST_CHANNEL_NAME,
+ body: 'Test task is running...',
+ android: {
+ channelId: TEST_CHANNEL_ID,
+ asForegroundService: true,
+ largeIcon: minibitsPngIcon,
+ importance: AndroidImportance.HIGH,
+ progress: {
+ indeterminate: true,
+ }
+ },
+ data: {task: TEST_TASK_FN}
+ }) */
+ }
+ }
+ ]
+ )
+ }
+
+ const copyBackup = function () {
+ try {
+ setIsLoading(true)
- if (mintsStore.allMints.length === 0) {
- setInfo(translate("missingMintsForProofsUserMessage"))
+ let exportedProofsStore: ProofsStoreSnapshot = {
+ proofs: [],
+ pendingProofs: [],
+ pendingByMintSecrets: []
+ }
+
+ let exportedMintsStore: MintsStoreSnapshot = {
+ mints: [],
+ blockedMintUrls: [],
+ counterBackups: []
+ }
+
+ let exportedContactsStore: ContactsStoreSnapshot = {
+ contacts: [],
+ publicPubkey: undefined,
+ selectedContact: undefined,
+ receivedEventIds: [],
+ lastPendingReceivedCheck: undefined
+ }
+
+ if(isEcashInBackup) {
+ // proofsStore is emptied in snapshot postprocess!
+ const proofsSnapshot = getSnapshot(proofsStore.proofs)
+
+ // Do not include orphaned proofs as they can not be imported without mintUrl
+ const cleaned = proofsSnapshot.filter(p => p.mintUrl && p.mintUrl.length > 0)
+
+ exportedProofsStore = {
+ proofs: cleaned,
+ pendingProofs: getSnapshot(proofsStore.pendingProofs) || [],
+ pendingByMintSecrets: getSnapshot(proofsStore.pendingByMintSecrets)
+ }
+ }
+
+ if(isMintsInBackup) {
+ exportedMintsStore = JSON.parse(JSON.stringify(getSnapshot(mintsStore)))
+
+ exportedMintsStore.mints.forEach((mint: any) => {
+ mint.keys = [];
+ })
+
+ //log.trace({exportedMintsStore})
+ }
+
+ if(isContactsInBackup) {
+ exportedContactsStore = getSnapshot(contactsStore)
+ }
+
+ const exportedSnapshot = {
+ proofsStore: exportedProofsStore,
+ mintsStore: exportedMintsStore,
+ contactsStore: exportedContactsStore,
+ }
+
+ log.trace({exportedSnapshot})
+
+ const prefix = 'minibits'
+ const version = 'A'
+
+ // CBOR - WIP, not working
+ // const encodedData = encodeCBOR(exportedSnapshot)
+ // const base64Data = encodeUint8toBase64Url(encodedData)
+
+ // Simple BASE64
+ const base64Data = btoa(JSON.stringify(exportedSnapshot))
+
+ const base64Encoded = prefix + version + base64Data
+
+ Clipboard.setString(base64Encoded)
+ setIsLoading(false)
+
+ } catch (e: any) {
+ setInfo(`Could not encode and copy wallet backup: ${e.message}`)
+ setIsLoading(false)
+ }
+ }
+
+
+ const groupProofsByMint = function (proofs: Proof[]) {
+ return proofs.reduce((acc: Record, proof) => {
+
+ const proofMint = CashuUtils.getMintFromProof(proof, mintsStore.allMints)
+ // Check if there's already an array for this keyset, if not, create one
+ if(!proofMint) {
+ return acc
+ }
+
+ if (!acc[proofMint.mintUrl]) {
+ acc[proofMint.mintUrl] = []
+ }
+
+ // Push the object into the array corresponding to its keyset
+ acc[proofMint.mintUrl].push(proof)
+ return acc
+ }, {})
+ }
+
+ const groupProofsByKeysets = function (proofsByMint: Proof[]) {
+ return proofsByMint.reduce((acc: Record, proof) => {
+ // Check if there's already an array for this keyset, if not, create one
+ if (!acc[proof.id as string]) {
+ acc[proof.id] = []
+ }
+
+ // Push the object into the array corresponding to its keyset
+ acc[proof.id].push(proof)
+ return acc;
+ }, {})
+ }
+
+
+ const copyEncodedTokens = function () {
+ try {
+ setIsLoading(true)
+ const encodedTokens: string[] = []
+
+ if (mintsStore.allMints.length === 0) {
+ setInfo(translate("missingMintsForProofsUserMessage"))
+ }
+
+ const groupedByMint = groupProofsByMint(proofsStore.proofs)
+
+ for (const mint in groupedByMint) {
+
+ const proofsByMint = groupedByMint[mint]
+
+ if(proofsByMint.length === 0) {
+ continue
}
- const groupedByMint = groupProofsByMint(proofsStore.proofs)
+ const groupedByKeyset = groupProofsByKeysets(proofsByMint)
- for (const mint in groupedByMint) {
+ for (const keysetId in groupedByKeyset) {
+ const proofsByKeysetId = groupedByKeyset[keysetId]
+ const proofsToExport: CashuProof[] = []
+
+ for (const p of proofsByKeysetId) {
+ // clean private params
+ const proofToExport: CashuProof = {
+ id: p.id,
+ amount: p.amount,
+ secret: p.secret,
+ C: p.C
+ }
+
+ proofsToExport.push(proofToExport)
+ }
+
+ const tokenByKeysetId: Token = {
+ mint,
+ proofs: proofsToExport,
+ unit: proofsByKeysetId[0].unit
+ }
- const proofsByMint = groupedByMint[mint]
+ log.trace('[copyEncodedTokens]', {tokenByKeysetId})
- if(proofsByMint.length === 0) {
- continue
- }
+ const encodedByMint = getEncodedToken(tokenByKeysetId)
+ encodedTokens.push(encodedByMint)
+ }
+ }
+
+ Clipboard.setString(JSON.stringify(encodedTokens))
+ setIsLoading(false)
- const groupedByKeyset = groupProofsByKeysets(proofsByMint)
-
- for (const keysetId in groupedByKeyset) {
- const proofsByKeysetId = groupedByKeyset[keysetId]
- const proofsToExport: CashuProof[] = []
-
- for (const p of proofsByKeysetId) {
- // clean private params
- const proofToExport: CashuProof = {
- id: p.id,
- amount: p.amount,
- secret: p.secret,
- C: p.C
- }
-
- proofsToExport.push(proofToExport)
- }
-
- const tokenByKeysetId: Token = {
- mint,
- proofs: proofsToExport,
- unit: proofsByKeysetId[0].unit
- }
-
- log.trace('[copyEncodedTokens]', {tokenByKeysetId})
-
- const encodedByMint = getEncodedToken(tokenByKeysetId)
- encodedTokens.push(encodedByMint)
- }
- }
-
- Clipboard.setString(JSON.stringify(encodedTokens))
- setIsLoading(false)
-
- } catch (e: any) {
- handleError(e)
- }
- }
+ } catch (e: any) {
+ handleError(e)
+ }
+ }
- const copyOrphanedProofs = function() {
- log.trace({orphanedProofs})
- if(orphanedProofs.length > 0) {
- Clipboard.setString(JSON.stringify(orphanedProofs))
- }
- }
+ const copyOrphanedProofs = function() {
+ log.trace({orphanedProofs})
+ if(orphanedProofs.length > 0) {
+ Clipboard.setString(JSON.stringify(orphanedProofs))
+ }
+ }
- const handleError = function (e: AppError): void {
- setIsLoading(false)
- setError(e)
- }
+ const handleError = function (e: AppError): void {
+ setIsLoading(false)
+ setError(e)
+ }
-
- const headerBg = useThemeColor('header')
- const hint = useThemeColor('textDim')
- const headerTitle = useThemeColor('headerTitle')
+
+ const headerBg = useThemeColor('header')
+ const hint = useThemeColor('textDim')
+ const headerTitle = useThemeColor('headerTitle')
- return (
-
-
-
-
-
-
- {totalProofsCount > 0 && (
-
+
+
+
+
+
+ {totalProofsCount > 0 && (
+
+ {proofsStore.proofsCount > OPTIMIZE_FROM_PROOFS_COUNT && (
+
+ )}
+
+
+ }
+ />
+ )}
+ {mintsStore.mintCount > 0 && (
+
+
+
+ }
+ topSeparator={totalProofsCount > 0 ? true : false}
+ />
+ )}
+ {contactsStore.count > 0 && (
+
- {proofsStore.proofsCount > OPTIMIZE_FROM_PROOFS_COUNT && (
-
- )}
-
+
- }
- />
- )}
- {mintsStore.mintCount > 0 && (
-
-
-
- }
- topSeparator={totalProofsCount > 0 ? true : false}
- />
- )}
- {contactsStore.count > 0 && (
-
-
-
- }
- topSeparator={mintsStore.mintCount > 0 ? true : false}
- />
- )}
- >
- }
- style={[$card]}
- />
-
-
-
-
-
-
-
-
-
- {isLoading && }
- {error && }
- {info && }
-
+ }
+ topSeparator={mintsStore.mintCount > 0 ? true : false}
+ />
+ )}
+ >
+ }
+ style={[$card]}
+ />
- {proofsStore.proofsCount > 0 && (
-
+
+
+
+
+
+
+
+
+ {isLoading && }
+ {error && }
+ {info && }
+
+
+ {proofsStore.proofsCount > 0 && (
+
+
+ {orphanedProofs.length > 0 && (
- {orphanedProofs.length > 0 && (
-
- )}
-
- )}
-
-
- {resultModalInfo?.status === TransactionStatus.COMPLETED && (
- <>
-
+ )}
+
+
+ {resultModalInfo?.status === TransactionStatus.COMPLETED && (
+ <>
+
+
+
-
-
- )
+ />
+
+ >
+ )}
+ >
+ }
+ onBackButtonPress={toggleResultModal}
+ onBackdropPress={toggleResultModal}
+ />
+
+ )
}
const $screen: ViewStyle = {
diff --git a/src/screens/ReceiveScreen.tsx b/src/screens/ReceiveScreen.tsx
index e227e480..a0289970 100644
--- a/src/screens/ReceiveScreen.tsx
+++ b/src/screens/ReceiveScreen.tsx
@@ -36,6 +36,7 @@ import numbro from 'numbro'
import { TranItem } from './TranDetailScreen'
import { translate } from '../i18n'
import { Token, getDecodedToken } from '@cashu/cashu-ts'
+import { RECEIVE_OFFLINE_PREPARE_TASK, RECEIVE_TASK } from '../services/wallet/receiveTask'
export enum ReceiveOption {
// CREATE_AND_SEND_PAYMENT_REQUEST = 'CREATE_AND_SEND_PAYMENT_REQUEST',
@@ -125,14 +126,14 @@ export const ReceiveScreen: FC> = observer(
if(isReceiveTaskSentToQueue) {
- EventEmitter.on('ev_receiveTask_result', handleReceiveTaskResult)
- EventEmitter.on('ev_receiveOfflinePrepareTask_result', handleReceiveTaskResult)
+ EventEmitter.on(`ev_${RECEIVE_TASK}_result`, handleReceiveTaskResult)
+ EventEmitter.on(`ev_${RECEIVE_OFFLINE_PREPARE_TASK}_result`, handleReceiveTaskResult)
}
// Unsubscribe from the 'sendCompleted' event on component unmount
return () => {
- EventEmitter.off('ev_receiveTask_result', handleReceiveTaskResult)
- EventEmitter.off('ev_receiveOfflinePrepareTask_result', handleReceiveTaskResult)
+ EventEmitter.off(`ev_${RECEIVE_TASK}_result`, handleReceiveTaskResult)
+ EventEmitter.off(`ev_${RECEIVE_OFFLINE_PREPARE_TASK}_result`, handleReceiveTaskResult)
}
}, [isReceiveTaskSentToQueue])
@@ -208,24 +209,12 @@ export const ReceiveScreen: FC> = observer(
const amountToReceiveInt = round(toNumber(amountToReceive) * getCurrency(unit).precision, 0)
const proofsCount = token!.proofs.length
- if(proofsCount > MAX_SWAP_INPUT_SIZE) {
-
- WalletTask.receiveBatch(
- token as Token,
- amountToReceiveInt,
- memo,
- encodedToken as string,
- )
-
- } else {
-
- WalletTask.receive(
- token as Token,
- amountToReceiveInt,
- memo,
- encodedToken as string,
- )
- }
+ WalletTask.receiveQueue(
+ token as Token,
+ amountToReceiveInt,
+ memo,
+ encodedToken as string,
+ )
}
@@ -262,7 +251,7 @@ export const ReceiveScreen: FC> = observer(
const amountToReceiveInt = round(toNumber(amountToReceive) * getCurrency(unit).precision, 0)
- WalletTask.receiveOfflinePrepare(
+ WalletTask.receiveOfflinePrepareQueue(
token as Token,
amountToReceiveInt,
memo,
diff --git a/src/screens/RecoveryOptionsScreen.tsx b/src/screens/RecoveryOptionsScreen.tsx
index a4bde530..db32ebac 100644
--- a/src/screens/RecoveryOptionsScreen.tsx
+++ b/src/screens/RecoveryOptionsScreen.tsx
@@ -19,7 +19,7 @@ import {useHeader} from '../utils/useHeader'
import {log} from '../services/logService'
import AppError from '../utils/AppError'
import { useStores } from '../models'
-import { SyncStateTaskResult, WalletTask } from '../services/walletService'
+import { SYNC_STATE_WITH_ALL_MINTS_TASK, SyncStateTaskResult } from '../services/walletService'
import EventEmitter from '../utils/eventEmitter'
import { translate } from '../i18n'
import { NotificationService, TASK_QUEUE_CHANNEL_ID, TASK_QUEUE_CHANNEL_NAME } from '../services/notificationService'
@@ -38,8 +38,6 @@ export const RecoveryOptionsScreen: FC> =
const [error, setError] = useState()
const [info, setInfo] = useState('')
const [isSyncStateSentToQueue, setIsSyncStateSentToQueue] = useState(false)
- const [totalSpentAmount, setTotalSpentAmount] = useState(0)
- const [syncErrors, setSyncErrors] = useState([])
useEffect(() => {
@@ -49,70 +47,20 @@ export const RecoveryOptionsScreen: FC> =
if (!isSyncStateSentToQueue) { return false }
setIsLoading(false)
-
- // runs per each mint
- if (result.error && result.error) {
- setSyncErrors(prev => [...prev, result.error!])
- }
-
- if (result && result.transactionStateUpdates.length > 0) {
-
- log.trace('[removeSpentByMintTaskResult]', {transactionStateUpdates: result.transactionStateUpdates})
-
- let totalSpentPerMint = 0
-
- for (const update of result.transactionStateUpdates) {
- if(update.spentByMintAmount) {
- totalSpentPerMint += update.spentByMintAmount
- }
- }
- setTotalSpentAmount(prev => prev + totalSpentPerMint)
- }
-
- if(totalSpentAmount === 0 && result.transactionStateUpdates.length === 0) {
- setInfo(`No spent ecash from ${new URL(result.mintUrl).hostname}.`)
- }
+ setInfo(result.message)
}
if(isSyncStateSentToQueue) {
- EventEmitter.on('ev__syncStateWithMintTask_result', removeSpentByMintTaskResult)
+ EventEmitter.on(`ev_${SYNC_STATE_WITH_ALL_MINTS_TASK}_result`, removeSpentByMintTaskResult)
}
return () => {
- EventEmitter.off('ev__syncStateWithMintTask_result', removeSpentByMintTaskResult)
- NotificationService.stopForegroundService()
+ EventEmitter.off(`ev_${SYNC_STATE_WITH_ALL_MINTS_TASK}_result`, removeSpentByMintTaskResult)
}
}, [isSyncStateSentToQueue])
- useEffect(() => {
- const showSpentEcashResult = () => {
- log.trace('[showSpentEcashResult] got update', {totalSpentAmount})
-
- if (totalSpentAmount === 0) { return false }
-
- setInfo(`Removed spent ecash with amount ${totalSpentAmount}.`)
- }
-
- showSpentEcashResult()
-
- }, [totalSpentAmount])
-
-
- useEffect(() => {
- const showSyncErrors = () => {
- log.trace('[showSyncErrors] got update', {syncErrors})
-
- if (syncErrors.length === 0) { return false }
-
- setError(syncErrors[syncErrors.length - 1])
- }
-
- showSyncErrors()
-
- }, [syncErrors])
-
const gotoSeedRecovery = function () {
navigation.navigate('SeedRecovery')
}
@@ -131,11 +79,10 @@ export const RecoveryOptionsScreen: FC> =
const checkSpent = async function () {
setIsLoading(true)
setIsSyncStateSentToQueue(true)
-
- // long task background processing
- notifee.displayNotification({
+
+ await notifee.displayNotification({
title: TASK_QUEUE_CHANNEL_NAME,
- body: 'Processing pending transactions...',
+ body: 'Cleaning spent ecash from spendable balance...',
android: {
channelId: TASK_QUEUE_CHANNEL_ID,
asForegroundService: true,
@@ -145,9 +92,8 @@ export const RecoveryOptionsScreen: FC> =
indeterminate: true,
},
},
- data: {task: 'syncSpendableStateWithMints'},
- })
- // WalletTask.syncSpendableStateWithMints()
+ data: {task: SYNC_STATE_WITH_ALL_MINTS_TASK},
+ })
}
diff --git a/src/screens/SendScreen.tsx b/src/screens/SendScreen.tsx
index 12e36285..15688e72 100644
--- a/src/screens/SendScreen.tsx
+++ b/src/screens/SendScreen.tsx
@@ -37,7 +37,7 @@ import {
} from '../components'
import {TransactionStatus, Transaction} from '../models/Transaction'
import {useStores} from '../models'
-import {NostrClient, SyncStateTaskResult, TransactionTaskResult, WalletTask} from '../services'
+import {NostrClient, SYNC_STATE_WITH_MINT_TASK, SyncStateTaskResult, TransactionTaskResult, WalletTask} from '../services'
import {log} from '../services/logService'
import AppError, {Err} from '../utils/AppError'
import {translate} from '../i18n'
@@ -60,6 +60,7 @@ import { MemoInputCard } from '../components/MemoInputCard'
import { PaymentRequest as CashuPaymentRequest, PaymentRequestTransport, PaymentRequestTransportType, decodePaymentRequest, getDecodedToken } from '@cashu/cashu-ts'
import { ProfilePointer } from 'nostr-tools/nip19'
import { MINIBITS_NIP05_DOMAIN } from '@env'
+import { SEND_TASK } from '../services/wallet/sendTask'
export enum SendOption {
SEND_TOKEN = 'SEND_TOKEN',
@@ -447,12 +448,12 @@ export const SendScreen: FC> = observer(
// Subscribe to the 'sendCompleted' event
if(isSendTaskSentToQueue) {
- EventEmitter.on('ev_sendTask_result', handleSendTaskResult)
+ EventEmitter.on(`ev_${SEND_TASK}_result`, handleSendTaskResult)
}
// Unsubscribe from the 'sendCompleted' event on component unmount
return () => {
- EventEmitter.off('ev_sendTask_result', handleSendTaskResult)
+ EventEmitter.off(`ev_${SEND_TASK}_result`, handleSendTaskResult)
}
}, [isSendTaskSentToQueue])
@@ -508,12 +509,12 @@ export const SendScreen: FC> = observer(
// Subscribe to the '_syncStateWithMintTask' event
if(transactionId) {
- EventEmitter.on('ev__syncStateWithMintTask_result', handleSendCompleted)
+ EventEmitter.on(`ev_${SYNC_STATE_WITH_MINT_TASK}_result`, handleSendCompleted)
}
// Unsubscribe from the '_syncStateWithMintTask' event on component unmount
return () => {
- EventEmitter.off('ev__syncStateWithMintTask_result', handleSendCompleted)
+ EventEmitter.off(`ev_${SYNC_STATE_WITH_MINT_TASK}_result`, handleSendCompleted)
}
}, [transactionId])
@@ -603,7 +604,7 @@ export const SendScreen: FC> = observer(
const amountToSendInt = round(toNumber(amountToSend) * getCurrency(unitRef.current).precision, 0)
- WalletTask.send(
+ WalletTask.sendQueue(
mintBalanceToSendFrom as MintBalance,
amountToSendInt,
unitRef.current,
diff --git a/src/screens/TopupScreen.tsx b/src/screens/TopupScreen.tsx
index 86111a94..a3bbe480 100644
--- a/src/screens/TopupScreen.tsx
+++ b/src/screens/TopupScreen.tsx
@@ -29,6 +29,7 @@ import {
import {TransactionStatus, Transaction} from '../models/Transaction'
import {useStores} from '../models'
import {
+ HANDLE_PENDING_TOPUP_TASK,
NostrClient,
NostrProfile,
TransactionTaskResult,
@@ -64,6 +65,7 @@ import numbro from 'numbro'
import {MintListItem} from './Mints/MintListItem'
import {TranItem} from './TranDetailScreen'
import {translate} from '../i18n'
+import { TOPUP_TASK } from '../services/wallet/topupTask'
if (
Platform.OS === 'android' &&
@@ -303,11 +305,11 @@ export const TopupScreen: FC> = observer(
}
// Subscribe to the 'sendCompleted' event
- EventEmitter.on('ev_topupTask_result', handleTopupTaskResult)
+ EventEmitter.on(`ev_${TOPUP_TASK}_result`, handleTopupTaskResult)
// Unsubscribe from the 'sendCompleted' event on component unmount
return () => {
- EventEmitter.off('ev_topupTask_result', handleTopupTaskResult)
+ EventEmitter.off(`ev_${TOPUP_TASK}_result`, handleTopupTaskResult)
}
}, [isTopupTaskSentToQueue])
@@ -346,13 +348,13 @@ export const TopupScreen: FC> = observer(
}
EventEmitter.on(
- 'ev__handlePendingTopupTask_result',
+ `ev_${HANDLE_PENDING_TOPUP_TASK}_result`,
handlePendingTopupTaskResult,
)
return () => {
EventEmitter.off(
- 'ev__handlePendingTopupTask_result',
+ `ev_${HANDLE_PENDING_TOPUP_TASK}_result`,
handlePendingTopupTaskResult,
)
}
@@ -470,7 +472,7 @@ export const TopupScreen: FC> = observer(
0,
)
- WalletTask.topup(
+ WalletTask.topupQueue(
mintBalanceToTopup as MintBalance,
amountToTopupInt,
unit,
diff --git a/src/screens/TranDetailScreen.tsx b/src/screens/TranDetailScreen.tsx
index a2c09838..2252ee41 100644
--- a/src/screens/TranDetailScreen.tsx
+++ b/src/screens/TranDetailScreen.tsx
@@ -41,7 +41,7 @@ import {
import AppError, {Err} from '../utils/AppError'
import {log} from '../services/logService'
import {isArray} from 'lodash'
-import {NostrClient, NostrEvent, NostrProfile, TransactionTaskResult, WalletTask} from '../services'
+import {HANDLE_PENDING_TOPUP_TASK, NostrClient, NostrEvent, NostrProfile, TransactionTaskResult, WalletTask} from '../services'
import {Proof} from '../models/Proof'
import useColorScheme from '../theme/useThemeColor'
import useIsInternetReachable from '../utils/useIsInternetReachable'
@@ -58,6 +58,8 @@ import { QRCodeBlock } from './Wallet/QRCode'
import { MintListItem } from './Mints/MintListItem'
import { Token, getDecodedToken } from '@cashu/cashu-ts'
import { faTruckMedical } from '@fortawesome/free-solid-svg-icons'
+import { RECEIVE_OFFLINE_COMPLETE_TASK, RECEIVE_TASK } from '../services/wallet/receiveTask'
+import { REVERT_TASK } from '../services/wallet/revertTask'
if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
UIManager.setLayoutAnimationEnabledExperimental(true)
@@ -603,12 +605,12 @@ const ReceiveInfoBlock = function (props: {
// Subscribe to the 'receiveTask' event
if(isReceiveTaskSentToQueue === true) {
- EventEmitter.on('ev_receiveTask_result', handleReceiveTaskResult)
+ EventEmitter.on(`ev_${RECEIVE_TASK}_result`, handleReceiveTaskResult)
}
// Unsubscribe from the 'receiveTask' event on component unmount
return () => {
- EventEmitter.off('ev_receiveTask_result', handleReceiveTaskResult)
+ EventEmitter.off(`ev_${RECEIVE_TASK}_result`, handleReceiveTaskResult)
}
}, [isReceiveTaskSentToQueue])
@@ -647,7 +649,7 @@ const ReceiveInfoBlock = function (props: {
}
setIsReceiveTaskSentToQueue(true)
- WalletTask.receive(
+ WalletTask.receiveQueue(
tokenToRetry,
amountToReceive,
memo,
@@ -886,12 +888,12 @@ const ReceiveOfflineInfoBlock = function (props: {
// Subscribe to the 'sendCompleted' event
if(isReceiveOfflineCompleteTaskSentToQueue) {
- EventEmitter.on('ev_receiveOfflineCompleteTask_result', handleReceiveOfflineCompleteTaskResult)
+ EventEmitter.on(`ev_${RECEIVE_OFFLINE_COMPLETE_TASK}_result`, handleReceiveOfflineCompleteTaskResult)
}
// Unsubscribe from the 'sendCompleted' event on component unmount
return () => {
- EventEmitter.off('ev_receiveOfflineCompleteTask_result', handleReceiveOfflineCompleteTaskResult)
+ EventEmitter.off(`ev_${RECEIVE_OFFLINE_COMPLETE_TASK}_result`, handleReceiveOfflineCompleteTaskResult)
}
}, [isReceiveOfflineCompleteTaskSentToQueue])
@@ -902,7 +904,7 @@ const ReceiveOfflineInfoBlock = function (props: {
const receiveOfflineComplete = async function () {
setIsLoading(true)
setIsReceiveOfflineCompleteTaskSentToQueue(true)
- WalletTask.receiveOfflineComplete(transaction.id!)
+ WalletTask.receiveOfflineCompleteQueue(transaction.id!)
}
const onGoBack = () => {
@@ -1095,12 +1097,12 @@ const SendInfoBlock = function (props: {
// Subscribe to the 'sendCompleted' event
if(isRevertTaskSentToQueue) {
- EventEmitter.on('ev_revertTask_result', handleRevertTaskResult)
+ EventEmitter.on(`ev_${REVERT_TASK}_result`, handleRevertTaskResult)
}
// Unsubscribe from the 'sendCompleted' event on component unmount
return () => {
- EventEmitter.off('ev_revertTask_result', handleRevertTaskResult)
+ EventEmitter.off(`ev_${REVERT_TASK}_result`, handleRevertTaskResult)
}
}, [isRevertTaskSentToQueue])
@@ -1135,7 +1137,7 @@ const SendInfoBlock = function (props: {
})
}
- WalletTask.revert(transaction)
+ WalletTask.revertQueue(transaction)
setIsLoading(true)
setIsRevertTaskSentToQueue(true)
@@ -1308,7 +1310,7 @@ const TopupInfoBlock = function (props: {
setIsLoading(false)
// do not react to an active poller :)
- if(pollerExists(`handlePendingTopupTaskPoller-${result.paymentHash}`)) {
+ if(pollerExists(`handlePendingTopupPoller-${result.paymentHash}`)) {
return false
}
@@ -1328,11 +1330,11 @@ const TopupInfoBlock = function (props: {
}
// Subscribe to the 'sendCompleted' event
- EventEmitter.on('ev__handlePendingTopupTask_result', handlePendingTopupTaskResult)
+ EventEmitter.on(`ev_${HANDLE_PENDING_TOPUP_TASK}_result`, handlePendingTopupTaskResult)
// Unsubscribe from the 'sendCompleted' event on component unmount
return () => {
- EventEmitter.off('ev__handlePendingTopupTask_result', handlePendingTopupTaskResult)
+ EventEmitter.off(`ev_${HANDLE_PENDING_TOPUP_TASK}_result`, handlePendingTopupTaskResult)
}
}, [isPendingTopupTaskSentToQueue]))
@@ -1348,7 +1350,7 @@ const TopupInfoBlock = function (props: {
setIsLoading(true)
setIsPendingTopupTaskSentToQueue(true)
- WalletTask.handlePendingTopup(
+ WalletTask.handlePendingTopupQueue(
{paymentRequest}
)
@@ -1895,7 +1897,7 @@ const getPaymentRequestToRetry = (
const paymentRequest: PaymentRequest = pendingRecord.paymentRequest
if(!paymentRequest) {return undefined}
- if(pollerExists(`handlePendingTopupTaskPoller-${paymentRequest.paymentHash}`)) {return undefined}
+ if(pollerExists(`handlePendingTopupPoller-${paymentRequest.paymentHash}`)) {return undefined}
// return paymentRequest to retry if wallet somehow failed to retreive proofs for paid invoice
return paymentRequest
diff --git a/src/screens/TransferScreen.tsx b/src/screens/TransferScreen.tsx
index 1d886795..ebda8474 100644
--- a/src/screens/TransferScreen.tsx
+++ b/src/screens/TransferScreen.tsx
@@ -50,6 +50,7 @@ import { TranItem } from './TranDetailScreen'
import useIsInternetReachable from '../utils/useIsInternetReachable'
import { translate } from '../i18n'
import { MemoInputCard } from '../components/MemoInputCard'
+import { TRANSFER_TASK } from '../services/wallet/transferTask'
if (
@@ -428,12 +429,12 @@ useEffect(() => {
// Subscribe to the task result event
if(isTransferTaskSentToQueue) {
- EventEmitter.on('ev_transferTask_result', handleTransferTaskResult)
+ EventEmitter.on(`ev_${TRANSFER_TASK}_result`, handleTransferTaskResult)
}
// Unsubscribe from the task result event on component unmount
return () => {
- EventEmitter.off('ev_transferTask_result', handleTransferTaskResult)
+ EventEmitter.off(`ev_${TRANSFER_TASK}_result`, handleTransferTaskResult)
}
}, [isTransferTaskSentToQueue])
@@ -636,7 +637,7 @@ const transfer = async function () {
const amountToTransferInt = round(toNumber(amountToTransfer) * getCurrency(unit).precision, 0)
- WalletTask.transfer(
+ WalletTask.transferQueue(
mintBalanceToTransferFrom,
amountToTransferInt,
unit,
diff --git a/src/screens/WalletScreen.tsx b/src/screens/WalletScreen.tsx
index 9b1ac51c..9daca0c7 100644
--- a/src/screens/WalletScreen.tsx
+++ b/src/screens/WalletScreen.tsx
@@ -42,7 +42,7 @@ import {useStores} from '../models'
import {WalletStackScreenProps} from '../navigation'
import {Mint, UnitBalance} from '../models/Mint'
import {MintsByUnit} from '../models/MintsStore'
-import {log, NostrClient, WalletTaskResult} from '../services'
+import {HANDLE_CLAIM_TASK, HANDLE_RECEIVED_EVENT_TASK, log, NostrClient, WalletTaskResult} from '../services'
import {Env} from '../utils/envtypes'
import {Transaction} from '../models/Transaction'
import {TransactionListItem} from './Transactions/TransactionListItem'
@@ -64,7 +64,6 @@ import { CurrencyCode, MintUnit, MintUnitCurrencyPairs, convertToFromSats, getCu
import { CurrencyAmount } from './Wallet/CurrencyAmount'
import { LeftProfileHeader } from './ContactsScreen'
import { getUnixTime } from 'date-fns/getUnixTime'
-import { minibitsPngIcon } from '../components/MinibitsIcon'
const deploymentKey = APP_ENV === Env.PROD ? CODEPUSH_PRODUCTION_DEPLOYMENT_KEY : CODEPUSH_STAGING_DEPLOYMENT_KEY
const MINT_CHECK_INTERVAL = 60
@@ -172,7 +171,7 @@ export const WalletScreen: FC = observer(
}
// Only once on startup - Create websocket subscriptions to receive tokens or payment requests by NOSTR DMs
- WalletTask.receiveEventsFromRelays().catch(e => false)
+ WalletTask.receiveEventsFromRelaysQueue()
// Set wallet tab to preferred unit
const preferredUnit: MintUnit = userSettingsStore.preferredUnit
@@ -185,7 +184,7 @@ export const WalletScreen: FC = observer(
// go through websockets only if remote notifications not working as push data messages are
// delivered even if notifications are disabled on device
const isRemoteDataPushEnabled = walletProfileStore.device ? true : false
- if(!isRemoteDataPushEnabled) {nwcStore.receiveNwcEvents()}
+ if(!isRemoteDataPushEnabled) {nwcStore.receiveNwcEventsQueue()}
}
const handleReceivedEventTaskResult = async (result: WalletTaskResult) => {
@@ -203,16 +202,16 @@ export const WalletScreen: FC = observer(
}
Linking.addEventListener('url', handleDeeplink)
- EventEmitter.on('ev__handleReceivedEventTask_result', handleReceivedEventTaskResult)
- EventEmitter.on('ev__handleClaimTask_result', handleClaimTaskResult)
+ EventEmitter.on(`ev_${HANDLE_RECEIVED_EVENT_TASK}_result`, handleReceivedEventTaskResult)
+ EventEmitter.on(`ev_${HANDLE_CLAIM_TASK}_result`, handleClaimTaskResult)
getInitialData()
// Unsubscribe from the task result event on component unmount
return () => {
- EventEmitter.off('ev__handleReceivedEventTask_result', handleReceivedEventTaskResult)
- EventEmitter.off('ev__handleClaimTask_result', handleClaimTaskResult)
+ EventEmitter.off(`ev_${HANDLE_RECEIVED_EVENT_TASK}_result`, handleReceivedEventTaskResult)
+ EventEmitter.off(`ev_${HANDLE_CLAIM_TASK}_result`, handleClaimTaskResult)
}
}, [])
@@ -279,10 +278,10 @@ export const WalletScreen: FC = observer(
if (nowInSec - lastMintCheckRef.current > MINT_CHECK_INTERVAL) {
lastMintCheckRef.current = nowInSec
- WalletTask.handleInFlight()
- WalletTask.handleClaim()
- WalletTask.syncPendingStateWithMints()
- WalletTask.handlePendingTopups()
+ WalletTask.handleInFlightQueue()
+ WalletTask.handleClaimQueue()
+ WalletTask.syncStateWithAllMintsQueue({isPending: true})
+ WalletTask.handlePendingTopupsQueue()
if(userSettingsStore.exchangeCurrency) {
walletStore.refreshExchangeRate(userSettingsStore.exchangeCurrency)
diff --git a/src/services/nostrService.ts b/src/services/nostrService.ts
index 1f8dd2ea..1f179d9c 100644
--- a/src/services/nostrService.ts
+++ b/src/services/nostrService.ts
@@ -97,10 +97,10 @@ const reconnectToRelays = async function () {
// recreate subscriptions if all relays down
if(isRefreshSubNeeded) {
log.trace('[reconnectToRelays] Refreshing Nostr subscriptions')
- WalletTask.receiveEventsFromRelays().catch(e => false)
+ WalletTask.receiveEventsFromRelaysQueue().catch(e => false)
if(!walletProfileStore.device) {
- nwcStore.receiveNwcEvents()
+ nwcStore.receiveNwcEventsQueue()
}
}
}
diff --git a/src/services/notificationService.ts b/src/services/notificationService.ts
index b591414e..9b389523 100644
--- a/src/services/notificationService.ts
+++ b/src/services/notificationService.ts
@@ -11,6 +11,7 @@ import AppError, { Err } from '../utils/AppError'
import { Platform } from 'react-native'
import { rootStoreInstance, setupRootStore } from '../models'
import { NwcRequest, nwcPngUrl } from '../models/NwcStore';
+import { HANDLE_NWC_REQUEST_TASK } from './walletService'
export type NotifyReceiveToLnurlData = {
type: 'NotifyReceiveToLnurlData',
@@ -36,7 +37,10 @@ const NWC_CHANNEL_ID = 'nwcDefault';
const NWC_CHANNEL_NAME = 'Minibits NWC payment'
export const TASK_QUEUE_CHANNEL_ID = 'internalDefault'
-export const TASK_QUEUE_CHANNEL_NAME = 'Minibits internal tasks'
+export const TASK_QUEUE_CHANNEL_NAME = 'Minibits tasks'
+
+export const TEST_CHANNEL_ID = 'testDefault'
+export const TEST_CHANNEL_NAME = 'Minibits test tasks'
export const initNotifications = async () => {
let enabled = await areNotificationsEnabled()
@@ -93,6 +97,16 @@ export const initNotifications = async () => {
}
})
+ notifee.isChannelCreated(TEST_CHANNEL_ID).then(isCreated => {
+ if (!isCreated) {
+ notifee.createChannel({
+ id: TEST_CHANNEL_ID,
+ name: TASK_QUEUE_CHANNEL_NAME,
+ sound: 'default',
+ })
+ }
+ })
+
}
@@ -176,7 +190,7 @@ const _nwcRequestHandler = async function(remoteData: NotifyNwcRequestData) {
await notifee.displayNotification({
title: NWC_CHANNEL_NAME,
- body: 'Nostr wallet command is being processed.',
+ body: 'Processing remote NWC command...',
android: {
channelId: NWC_CHANNEL_ID,
asForegroundService: true,
@@ -186,10 +200,10 @@ const _nwcRequestHandler = async function(remoteData: NotifyNwcRequestData) {
indeterminate: true,
},
},
- data: {task: 'handleNwcRequestFromNotification', data: cleanedRequestEvent}, // Pass the task data to the foreground service
+ data: {task: HANDLE_NWC_REQUEST_TASK, data: cleanedRequestEvent}, // Pass the task data to the foreground service
})
} else {
- await nwcStore.handleNwcRequestFromNotification(requestEvent, nwcRequest)
+ await nwcStore.handleNwcRequestTask(requestEvent, nwcRequest)
}
}
diff --git a/src/services/syncQueueService.ts b/src/services/syncQueueService.ts
index 1d2645e0..927f0911 100644
--- a/src/services/syncQueueService.ts
+++ b/src/services/syncQueueService.ts
@@ -1,11 +1,11 @@
-import { Task, TaskId, TaskQueue, TaskStatus } from "simple-js-task-queue"
+import { Task, TaskId, TaskQueue, TaskStatus } from "taskon"
import {log} from './logService'
import EventEmitter from '../utils/eventEmitter'
import { TransactionTaskResult, WalletTaskResult } from "./walletService"
import { NotificationService } from "./notificationService"
let _queue: any = undefined
-// const start = new Date().getTime()
+
const getSyncQueue = function () {
if(!_queue) {
@@ -15,12 +15,12 @@ const getSyncQueue = function () {
stopOnError: false,
// verbose: true,
taskPrioritizationMode: "head",
- // memorizeTasks: true
+ memorizeTasks: true
})
- return _queue
+ return _queue as TaskQueue
}
- return _queue
+ return _queue as TaskQueue
}
const addTask = function (taskId: TaskId, task: Promise | any) {
@@ -54,18 +54,19 @@ const addPrioritizedTask = function (taskId: TaskId, task: Promise | any) {
const _handleTaskResult = async (taskId: TaskId, result: WalletTaskResult | TransactionTaskResult) => {
log.info(
`[_handleTaskResult] The result of task ${taskId}`, result
- )
-
- const queue: TaskQueue = getSyncQueue()
+ )
EventEmitter.emit(`ev_${result.taskFunction}_result`, result)
- /* if(queue.getAllTasksDetails(['idle', 'running']).length === 0) {
- if(await NotificationService.isNotificationDispayed()) {
+ const queue: TaskQueue = getSyncQueue()
+
+ if(queue.getAllTasksDetails(['idle', 'running']).length === 0) {
+
+ if(await NotificationService.isNotificationDispayed()) {
log.trace('[_handleTaskResult] stopForegroundService')
NotificationService.stopForegroundService()
}
- } */
+ }
}
// Helper function to handle the task status changes
@@ -76,7 +77,8 @@ const _handleTaskStatusChange = (status: TaskStatus, task: Task) => {
}
-export const SyncQueue = {
+export const SyncQueue = {
+ getSyncQueue,
addTask,
addPrioritizedTask,
}
diff --git a/src/services/wallet/receiveTask.ts b/src/services/wallet/receiveTask.ts
index 6eba5879..81077cec 100644
--- a/src/services/wallet/receiveTask.ts
+++ b/src/services/wallet/receiveTask.ts
@@ -24,9 +24,9 @@ const {
} = rootStoreInstance
// function names to pass to task results
-const RECEIVE = 'receiveTask'
-const RECEIVE_OFFLINE_PREPARE = 'receiveOfflinePrepareTask'
-const RECEIVE_OFFLINE_COMPLETE = 'receiveOfflineCompleteTask'
+export const RECEIVE_TASK = 'receiveTask'
+export const RECEIVE_OFFLINE_PREPARE_TASK = 'receiveOfflinePrepareTask'
+export const RECEIVE_OFFLINE_COMPLETE_TASK = 'receiveOfflineCompleteTask'
export const receiveTask = async function (
token: Token,
@@ -86,14 +86,15 @@ export const receiveTask = async function (
)
return {
- taskFunction: RECEIVE,
+ taskFunction: RECEIVE_TASK,
transaction,
message: `The mint ${mintToReceive} is blocked. You can unblock it in Settings.`,
} as TransactionTaskResult
}
const {
- receivedAmount,
+ receivedAmount,
+ receivedProofs,
outputToken,
swapFeePaid,
} = await receiveSync(
@@ -133,11 +134,12 @@ export const receiveTask = async function (
}
return {
- taskFunction: RECEIVE,
+ taskFunction: RECEIVE_TASK,
mintUrl: mintToReceive,
transaction,
message: `You've received ${formatCurrency(receivedAmount, getCurrency(unit).code)} ${getCurrency(unit).code} to your Minibits wallet.`,
receivedAmount,
+ receivedProofsCount: receivedProofs.length
} as TransactionTaskResult
} catch (e: any) {
@@ -158,7 +160,7 @@ export const receiveTask = async function (
log.error(e.name, e.message)
return {
- taskFunction: RECEIVE,
+ taskFunction: RECEIVE_TASK,
mintUrl: mintToReceive,
transaction,
message: e.message,
@@ -221,7 +223,7 @@ export const receiveOfflinePrepareTask = async function (
)
return {
- taskFunction: RECEIVE_OFFLINE_PREPARE,
+ taskFunction: RECEIVE_OFFLINE_PREPARE_TASK,
mintUrl: mintToReceive,
transaction: blockedTransaction,
message: `The mint ${mintToReceive} is blocked. You can unblock it in Settings.`,
@@ -240,7 +242,7 @@ export const receiveOfflinePrepareTask = async function (
)
return {
- taskFunction: RECEIVE_OFFLINE_PREPARE,
+ taskFunction: RECEIVE_OFFLINE_PREPARE_TASK,
mintUrl: mintToReceive,
transaction,
message: `You received ${formatCurrency(amountToReceive, getCurrency(unit).code)} ${getCurrency(unit).code} while offline. You need to redeem them to your wallet when you will be online again.`,
@@ -262,7 +264,7 @@ export const receiveOfflinePrepareTask = async function (
log.error(e.name, e.message)
return {
- taskFunction: RECEIVE_OFFLINE_PREPARE,
+ taskFunction: RECEIVE_OFFLINE_PREPARE_TASK,
mintUrl: mintToReceive,
transaction,
message: '',
@@ -307,7 +309,7 @@ export const receiveOfflineCompleteTask = async function (
)
return {
- taskFunction: RECEIVE_OFFLINE_COMPLETE,
+ taskFunction: RECEIVE_OFFLINE_COMPLETE_TASK,
mintUrl: mintToReceive,
transaction,
message: `The mint ${mintToReceive} is blocked. You can unblock it in Settings.`,
@@ -354,7 +356,7 @@ export const receiveOfflineCompleteTask = async function (
}
return {
- taskFunction: RECEIVE_OFFLINE_COMPLETE,
+ taskFunction: RECEIVE_OFFLINE_COMPLETE_TASK,
mintUrl: mintToReceive,
transaction,
message: `You've received ${formatCurrency(receivedAmount, getCurrency(unit).code)} ${getCurrency(unit).code} to your Minibits wallet.`,
@@ -378,7 +380,7 @@ export const receiveOfflineCompleteTask = async function (
}
return {
- taskFunction: RECEIVE_OFFLINE_COMPLETE,
+ taskFunction: RECEIVE_OFFLINE_COMPLETE_TASK,
mintUrl: mintToReceive,
transaction,
message: '',
diff --git a/src/services/wallet/revertTask.ts b/src/services/wallet/revertTask.ts
index 308dab93..af4b61ba 100644
--- a/src/services/wallet/revertTask.ts
+++ b/src/services/wallet/revertTask.ts
@@ -17,7 +17,7 @@ const {
walletStore
} = rootStoreInstance
-const REVERT = 'revertTask'
+export const REVERT_TASK = 'revertTask'
export const revertTask = async function (
transaction: Transaction
@@ -101,7 +101,7 @@ try {
}
return {
- taskFunction: REVERT,
+ taskFunction: REVERT_TASK,
mintUrl: mintInstance.mintUrl,
transaction,
message: `Transaction has been reverted and funds were returned to spendable balance.`,
@@ -125,7 +125,7 @@ try {
log.error(e.name, e.message)
return {
- taskFunction: REVERT,
+ taskFunction: REVERT_TASK,
mintUrl: transaction.mint,
transaction,
message: e.message,
diff --git a/src/services/wallet/sendTask.ts b/src/services/wallet/sendTask.ts
index c964dd67..4d0e7b98 100644
--- a/src/services/wallet/sendTask.ts
+++ b/src/services/wallet/sendTask.ts
@@ -28,6 +28,8 @@ const {
walletStore
} = rootStoreInstance
+export const SEND_TASK = 'sendTask'
+
export const sendTask = async function (
mintBalanceToSendFrom: MintBalance,
amountToSend: number,
@@ -38,8 +40,8 @@ export const sendTask = async function (
const mintUrl = mintBalanceToSendFrom.mintUrl
- log.trace('[send]', 'mintBalanceToSendFrom', mintBalanceToSendFrom)
- log.trace('[send]', 'amountToSend', {amountToSend, unit})
+ log.trace('[sendTask]', 'mintBalanceToSendFrom', mintBalanceToSendFrom)
+ log.trace('[sendTask]', 'amountToSend', {amountToSend, unit})
// create draft transaction
const transactionData: TransactionData[] = [
@@ -135,7 +137,7 @@ export const sendTask = async function (
poller(
`syncStateWithMintPoller-${mintUrl}`,
- WalletTask.syncStateWithMint,
+ WalletTask.syncStateWithMintQueue,
{
interval: 6 * 1000,
maxPolls: 5,
@@ -147,7 +149,7 @@ export const sendTask = async function (
}
return {
- taskFunction: 'sendTask',
+ taskFunction: SEND_TASK,
mintUrl,
transaction,
message: '',
@@ -170,7 +172,7 @@ export const sendTask = async function (
}
return {
- taskFunction: 'sendTask',
+ taskFunction: SEND_TASK,
mintUrl,
transaction,
message: e.message,
@@ -421,7 +423,7 @@ export const sendFromMintSync = async function (
log.error('[sendFromMintSync] Going to clean spent proofs from pending', {transactionId})
- await WalletTask.syncStateWithMintSync({
+ await WalletTask.syncStateWithMintTask({
proofsToSync: proofsStore.getByMint(mintUrl, {isPending: true, unit}),
mintUrl,
isPending: true
diff --git a/src/services/wallet/topupTask.ts b/src/services/wallet/topupTask.ts
index 433ba548..629e93f0 100644
--- a/src/services/wallet/topupTask.ts
+++ b/src/services/wallet/topupTask.ts
@@ -21,7 +21,7 @@ const {
// const {walletStore} = nonPersistedStores
-const TOPUP = 'topupTask'
+export const TOPUP_TASK = 'topupTask'
export const topupTask = async function (
mintBalanceToTopup: MintBalance,
@@ -138,19 +138,19 @@ export const topupTask = async function (
if(!nwcEvent) {
poller(
- `handlePendingTopupTaskPoller-${paymentRequest.paymentHash}`,
- WalletTask.handlePendingTopup,
+ `handlePendingTopupPoller-${paymentRequest.paymentHash}`,
+ WalletTask.handlePendingTopupQueue,
{
interval: 6 * 1000,
maxPolls: 10,
maxErrors: 2
},
{paymentRequest})
- .then(() => log.trace('Polling completed', [], `handlePendingTopupTaskPoller`))
+ .then(() => log.trace('Polling completed', [], `handlePendingTopupPoller`))
}
return {
- taskFunction: TOPUP,
+ taskFunction: TOPUP_TASK,
mintUrl,
transaction,
message: '',
@@ -177,7 +177,7 @@ export const topupTask = async function (
log.error(e.name, e.message)
return {
- taskFunction: TOPUP,
+ taskFunction: TOPUP_TASK,
transaction,
message: e.message,
error: WalletUtils.formatError(e),
diff --git a/src/services/wallet/transferTask.ts b/src/services/wallet/transferTask.ts
index f69e72b9..544489dd 100644
--- a/src/services/wallet/transferTask.ts
+++ b/src/services/wallet/transferTask.ts
@@ -21,7 +21,7 @@ const {
// const {walletStore} = nonPersistedStores
-const TRANSFER = 'transferTask'
+export const TRANSFER_TASK = 'transferTask'
export const transferTask = async function (
mintBalanceToTransferFrom: MintBalance,
@@ -248,7 +248,7 @@ export const transferTask = async function (
transaction.setBalanceAfter(balanceAfter)
return {
- taskFunction: TRANSFER,
+ taskFunction: TRANSFER_TASK,
mintUrl,
transaction,
message: `Lightning invoice has been successfully paid and settled with your Minibits ecash. Fee has been ${formatCurrency(transaction.fee, getCurrency(unit).code)} ${getCurrency(unit).code}.`,
@@ -268,7 +268,7 @@ export const transferTask = async function (
})
return {
- taskFunction: TRANSFER,
+ taskFunction: TRANSFER_TASK,
mintUrl,
transaction,
message: `Lightning payment did not complete in time. Your ecash will remain pending until the payment completes or fails.`,
@@ -287,7 +287,7 @@ export const transferTask = async function (
} catch (e: any) {
let message = e.message
let taskResult: TransactionTaskResult = {
- taskFunction: TRANSFER,
+ taskFunction: TRANSFER_TASK,
mintUrl,
transaction,
message,
@@ -350,7 +350,7 @@ export const transferTask = async function (
}
}
- await WalletTask.syncStateWithMintSync({
+ await WalletTask.syncStateWithMintTask({
proofsToSync: proofsStore.getByMint(mintUrl, {isPending: true, unit}),
mintUrl,
isPending: true
diff --git a/src/services/walletService.ts b/src/services/walletService.ts
index 9f9f0d4b..de8f8906 100644
--- a/src/services/walletService.ts
+++ b/src/services/walletService.ts
@@ -24,7 +24,7 @@ import { IncomingDataType, IncomingParser } from './incomingParser'
import { Contact } from '../models/Contact'
import { SyncQueue } from './syncQueueService'
import { receiveTask, receiveOfflinePrepareTask, receiveOfflineCompleteTask} from './wallet/receiveTask'
-import { sendTask } from './wallet/sendTask'
+import { sendFromMintSync, sendTask } from './wallet/sendTask'
import { topupTask } from './wallet/topupTask'
import { transferTask } from './wallet/transferTask'
import { revertTask } from './wallet/revertTask'
@@ -34,8 +34,6 @@ import { MintUnit, formatCurrency, getCurrency } from './wallet/currency'
import { MinibitsClient } from './minibitsService'
import { KeyChain } from './keyChain'
import { UnsignedEvent } from 'nostr-tools'
-import { minibitsPngIcon } from '../components/MinibitsIcon'
-
/**
* The default number of proofs per denomination to keep in a wallet.
@@ -45,31 +43,47 @@ export const DEFAULT_DENOMINATION_TARGET = 2
export const MAX_SWAP_INPUT_SIZE = 100
export const MAX_SYNC_INPUT_SIZE = 200 // 1000 hard mint limit
-type WalletTaskService = {
- syncPendingStateWithMints: () => Promise
- syncSpendableStateWithMints: () => Promise
- syncStateWithMint: (
+export const TEST_TASK = 'testTask'
+export const SWAP_ALL_TASK = 'swapAllTask'
+export const SYNC_STATE_WITH_ALL_MINTS_TASK = 'syncStateWithAllMintsTask'
+export const SYNC_STATE_WITH_MINT_TASK = 'syncStateWithMintTask'
+export const HANDLE_NWC_REQUEST_TASK = 'handleNwcRequestTask'
+export const HANDLE_CLAIM_TASK = 'handleClaimTask'
+export const HANDLE_PENDING_TOPUP_TASK = 'handlePendingTopupTask'
+export const HANDLE_INFLIGHT_BY_MINT_TASK = 'handleInFlightByMintTask'
+export const HANDLE_RECEIVED_EVENT_TASK = 'handleReceivedEventTask'
+
+type WalletTaskService = {
+ syncStateWithAllMintsQueue: (
+ options: {
+ isPending: boolean
+ }
+ ) => Promise
+ syncStateWithMintQueue: (
options: {
proofsToSync: Proof[],
mintUrl: string,
isPending: boolean
}
) => Promise
- syncStateWithMintSync: (
+ syncStateWithMintTask: (
options: {
proofsToSync: Proof[],
mintUrl: string,
isPending: boolean
}
) => Promise
- handleInFlight: () => Promise
- handlePendingTopups: () => Promise
- handlePendingTopup: (params: {
+ handleInFlightQueue: () => Promise
+ handlePendingTopupsQueue: () => Promise
+ handlePendingTopupQueue: (params: {
paymentRequest: PaymentRequest
}) => Promise
- handleClaim: () => Promise
- receiveEventsFromRelays: () => Promise
- transfer: (
+ handleClaimQueue: () => Promise
+ handleNwcRequestQueue: (params: {
+ requestEvent: NostrEvent
+ }) => Promise
+ receiveEventsFromRelaysQueue: () => Promise
+ transferQueue: (
mintBalanceToTransferFrom: MintBalance,
amountToTransfer: number,
unit: MintUnit,
@@ -79,36 +93,30 @@ type WalletTaskService = {
encodedInvoice: string,
nwcEvent?: NostrEvent
) => Promise
- receive: (
+ receiveQueue: (
token: Token,
amountToReceive: number,
memo: string,
encodedToken: string,
) => Promise
- receiveBatch: (
+ receiveOfflinePrepareQueue: (
token: Token,
amountToReceive: number,
memo: string,
encodedToken: string,
) => Promise
- receiveOfflinePrepare: (
- token: Token,
- amountToReceive: number,
- memo: string,
- encodedToken: string,
- ) => Promise
- receiveOfflineComplete: (
+ receiveOfflineCompleteQueue: (
transactionId: number
) => Promise
- send: (
+ sendQueue: (
mintBalanceToSendFrom: MintBalance,
amountToSend: number,
unit: MintUnit,
memo: string,
selectedProofs: Proof[]
) => Promise
- sendAll: () => Promise
- topup: (
+ swapAllQueue: () => Promise
+ topupQueue: (
mintBalanceToTopup: MintBalance,
amountToTopup: number,
unit: MintUnit,
@@ -116,20 +124,22 @@ type WalletTaskService = {
contactToSendTo?: Contact,
nwcEvent?: NostrEvent
) => Promise
- revert: (
+ revertQueue: (
transaction: Transaction
) => Promise
+ testQueue: () => Promise
}
export interface WalletTaskResult {
taskFunction: string
- mintUrl: string
message: string
+ mintUrl?: string
error?: AppError
[key: string]: any
}
export interface TransactionTaskResult extends WalletTaskResult {
+ mintUrl: string,
transaction?: Transaction
swapFeePaid?: number
lightningFeePaid?: number
@@ -177,10 +187,11 @@ const {
contactsStore,
relaysStore,
walletStore,
+ nwcStore,
} = rootStoreInstance
-const transfer = async function (
+const transferQueue = async function (
mintBalanceToTransferFrom: MintBalance,
amountToTransfer: number,
unit: MintUnit,
@@ -208,39 +219,60 @@ const transfer = async function (
}
-const receive = async function (
+const receiveQueue = async function (
token: Token,
amountToReceive: number,
memo: string,
encodedToken: string,
): Promise {
const now = new Date().getTime()
- SyncQueue.addPrioritizedTask(
- `receiveTask-${now}`,
- async () => await receiveTask(
- token,
- amountToReceive,
- memo,
- encodedToken,
+ const proofsCount = token.proofs.length
+
+ if(proofsCount > MAX_SWAP_INPUT_SIZE) {
+
+ SyncQueue.addPrioritizedTask(
+ `receiveBatchTask-${now}`,
+ async () => await receiveBatchTask(
+ token,
+ amountToReceive,
+ memo,
+ encodedToken,
+ )
)
- )
+
+ } else {
+
+ SyncQueue.addPrioritizedTask(
+ `receiveTask-${now}`,
+ async () => await receiveTask(
+ token,
+ amountToReceive,
+ memo,
+ encodedToken,
+ )
+ )
+ }
+
return
}
/*
* Receive big tokens in batches to keep mint load reasonable.
- * Used when optimizing wallet proof amounts but might become the default.
+ *
*/
-const receiveBatch = async function (
+const receiveBatchTask = async function (
token: Token,
amountToReceive: number,
memo: string,
encodedToken: string,
-) {
+) : Promise {
+
const maxBatchSize = MAX_SWAP_INPUT_SIZE
const mintUrl = token.mint
const proofsToReceive = token.proofs
const unit = token.unit
+
+ let receivedProofsCount = 0
if (proofsToReceive.length > maxBatchSize) {
@@ -259,31 +291,45 @@ const receiveBatch = async function (
}
const batchEncodedToken = getEncodedToken(batchToken)
-
- // Queued WalletTask
- receive(
+
+ const result = await receiveTask(
batchToken,
batchAmount,
`${memo} #${index}`,
batchEncodedToken,
- )
+ )
+
+ if(result.receivedProofsCount) {
+ receivedProofsCount += result.receivedProofsCount
+ }
}
} else {
// If the length is less than or equal to 100, do normal receive
- receive(
+ const result = await receiveTask(
token,
amountToReceive,
memo,
encodedToken,
)
+
+ if(result.receivedProofsCount) {
+ receivedProofsCount += result.receivedProofsCount
+ }
+ }
+
+ return {
+ taskFunction: 'receiveBatchTask',
+ mintUrl,
+ message: 'receiveBatchTask completed. ',
+ receivedProofsCount
}
}
-const receiveOfflinePrepare = async function (
+const receiveOfflinePrepareQueue = async function (
token: Token,
amountToReceive: number,
memo: string,
@@ -303,7 +349,7 @@ const receiveOfflinePrepare = async function (
}
-const receiveOfflineComplete = async function (
+const receiveOfflineCompleteQueue = async function (
transactionId: number
): Promise {
const now = new Date().getTime()
@@ -317,7 +363,7 @@ const receiveOfflineComplete = async function (
}
-const send = async function (
+const sendQueue = async function (
mintBalanceToSendFrom: MintBalance,
amountToSend: number,
unit: MintUnit,
@@ -340,60 +386,125 @@ const send = async function (
/*
- * sendAll moves all proofs to pending to prepare to swap them for standard amount preference
+ * swapAllTask sends all proofs to pending and swaps them with the mint for standard amount preference
* This decreases the total number of proofs held by the wallet. Used to optimize exported backup size.
+ * Heavy, needs to run using the foreground service. May freeze the wallet.
*/
-const sendAll = async function (): Promise {
- log.trace('[sendAll] start')
+
+const swapAllQueue = async function (): Promise {
+ const now = new Date().getTime()
+ return SyncQueue.addPrioritizedTask(
+ `swapAllTask-${now}`,
+ async () => await swapAllTask()
+ )
+}
+
+const swapAllTask = async function (): Promise {
+ log.trace('[swapAllTask] start')
+
if (mintsStore.mintCount === 0) {
- return
+ return {
+ taskFunction: SWAP_ALL_TASK,
+ message: 'No mints to swap with.'
+ }
}
+ let initialProofsCount = 0
+ let finalProofsCount = 0
+ const errors: string[] = []
+
// Move all proofs by mint units to pending as in offline mode (do not ask for swap)
for (const mint of mintsStore.allMints) {
// Do not create a pending transaction above mint's spent sync (check) limit as it becomes stuck pending
// As well keep tokens reasonably sized so that a device can keep related transaction in the state / load it from DB
- const maxBatchSize = MAX_SWAP_INPUT_SIZE
+ const maxBatchSize = MAX_SWAP_INPUT_SIZE
for (const unit of mint.units) {
const proofsToOptimize = proofsStore.getByMint(mint.mintUrl, { isPending: false, unit })
- const totalProofsCount = proofsToOptimize.length
+ initialProofsCount += proofsToOptimize.length
const mintBalance = mint.balances
-
- if (totalProofsCount > maxBatchSize) {
+
+ if (initialProofsCount > maxBatchSize) {
let index = 0
- for (let i = 0; i < totalProofsCount; i += maxBatchSize) {
+ for (let i = 0; i < initialProofsCount; i += maxBatchSize) {
index++
const batch = proofsToOptimize.slice(i, i + maxBatchSize)
const batchAmount = CashuUtils.getProofsAmount(batch)
- send(
+ const sendResult = await sendTask(
mintBalance!,
batchAmount,
unit,
`Optimize ecash #${index}`,
batch // forces offline mode
)
+
+ const encodedTokenToReceive: string = sendResult.encodedTokenToSend
+ const tokenToReceive = getDecodedToken(encodedTokenToReceive)
+ const tokenAmount = CashuUtils.getProofsAmount(tokenToReceive.proofs)
+
+ const receiveResult = await receiveBatchTask(
+ tokenToReceive,
+ tokenAmount,
+ tokenToReceive.memo as string,
+ encodedTokenToReceive
+ )
+
+ if(receiveResult.receivedProofsCount && receiveResult.receivedProofsCount > 0) {
+ finalProofsCount += receiveResult.receivedProofsCount
+ }
+
+ if(receiveResult.error) {
+ errors.push(receiveResult.error.message)
+ }
}
} else {
// If the length is less than or equal to limit, run with all proofs.
const proofsAmount = CashuUtils.getProofsAmount(proofsToOptimize)
- send(
+ const sendResult = await sendTask(
mintBalance!,
proofsAmount,
unit,
`Optimize ecash`,
proofsToOptimize // forces offline mode
)
+
+ const encodedTokenToReceive: string = sendResult.encodedTokenToSend
+ const tokenToReceive = getDecodedToken(encodedTokenToReceive)
+ const tokenAmount = CashuUtils.getProofsAmount(tokenToReceive.proofs)
+
+ const receiveResult = await receiveBatchTask(
+ tokenToReceive,
+ tokenAmount,
+ tokenToReceive.memo as string,
+ encodedTokenToReceive
+ )
+
+ if(receiveResult.receivedProofsCount && receiveResult.receivedProofsCount > 0) {
+ finalProofsCount += receiveResult.receivedProofsCount
+ }
+
+ if(receiveResult.error) {
+ errors.push(receiveResult.error.message)
+ }
}
+
}
}
+
+ return {
+ taskFunction: SWAP_ALL_TASK,
+ message: `Proofs optimization completed with ${errors.length} errors. Proofs number went from ${initialProofsCount} to ${finalProofsCount}`,
+ initialProofsCount,
+ finalProofsCount,
+ errors,
+ }
}
-const topup = async function (
+const topupQueue = async function (
mintBalanceToTopup: MintBalance,
amountToTopup: number,
unit: MintUnit,
@@ -418,7 +529,7 @@ const topup = async function (
-const revert = async function (
+const revertQueue = async function (
transaction: Transaction
): Promise {
const now = new Date().getTime()
@@ -430,59 +541,42 @@ const revert = async function (
}
-/*
- * Checks with all mints whether their proofs kept in pending state by the wallet have been spent.
- */
-const syncPendingStateWithMints = async function (): Promise {
- log.trace('[syncPendingStateWithMint] start')
- if (mintsStore.mintCount === 0) {
- return
- }
- const isPending = true
- 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) {
-
- if(pollerExists(`syncPendingStateWithMintPoller-${mint.mintUrl}`)) {
- log.trace('[syncPendingStateWithMint] Skipping, poller exists', {mintUrl: mint.mintUrl})
- continue
- }
-
- const proofsToSync = proofsStore.getByMint(mint.mintUrl, {isPending})
- const totalProofsCount = proofsToSync.length
-
- if(totalProofsCount === 0) {
- log.trace('[syncPendingStateWithMint] No proofs to sync, skipping...', {mintUrl: mint.mintUrl})
- continue
- }
-
- if (totalProofsCount > maxBatchSize) {
- for (let i = 0; i < totalProofsCount; i += maxBatchSize) {
- const batch = proofsToSync.slice(i, i + maxBatchSize)
- await syncStateWithMint({ proofsToSync: batch, mintUrl: mint.mintUrl, isPending })
- }
- } else {
- // If the length is less than or equal to 100, run syncStateWithMint with all proofs.
- await syncStateWithMint({ proofsToSync, mintUrl: mint.mintUrl, isPending });
- }
- }
+const syncStateWithAllMintsQueue = async function (options: {
+ isPending: boolean
+}): Promise {
+ const { isPending } = options
+ const now = new Date().getTime()
+ return SyncQueue.addPrioritizedTask(
+ `syncSpendableStateTask-${now}`,
+ async () => await syncStateWithAllMintsTask({ isPending })
+ )
}
-/*
- * Recover stuck wallet if tx error caused spent proof to remain in spendable state by the wallet.
- * TODO batching
- */
-const syncSpendableStateWithMints = async function (): Promise {
- log.trace('[syncSpendableStateWithMint] start')
+
+const syncStateWithAllMintsTask = async function (options: {
+ isPending: boolean
+}): Promise {
+ log.trace('[syncStateWithAllMintsTask] start')
if (mintsStore.mintCount === 0) {
- return
+ return {
+ taskFunction: SYNC_STATE_WITH_ALL_MINTS_TASK,
+ transactionStateUpdates: [],
+ completedTransactionIds: [],
+ errorTransactionIds: [],
+ revertedTransactionIds: [],
+ message: 'No mints'
+ }
}
- const isPending = false
+ const { isPending } = options
const maxBatchSize = MAX_SYNC_INPUT_SIZE
+ const transactionStateUpdates: TransactionStateUpdate[] = []
+ const completedTransactionIds: number[] = []
+ const errorTransactionIds: number[] = []
+ const revertedTransactionIds: number[] = []
+ const errors: string[] = []
// group proofs by mint so that we do max one call per mint
// does not depend on unit, process in batches by 100
@@ -491,30 +585,71 @@ const syncSpendableStateWithMints = async function (): Promise {
const totalProofsCount = proofsToSync.length
if(totalProofsCount === 0) {
- log.trace('[syncSpendableStateWithMint] No proofs to sync, skipping...', {mint})
+ log.trace('[syncStateWithAllMintsTask] No proofs to sync, skipping...', { mint: mint.mintUrl })
continue
}
if (totalProofsCount > maxBatchSize) {
- for (let i = 0; i < totalProofsCount; i += maxBatchSize) {
- const batch = proofsToSync.slice(i, i + maxBatchSize)
- await syncStateWithMint({ proofsToSync: batch, mintUrl: mint.mintUrl, isPending })
- }
+
+ for (let i = 0; i < totalProofsCount; i += maxBatchSize) {
+ const batch = proofsToSync.slice(i, i + maxBatchSize)
+ const result = await syncStateWithMintTask({ proofsToSync: batch, mintUrl: mint.mintUrl, isPending })
+
+ transactionStateUpdates.push(...result.transactionStateUpdates)
+ completedTransactionIds.push(...result.completedTransactionIds)
+ errorTransactionIds.push(...result.errorTransactionIds)
+ revertedTransactionIds.push(...result.revertedTransactionIds)
+
+ if(result.error) {
+ errors.push(result.error.message)
+ }
+ }
+
} else {
- // If the length is less than or equal to 100, run syncStateWithMint with all proofs.
- await syncStateWithMint({ proofsToSync, mintUrl: mint.mintUrl, isPending });
+ // If the length is less than or equal to 100, run syncStateWithMint with all proofs.
+ const result = await syncStateWithMintTask({ proofsToSync, mintUrl: mint.mintUrl, isPending })
+
+ transactionStateUpdates.push(...result.transactionStateUpdates)
+ completedTransactionIds.push(...result.completedTransactionIds)
+ errorTransactionIds.push(...result.errorTransactionIds)
+ revertedTransactionIds.push(...result.revertedTransactionIds)
+
+ if(result.error) {
+ errors.push(result.error.message)
+ }
}
}
- log.trace('[syncSpendableStateWithMints] returning')
+ let totalSpent = 0
+ let message = ''
- return
+ for (const update of transactionStateUpdates) {
+ if(update.spentByMintAmount) {
+ totalSpent += update.spentByMintAmount
+ }
+ }
+
+ if(isPending) {
+ message = 'Pending proofs were synced with the mints.'
+ } else {
+ message = `Sync completed with ${errors.length} errors. Spent ecash with ${totalSpent} amount was cleaned.`
+ }
+
+ return {
+ taskFunction: SYNC_STATE_WITH_ALL_MINTS_TASK,
+ transactionStateUpdates,
+ completedTransactionIds,
+ errorTransactionIds,
+ revertedTransactionIds,
+ errors,
+ message
+ }
}
/*
- * Pass _syncStateWithMintTask function into synchronous queue for safe processing without race conditions on proof counters. *
+ * Pass syncStateWithMintTask function into synchronous queue for safe processing without race conditions on proof counters. *
*/
-const syncStateWithMint = async function (
+const syncStateWithMintQueue = async function (
options: {
proofsToSync: Proof[],
mintUrl: string,
@@ -522,37 +657,16 @@ const syncStateWithMint = async function (
}
): Promise {
const {mintUrl, isPending, proofsToSync} = options
- log.trace('[syncStateWithMint] start', {mintUrl, isPending, proofsToSyncCount: proofsToSync.length})
+ log.trace('[syncStateWithMintQueue] start', {mintUrl, isPending, proofsToSyncCount: proofsToSync.length})
const now = new Date().getTime()
return SyncQueue.addTask(
- `_syncStateWithMintTask-${now}`,
- async () => await _syncStateWithMintTask({proofsToSync, mintUrl, isPending})
+ `syncStateWithMintTask-${now}`,
+ async () => await syncStateWithMintTask({proofsToSync, mintUrl, isPending})
)
}
-
-// Use only within another queued task!
-const syncStateWithMintSync = async function (
- options: {
- proofsToSync: Proof[],
- mintUrl: string,
- isPending: boolean
- },
-
-): Promise {
- const {mintUrl, isPending, proofsToSync} = options
-
- log.trace('[syncStateWithMintSync] start', {mintUrl, isPending, proofsToSyncCount: proofsToSync.length})
-
- if(proofsToSync.length > 0) {
- return await _syncStateWithMintTask({proofsToSync, mintUrl, isPending})
- } else {
- log.warn('[syncStateWithMintSync] No proofs to sync, skipping...', {mintUrl, isPending})
- }
-}
-
-
+
/*
* Checks with the mint whether proofs have been spent / pending by mint.
@@ -570,7 +684,7 @@ const syncStateWithMintSync = async function (
* @returns WalletTaskResult
*/
-const _syncStateWithMintTask = async function (
+const syncStateWithMintTask = async function (
options: {
proofsToSync: Proof[],
mintUrl: string,
@@ -578,7 +692,7 @@ const _syncStateWithMintTask = async function (
}
): Promise {
- log.trace('[_syncStateWithMintTask] start', {mintUrl: options.mintUrl})
+ log.trace('[syncStateWithMintTask] start', {mintUrl: options.mintUrl})
const transactionStateUpdates: TransactionStateUpdate[] = []
const completedTransactionIds: number[] = []
@@ -593,10 +707,10 @@ const _syncStateWithMintTask = async function (
if (proofsToSync.length === 0) {
const message = `No ${isPending ? 'pending' : ''} proofs found for mint, skipping mint call...`
- log.trace('[_syncStateWithMintTask]', message, mintUrl)
+ log.trace('[syncStateWithMintTask]', message, mintUrl)
return {
- taskFunction: '_syncStateWithMintTask',
+ taskFunction: SYNC_STATE_WITH_MINT_TASK,
mintUrl,
message,
transactionStateUpdates: [],
@@ -620,7 +734,7 @@ const _syncStateWithMintTask = async function (
const pendingByMintAmount = CashuUtils.getProofsAmount(proofsByState.PENDING)
const unspentByMintAmount = CashuUtils.getProofsAmount(proofsByState.UNSPENT)
- log.debug('[_syncStateWithMintTask]', `${isPending ? 'Pending' : ''} spent and pending by mint amounts`, {
+ log.debug('[syncStateWithMintTask]', `${isPending ? 'Pending' : ''} spent and pending by mint amounts`, {
spentByMintAmount,
pendingByMintAmount,
unspentByMintAmount,
@@ -680,7 +794,7 @@ const _syncStateWithMintTask = async function (
)
if(unspentProofs.length > 0) {
- log.trace('[_syncStateWithMintTask]', `Moving ${unspentProofs.length} unspent proofs from pending back to spendable.`)
+ log.trace('[syncStateWithMintTask]', `Moving ${unspentProofs.length} unspent proofs from pending back to spendable.`)
// remove it from pending proofs in the wallet
proofsStore.removeProofs(unspentProofs, true, true)
// add proofs back to the spendable wallet
@@ -716,12 +830,12 @@ const _syncStateWithMintTask = async function (
} as TransactionStateUpdate
})
- log.trace('[_syncStateWithMintTask]', {spentStateUpdates})
+ log.trace('[syncStateWithMintTask]', {spentStateUpdates})
transactionStateUpdates.push(...spentStateUpdates)
// Update related transactions statuses
- log.debug('[_syncStateWithMintTask]', 'Transaction id(s) to complete', completedTransactionIds.toString())
+ log.debug('[syncStateWithMintTask]', 'Transaction id(s) to complete', completedTransactionIds.toString())
// Complete related transactions
if (completedTransactionIds.length > 0) {
@@ -753,7 +867,7 @@ const _syncStateWithMintTask = async function (
JSON.stringify(transactionDataUpdate),
)
- log.error('[_syncStateWithMintTask]', `Transaction status update error`, {spentStateUpdates})
+ log.error('[syncStateWithMintTask]', `Transaction status update error`, {spentStateUpdates})
stopPolling(`syncStateWithMintPoller-${mintUrl}`)
}
}
@@ -805,7 +919,7 @@ const _syncStateWithMintTask = async function (
updatedStatus: TransactionStatus.PENDING
} as TransactionStateUpdate))
- log.trace('[_syncStateWithMintTask]', {pendingStateUpdates})
+ log.trace('[syncStateWithMintTask]', {pendingStateUpdates})
transactionStateUpdates.push(...pendingStateUpdates)
@@ -818,7 +932,7 @@ const _syncStateWithMintTask = async function (
}
// Update related transactions statuses
- log.debug('[_syncStateWithMintTask]', 'Transaction id(s) to be pending', pendingTransactionIds.toString())
+ log.debug('[syncStateWithMintTask]', 'Transaction id(s) to be pending', pendingTransactionIds.toString())
// Keep or make related transactions pending (mostly timed-out transfers from PREPARED status)
if (pendingTransactionIds.length > 0) {
@@ -842,7 +956,7 @@ const _syncStateWithMintTask = async function (
// are not pending anymore nor were spent - thus lightning payment failed
const remainingSecrets: string[] = getSnapshot(proofsStore.pendingByMintSecrets)
- log.trace('[_syncStateWithMintTask]', 'Remaining pendingByMintSecrets', remainingSecrets)
+ log.trace('[syncStateWithMintTask]', 'Remaining pendingByMintSecrets', remainingSecrets)
if(remainingSecrets.length > 0) {
let secretsTobeMovedToSpendable: string[] = []
@@ -856,7 +970,7 @@ const _syncStateWithMintTask = async function (
}
if(secretsTobeMovedToSpendable.length > 0) {
- log.debug('[_syncStateWithMintTask]', 'Moving proofs from pendingByMint to spendable', {secretsTobeMovedToSpendable})
+ log.debug('[syncStateWithMintTask]', 'Moving proofs from pendingByMint to spendable', {secretsTobeMovedToSpendable})
// Map to track reverted amounts by transaction ID
const transactionStateMap: { [key: number]: number } = {}
@@ -891,7 +1005,7 @@ const _syncStateWithMintTask = async function (
updatedStatus: TransactionStatus.REVERTED
} as TransactionStateUpdate))
- log.trace('[_syncStateWithMintTask]', {revertedStateUpdates})
+ log.trace('[syncStateWithMintTask]', {revertedStateUpdates})
transactionStateUpdates.push(...revertedStateUpdates)
@@ -923,7 +1037,7 @@ const _syncStateWithMintTask = async function (
}
return {
- taskFunction: '_syncStateWithMintTask',
+ taskFunction: SYNC_STATE_WITH_MINT_TASK,
mintUrl,
message: `Completed sync for ${isPending ? 'pending' : ''} proofs with the mint`,
transactionStateUpdates,
@@ -934,14 +1048,14 @@ const _syncStateWithMintTask = async function (
} as SyncStateTaskResult
} catch(e: any) {
// silent
- log.error('[_syncStateWithMintTask]', e.name, {message: e.message, mintUrl})
+ log.error('[syncStateWithMintTask]', e.name, {message: e.message, mintUrl})
if(mint && e.name === Err.MINT_ERROR && e.message.includes('network')) {
mint.setStatus(MintStatus.OFFLINE)
}
return {
- taskFunction: '_syncStateWithMintTask',
+ taskFunction: SYNC_STATE_WITH_MINT_TASK,
mintUrl,
message: `Sync for ${isPending ? 'pending' : ''} proofs with the mint ended with error: ${e.message}`,
error: e,
@@ -955,7 +1069,7 @@ const _syncStateWithMintTask = async function (
}
-const handleInFlight = async function (): Promise {
+const handleInFlightQueue = async function (): Promise {
log.trace('[handleInFlight] start')
if (mintsStore.mintCount === 0) {
return
@@ -971,8 +1085,8 @@ const handleInFlight = async function (): Promise {
const now = new Date().getTime()
SyncQueue.addTask(
- `_handleInFlightByMintTask-${now}`,
- async () => await _handleInFlightByMintTask(mint)
+ `${HANDLE_INFLIGHT_BY_MINT_TASK}-${now}`,
+ async () => await handleInFlightByMintTask(mint)
)
}
@@ -983,12 +1097,12 @@ const handleInFlight = async function (): Promise {
/*
* Recover proofs that were issued by mint, but wallet failed to receive them if swap did not complete.
*/
-const _handleInFlightByMintTask = async function (mint: Mint): Promise {
+const handleInFlightByMintTask = async function (mint: Mint): Promise {
const mintUrl = mint.mintUrl
const inFlightCounters = mint.proofsCountersWithInFlightRequests
- log.trace('[_handleInFlightByMintTask]', {mintUrl: mint.mintUrl, inFlightCounters})
+ log.trace('[handleInFlightByMintTask]', {mintUrl: mint.mintUrl, inFlightCounters})
const allInFlightRequestLength = mint.allInFLightRequests?.length
const errors: string[] = []
@@ -1036,7 +1150,7 @@ const _handleInFlightByMintTask = async function (mint: Mint): Promise {
+const handlePendingTopupsQueue = async function (): Promise {
const paymentRequests: PaymentRequest[] = paymentRequestsStore.allOutgoing
- log.trace('[handlePendingTopups] start', {paymentRequests})
+ log.trace('[handlePendingTopupsQueue] start', {paymentRequests})
if (paymentRequests.length === 0) {
- log.trace('[handlePendingTopups]', 'No outgoing payment requests in store - skipping task send to the queue...')
+ log.trace('[handlePendingTopupsQueue]', 'No outgoing payment requests in store - skipping task send to the queue...')
return
}
for (const pr of paymentRequests) {
// skip pr if active poller exists
- if(pollerExists(`handlePendingTopupTaskPoller-${pr.paymentHash}`)) {
- log.trace('[handlePendingTopups] Skipping check of paymentRequest, poller exists', {paymentHash: pr.paymentHash})
+ if(pollerExists(`handlePendingTopupPoller-${pr.paymentHash}`)) {
+ log.trace('[handlePendingTopupsQueue] Skipping check of paymentRequest, poller exists', {paymentHash: pr.paymentHash})
continue
}
const now = new Date().getTime()
SyncQueue.addTask(
- `_handlePendingTopupTask-${now}`,
- async () => await _handlePendingTopupTask({paymentRequest: pr})
+ `handlePendingTopupTask-${now}`,
+ async () => await handlePendingTopupTask({paymentRequest: pr})
)
}
}
-const handlePendingTopup = async function (params: {paymentRequest: PaymentRequest}): Promise {
+const handlePendingTopupQueue = async function (params: {paymentRequest: PaymentRequest}): Promise {
const {paymentRequest} = params
log.trace('[handlePendingTopup] start', {paymentHash: paymentRequest.paymentHash})
@@ -1367,13 +1481,13 @@ const handlePendingTopup = async function (params: {paymentRequest: PaymentReque
SyncQueue.addTask(
`_handlePendingTopupTask-${now}`,
- async () => await _handlePendingTopupTask({paymentRequest})
+ async () => await handlePendingTopupTask({paymentRequest})
)
}
-const _handlePendingTopupTask = async function (params: {paymentRequest: PaymentRequest}): Promise {
+const handlePendingTopupTask = async function (params: {paymentRequest: PaymentRequest}): Promise {
const {paymentRequest: pr} = params
const transactionId = {...pr}.transactionId || 0// copy
const mintUrl = {...pr}.mint // copy
@@ -1403,7 +1517,7 @@ const _handlePendingTopupTask = async function (params: {paymentRequest: Payment
case MintQuoteState.UNPAID:
case MintQuoteState.ISSUED:
if (isBefore(pr.expiresAt as Date, new Date())) {
- log.debug('[_handlePendingTopupTask]', `Invoice expired, removing: ${pr.paymentHash}`)
+ log.debug('[handlePendingTopupTask]', `Invoice expired, removing: ${pr.paymentHash}`)
// expire related tx - but only if it has not been completed before this check
if(transaction.status !== TransactionStatus.COMPLETED) {
@@ -1425,10 +1539,10 @@ const _handlePendingTopupTask = async function (params: {paymentRequest: Payment
}
// continue
case MintQuoteState.UNPAID:
- log.trace('[_handlePendingTopupTask] Quote not paid', {mintUrl, mintQuote})
+ log.trace('[handlePendingTopupTask] Quote not paid', {mintUrl, mintQuote})
return {
- taskFunction: '_handlePendingTopupTask',
+ taskFunction: HANDLE_PENDING_TOPUP_TASK,
transaction,
mintUrl,
unit,
@@ -1455,7 +1569,7 @@ const _handlePendingTopupTask = async function (params: {paymentRequest: Payment
} catch (e: any) {
if(e.message.includes('outputs have already been signed before')) {
- log.error('[_handlePendingTopupTask] Increasing proofsCounter outdated values and repeating mintProofs.')
+ log.error('[handlePendingTopupTask] Increasing proofsCounter outdated values and repeating mintProofs.')
proofs = (await walletStore.mintProofs(
mintUrl as string,
@@ -1485,7 +1599,7 @@ const _handlePendingTopupTask = async function (params: {paymentRequest: Payment
}
)
- stopPolling(`handlePendingTopupTaskPoller-${paymentHash}`)
+ stopPolling(`handlePendingTopupPoller-${paymentHash}`)
const currencyCode = getCurrency(pr.mintUnit!).code
// update related tx
@@ -1520,7 +1634,7 @@ const _handlePendingTopupTask = async function (params: {paymentRequest: Payment
paymentRequestsStore.removePaymentRequest(pr)
return {
- taskFunction: '_handlePendingTopupTask',
+ taskFunction: HANDLE_PENDING_TOPUP_TASK,
mintUrl,
unit,
amount,
@@ -1532,10 +1646,10 @@ const _handlePendingTopupTask = async function (params: {paymentRequest: Payment
* ISSUED
*/
case MintQuoteState.ISSUED:
- log.trace('[_handlePendingTopupTask] Quote already issued', {mintUrl, mintQuote})
+ log.trace('[handlePendingTopupTask] Quote already issued', {mintUrl, mintQuote})
return {
- taskFunction: '_handlePendingTopupTask',
+ taskFunction: HANDLE_PENDING_TOPUP_TASK,
transaction,
mintUrl,
unit,
@@ -1547,9 +1661,9 @@ const _handlePendingTopupTask = async function (params: {paymentRequest: Payment
* UNKNOWN
*/
default:
- log.error(`[_handlePendingTopupTask] Unknown MintQuoteState`, {state})
+ log.error(`[handlePendingTopupTask] Unknown MintQuoteState`, {state})
return {
- taskFunction: '_handlePendingTopupTask',
+ taskFunction: HANDLE_PENDING_TOPUP_TASK,
transaction,
mintUrl,
unit,
@@ -1562,23 +1676,23 @@ const _handlePendingTopupTask = async function (params: {paymentRequest: Payment
} catch (e: any) {
return {
- taskFunction: '_handlePendingTopupTask',
+ taskFunction: HANDLE_PENDING_TOPUP_TASK,
mintUrl,
unit,
amount,
paymentHash,
transaction,
error: {name: e.name, message: e.message, params: e.params || undefined},
- message: `_handlePendingTopupTask ended with error: ${e.message}`,
+ message: `handlePendingTopupTask ended with error: ${e.message}`,
} as TransactionTaskResult
}
}
-const handleClaim = async function (): Promise {
+const handleClaimQueue = async function (): Promise {
- log.info('[handleClaim] start')
+ log.info('[handleClaimQueue] start')
const {walletId, seedHash, pubkey} = walletProfileStore
const {isBatchClaimOn} = userSettingsStore
let recoveredSeedHash: string | undefined = undefined
@@ -1610,25 +1724,25 @@ const handleClaim = async function (): Promise {
)
if(claimedTokens.length === 0) {
- log.debug('[handleClaim] No claimed invoices returned from the server...')
+ log.debug('[handleClaimQueue] No claimed invoices returned from the server...')
return
}
- log.debug(`[handleClaim] Claimed ${claimedTokens.length} tokens from the server...`)
+ log.debug(`[handleClaimQueue] Claimed ${claimedTokens.length} tokens from the server...`)
for(const claimedToken of claimedTokens) {
const now = new Date().getTime()
SyncQueue.addTask(
- `_handleClaimTask-${now}`,
- async () => await _handleClaimTask({claimedToken})
+ `handleClaimTask-${now}`,
+ async () => await handleClaimTask({claimedToken})
)
}
return
}
-const _handleClaimTask = async function (params: {
+const handleClaimTask = async function (params: {
claimedToken: {
token: string,
zapSenderProfile?: string,
@@ -1639,16 +1753,16 @@ const _handleClaimTask = async function (params: {
try {
const {claimedToken} = params
- log.debug('[_handleClaimTask] claimed token', {claimedToken})
+ log.debug('[handleClaimTask] claimed token', {claimedToken})
if(!claimedToken.token) {
- throw new AppError(Err.VALIDATION_ERROR, '[_handleClaimTask] Missing encodedToken to receive.')
+ throw new AppError(Err.VALIDATION_ERROR, '[handleClaimTask] Missing encodedToken to receive.')
}
const encryptedToken = claimedToken.token
const encodedToken = await NostrClient.decryptNip04(MINIBIT_SERVER_NOSTR_PUBKEY, encryptedToken)
- log.debug('[_handleClaimTask] decrypted token', {encodedToken})
+ log.debug('[handleClaimTask] decrypted token', {encodedToken})
decoded = getDecodedToken(encodedToken)
const amountToReceive = CashuUtils.getProofsAmount(decoded.proofs)
@@ -1683,7 +1797,7 @@ const _handleClaimTask = async function (params: {
return {
mintUrl: decoded.mint,
- taskFunction: '_handleClaimTask',
+ taskFunction: HANDLE_CLAIM_TASK,
message: result.error ? result.error.message : 'Ecash sent to your lightning address has been received.',
error: result.error || undefined,
proofsCount: decoded.proofs.length,
@@ -1695,7 +1809,7 @@ const _handleClaimTask = async function (params: {
return {
mintUrl: decoded ? decoded.mint : '',
- taskFunction: '_handleClaimTask',
+ taskFunction: HANDLE_CLAIM_TASK,
message: e.message,
error: WalletUtils.formatError(e),
} as WalletTaskResult
@@ -1704,6 +1818,20 @@ const _handleClaimTask = async function (params: {
+const handleNwcRequestQueue = async function (params: {requestEvent: NostrEvent}): Promise {
+ const {requestEvent} = params
+ log.trace('[handleNwcRequestQueue] start', {requestEvent})
+
+ const now = new Date().getTime()
+
+ SyncQueue.addTask(
+ `handleNwcRequestTask-${now}`,
+ async () => await nwcStore.handleNwcRequestTask(requestEvent)
+ )
+}
+
+
+
const _sendTopupNotification = async function (pr: PaymentRequest) {
const currencyCode = getCurrency(pr.mintUnit!).code
@@ -1717,7 +1845,7 @@ const _sendTopupNotification = async function (pr: PaymentRequest) {
/*
* Checks with NOSTR relays whether there is ecash to be received or an invoice to be paid.
*/
-const receiveEventsFromRelays = async function (): Promise {
+const receiveEventsFromRelaysQueue = async function (): Promise {
log.trace('[receiveEventsFromRelays] starting listening for events')
if(!walletProfileStore.pubkey) {
@@ -1778,8 +1906,8 @@ const receiveEventsFromRelays = async function (): Promise {
const now = new Date().getTime()
SyncQueue.addTask(
- `_handleReceivedEventTask-${now}`,
- async () => await _handleReceivedEventTask(event)
+ `handleReceivedEventTask-${now}`,
+ async () => await handleReceivedEventTask(event)
)
},
oneose() {
@@ -1805,7 +1933,7 @@ const receiveEventsFromRelays = async function (): Promise {
}
}
-const _handleReceivedEventTask = async function (encryptedEvent: NostrEvent): Promise {
+const handleReceivedEventTask = async function (encryptedEvent: NostrEvent): Promise {
try {
let directMessageEvent: NostrEvent | UnsignedEvent | undefined = undefined
let decryptedMessage: string | undefined = undefined
@@ -1827,7 +1955,7 @@ const _handleReceivedEventTask = async function (encryptedEvent: NostrEvent): Pr
// set REAL direct message created_at as the lastPendingReceivedCheck
contactsStore.setLastPendingReceivedCheck(directMessageEvent.created_at)
- log.trace('[_handleReceivedEventTask]', 'Received event', {directMessageEvent})
+ log.trace('[handleReceivedEventTask]', 'Received event', {directMessageEvent})
// get sender profile and save it as a contact
// this is not valid for events sent from LNURL bridge, that are sent and signed by a minibits server key
@@ -1882,7 +2010,7 @@ const _handleReceivedEventTask = async function (encryptedEvent: NostrEvent): Pr
}
}
} catch (e: any) {
- log.warn('[_handleReceivedEventTask]', 'Could not get sender from zapRequest', {message: e.message, maybeZapSenderString})
+ log.warn('[handleReceivedEventTask]', 'Could not get sender from zapRequest', {message: e.message, maybeZapSenderString})
}
}
}
@@ -1890,7 +2018,7 @@ const _handleReceivedEventTask = async function (encryptedEvent: NostrEvent): Pr
// parse incoming message
const incoming = IncomingParser.findAndExtract(decryptedMessage)
- log.trace('[_handleReceivedEventTask]', 'Incoming data', {incoming})
+ log.trace('[handleReceivedEventTask]', 'Incoming data', {incoming})
//
// Receive token start
@@ -1935,7 +2063,7 @@ const _handleReceivedEventTask = async function (encryptedEvent: NostrEvent): Pr
return {
mintUrl: decoded.mint,
- taskFunction: '_handleReceivedEventTask',
+ taskFunction: HANDLE_RECEIVED_EVENT_TASK,
message: 'Incoming ecash token has been received.',
proofsCount: decoded.proofs.length,
proofsAmount: receivedAmount,
@@ -1996,7 +2124,7 @@ const _handleReceivedEventTask = async function (encryptedEvent: NostrEvent): Pr
return {
mintUrl: '',
- taskFunction: '_handleReceivedEventTask',
+ taskFunction: HANDLE_RECEIVED_EVENT_TASK,
message: 'Incoming payment request been received.',
proofsCount: 0,
proofsAmount: amount,
@@ -2004,19 +2132,18 @@ const _handleReceivedEventTask = async function (encryptedEvent: NostrEvent): Pr
} as WalletTaskResult
}
else if(incoming.type === IncomingDataType.CASHU_PAYMENT_REQUEST) {
- throw new AppError(Err.NOTFOUND_ERROR, 'CASHU_PAYMENT_REQUEST support is not yet implemented.', {caller: '_handleReceivedEventTask'})
+ throw new AppError(Err.NOTFOUND_ERROR, 'CASHU_PAYMENT_REQUEST support is not yet implemented.', {caller: HANDLE_RECEIVED_EVENT_TASK})
}
else if (incoming.type === IncomingDataType.LNURL) {
- throw new AppError(Err.NOTFOUND_ERROR, 'LNURL support is not yet implemented.', {caller: '_handleReceivedEventTask'})
+ throw new AppError(Err.NOTFOUND_ERROR, 'LNURL support is not yet implemented.', {caller: HANDLE_RECEIVED_EVENT_TASK})
} else {
throw new AppError(Err.NOTFOUND_ERROR, 'Received unknown message', incoming)
}
} catch (e: any) {
- log.error('[_handleReceivedEventTask]', e.name, e.message)
+ log.error('[handleReceivedEventTask]', e.name, e.message)
- return {
- mintUrl: '',
- taskFunction: '_handleReceivedEventTask',
+ return {
+ taskFunction: HANDLE_RECEIVED_EVENT_TASK,
message: e.message,
error: WalletUtils.formatError(e)
} as WalletTaskResult
@@ -2068,23 +2195,36 @@ const _extractZapSenderData = function (str: string) {
}
+const testQueue = async () => {
+ const now = new Date().getTime()
+
+ SyncQueue.addTask(
+ `testTask-${now}`,
+ async () => {
+ await new Promise((res) => setTimeout(res, 10 * 1000))
+ return `testTask-${now} result`
+ }
+ )
+};
+
+
export const WalletTask: WalletTaskService = {
- syncPendingStateWithMints,
- syncSpendableStateWithMints,
- syncStateWithMint,
- syncStateWithMintSync,
- handleInFlight,
- handlePendingTopups,
- handlePendingTopup,
- handleClaim,
- receiveEventsFromRelays,
- receive,
- receiveBatch,
- receiveOfflinePrepare,
- receiveOfflineComplete,
- send,
- sendAll,
- transfer,
- topup,
- revert
+ syncStateWithAllMintsQueue,
+ syncStateWithMintQueue,
+ syncStateWithMintTask,
+ handleInFlightQueue,
+ handlePendingTopupsQueue,
+ handlePendingTopupQueue,
+ handleClaimQueue,
+ handleNwcRequestQueue,
+ receiveEventsFromRelaysQueue,
+ receiveQueue,
+ receiveOfflinePrepareQueue,
+ receiveOfflineCompleteQueue,
+ sendQueue,
+ swapAllQueue,
+ transferQueue,
+ topupQueue,
+ revertQueue,
+ testQueue
}
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 5f3f32b3..9e7a7070 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1712,6 +1712,174 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/aix-ppc64@npm:0.23.1":
+ version: 0.23.1
+ resolution: "@esbuild/aix-ppc64@npm:0.23.1"
+ conditions: os=aix & cpu=ppc64
+ languageName: node
+ linkType: hard
+
+"@esbuild/android-arm64@npm:0.23.1":
+ version: 0.23.1
+ resolution: "@esbuild/android-arm64@npm:0.23.1"
+ conditions: os=android & cpu=arm64
+ languageName: node
+ linkType: hard
+
+"@esbuild/android-arm@npm:0.23.1":
+ version: 0.23.1
+ resolution: "@esbuild/android-arm@npm:0.23.1"
+ conditions: os=android & cpu=arm
+ languageName: node
+ linkType: hard
+
+"@esbuild/android-x64@npm:0.23.1":
+ version: 0.23.1
+ resolution: "@esbuild/android-x64@npm:0.23.1"
+ conditions: os=android & cpu=x64
+ languageName: node
+ linkType: hard
+
+"@esbuild/darwin-arm64@npm:0.23.1":
+ version: 0.23.1
+ resolution: "@esbuild/darwin-arm64@npm:0.23.1"
+ conditions: os=darwin & cpu=arm64
+ languageName: node
+ linkType: hard
+
+"@esbuild/darwin-x64@npm:0.23.1":
+ version: 0.23.1
+ resolution: "@esbuild/darwin-x64@npm:0.23.1"
+ conditions: os=darwin & cpu=x64
+ languageName: node
+ linkType: hard
+
+"@esbuild/freebsd-arm64@npm:0.23.1":
+ version: 0.23.1
+ resolution: "@esbuild/freebsd-arm64@npm:0.23.1"
+ conditions: os=freebsd & cpu=arm64
+ languageName: node
+ linkType: hard
+
+"@esbuild/freebsd-x64@npm:0.23.1":
+ version: 0.23.1
+ resolution: "@esbuild/freebsd-x64@npm:0.23.1"
+ conditions: os=freebsd & cpu=x64
+ languageName: node
+ linkType: hard
+
+"@esbuild/linux-arm64@npm:0.23.1":
+ version: 0.23.1
+ resolution: "@esbuild/linux-arm64@npm:0.23.1"
+ conditions: os=linux & cpu=arm64
+ languageName: node
+ linkType: hard
+
+"@esbuild/linux-arm@npm:0.23.1":
+ version: 0.23.1
+ resolution: "@esbuild/linux-arm@npm:0.23.1"
+ conditions: os=linux & cpu=arm
+ languageName: node
+ linkType: hard
+
+"@esbuild/linux-ia32@npm:0.23.1":
+ version: 0.23.1
+ resolution: "@esbuild/linux-ia32@npm:0.23.1"
+ conditions: os=linux & cpu=ia32
+ languageName: node
+ linkType: hard
+
+"@esbuild/linux-loong64@npm:0.23.1":
+ version: 0.23.1
+ resolution: "@esbuild/linux-loong64@npm:0.23.1"
+ conditions: os=linux & cpu=loong64
+ languageName: node
+ linkType: hard
+
+"@esbuild/linux-mips64el@npm:0.23.1":
+ version: 0.23.1
+ resolution: "@esbuild/linux-mips64el@npm:0.23.1"
+ conditions: os=linux & cpu=mips64el
+ languageName: node
+ linkType: hard
+
+"@esbuild/linux-ppc64@npm:0.23.1":
+ version: 0.23.1
+ resolution: "@esbuild/linux-ppc64@npm:0.23.1"
+ conditions: os=linux & cpu=ppc64
+ languageName: node
+ linkType: hard
+
+"@esbuild/linux-riscv64@npm:0.23.1":
+ version: 0.23.1
+ resolution: "@esbuild/linux-riscv64@npm:0.23.1"
+ conditions: os=linux & cpu=riscv64
+ languageName: node
+ linkType: hard
+
+"@esbuild/linux-s390x@npm:0.23.1":
+ version: 0.23.1
+ resolution: "@esbuild/linux-s390x@npm:0.23.1"
+ conditions: os=linux & cpu=s390x
+ languageName: node
+ linkType: hard
+
+"@esbuild/linux-x64@npm:0.23.1":
+ version: 0.23.1
+ resolution: "@esbuild/linux-x64@npm:0.23.1"
+ conditions: os=linux & cpu=x64
+ languageName: node
+ linkType: hard
+
+"@esbuild/netbsd-x64@npm:0.23.1":
+ version: 0.23.1
+ resolution: "@esbuild/netbsd-x64@npm:0.23.1"
+ conditions: os=netbsd & cpu=x64
+ languageName: node
+ linkType: hard
+
+"@esbuild/openbsd-arm64@npm:0.23.1":
+ version: 0.23.1
+ resolution: "@esbuild/openbsd-arm64@npm:0.23.1"
+ conditions: os=openbsd & cpu=arm64
+ languageName: node
+ linkType: hard
+
+"@esbuild/openbsd-x64@npm:0.23.1":
+ version: 0.23.1
+ resolution: "@esbuild/openbsd-x64@npm:0.23.1"
+ conditions: os=openbsd & cpu=x64
+ languageName: node
+ linkType: hard
+
+"@esbuild/sunos-x64@npm:0.23.1":
+ version: 0.23.1
+ resolution: "@esbuild/sunos-x64@npm:0.23.1"
+ conditions: os=sunos & cpu=x64
+ languageName: node
+ linkType: hard
+
+"@esbuild/win32-arm64@npm:0.23.1":
+ version: 0.23.1
+ resolution: "@esbuild/win32-arm64@npm:0.23.1"
+ conditions: os=win32 & cpu=arm64
+ languageName: node
+ linkType: hard
+
+"@esbuild/win32-ia32@npm:0.23.1":
+ version: 0.23.1
+ resolution: "@esbuild/win32-ia32@npm:0.23.1"
+ conditions: os=win32 & cpu=ia32
+ languageName: node
+ linkType: hard
+
+"@esbuild/win32-x64@npm:0.23.1":
+ version: 0.23.1
+ resolution: "@esbuild/win32-x64@npm:0.23.1"
+ conditions: os=win32 & cpu=x64
+ languageName: node
+ linkType: hard
+
"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0":
version: 4.4.0
resolution: "@eslint-community/eslint-utils@npm:4.4.0"
@@ -6225,6 +6393,89 @@ __metadata:
languageName: node
linkType: hard
+"esbuild@npm:~0.23.0":
+ version: 0.23.1
+ resolution: "esbuild@npm:0.23.1"
+ dependencies:
+ "@esbuild/aix-ppc64": 0.23.1
+ "@esbuild/android-arm": 0.23.1
+ "@esbuild/android-arm64": 0.23.1
+ "@esbuild/android-x64": 0.23.1
+ "@esbuild/darwin-arm64": 0.23.1
+ "@esbuild/darwin-x64": 0.23.1
+ "@esbuild/freebsd-arm64": 0.23.1
+ "@esbuild/freebsd-x64": 0.23.1
+ "@esbuild/linux-arm": 0.23.1
+ "@esbuild/linux-arm64": 0.23.1
+ "@esbuild/linux-ia32": 0.23.1
+ "@esbuild/linux-loong64": 0.23.1
+ "@esbuild/linux-mips64el": 0.23.1
+ "@esbuild/linux-ppc64": 0.23.1
+ "@esbuild/linux-riscv64": 0.23.1
+ "@esbuild/linux-s390x": 0.23.1
+ "@esbuild/linux-x64": 0.23.1
+ "@esbuild/netbsd-x64": 0.23.1
+ "@esbuild/openbsd-arm64": 0.23.1
+ "@esbuild/openbsd-x64": 0.23.1
+ "@esbuild/sunos-x64": 0.23.1
+ "@esbuild/win32-arm64": 0.23.1
+ "@esbuild/win32-ia32": 0.23.1
+ "@esbuild/win32-x64": 0.23.1
+ dependenciesMeta:
+ "@esbuild/aix-ppc64":
+ optional: true
+ "@esbuild/android-arm":
+ optional: true
+ "@esbuild/android-arm64":
+ optional: true
+ "@esbuild/android-x64":
+ optional: true
+ "@esbuild/darwin-arm64":
+ optional: true
+ "@esbuild/darwin-x64":
+ optional: true
+ "@esbuild/freebsd-arm64":
+ optional: true
+ "@esbuild/freebsd-x64":
+ optional: true
+ "@esbuild/linux-arm":
+ optional: true
+ "@esbuild/linux-arm64":
+ optional: true
+ "@esbuild/linux-ia32":
+ optional: true
+ "@esbuild/linux-loong64":
+ optional: true
+ "@esbuild/linux-mips64el":
+ optional: true
+ "@esbuild/linux-ppc64":
+ optional: true
+ "@esbuild/linux-riscv64":
+ optional: true
+ "@esbuild/linux-s390x":
+ optional: true
+ "@esbuild/linux-x64":
+ optional: true
+ "@esbuild/netbsd-x64":
+ optional: true
+ "@esbuild/openbsd-arm64":
+ optional: true
+ "@esbuild/openbsd-x64":
+ optional: true
+ "@esbuild/sunos-x64":
+ optional: true
+ "@esbuild/win32-arm64":
+ optional: true
+ "@esbuild/win32-ia32":
+ optional: true
+ "@esbuild/win32-x64":
+ optional: true
+ bin:
+ esbuild: bin/esbuild
+ checksum: 0413c3b9257327fb598427688b7186ea335bf1693746fe5713cc93c95854d6388b8ed4ad643fddf5b5ace093f7dcd9038dd58e087bf2da1f04dfb4c5571660af
+ languageName: node
+ linkType: hard
+
"escalade@npm:^3.1.1, escalade@npm:^3.1.2":
version: 3.2.0
resolution: "escalade@npm:3.2.0"
@@ -7029,7 +7280,7 @@ __metadata:
languageName: node
linkType: hard
-"fsevents@npm:^2.3.2":
+"fsevents@npm:^2.3.2, fsevents@npm:~2.3.3":
version: 2.3.3
resolution: "fsevents@npm:2.3.3"
dependencies:
@@ -7039,7 +7290,7 @@ __metadata:
languageName: node
linkType: hard
-"fsevents@patch:fsevents@^2.3.2#~builtin":
+"fsevents@patch:fsevents@^2.3.2#~builtin, fsevents@patch:fsevents@~2.3.3#~builtin":
version: 2.3.3
resolution: "fsevents@patch:fsevents@npm%3A2.3.3#~builtin::version=2.3.3&hash=df0bf1"
dependencies:
@@ -7126,6 +7377,15 @@ __metadata:
languageName: node
linkType: hard
+"get-tsconfig@npm:^4.7.5":
+ version: 4.8.1
+ resolution: "get-tsconfig@npm:4.8.1"
+ dependencies:
+ resolve-pkg-maps: ^1.0.0
+ checksum: 12df01672e691d2ff6db8cf7fed1ddfef90ed94a5f3d822c63c147a26742026d582acd86afcd6f65db67d809625d17dd7f9d34f4d3f38f69bc2f48e19b2bdd5b
+ languageName: node
+ linkType: hard
+
"get-uri@npm:^6.0.1":
version: 6.0.3
resolution: "get-uri@npm:6.0.3"
@@ -9534,7 +9794,7 @@ __metadata:
react-native-tab-view: ^3.5.2
react-native-url-polyfill: ^2.0.0
react-test-renderer: 18.3.1
- simple-js-task-queue: ^0.2.2
+ taskon: ^0.3.0
text-encoding-polyfill: ^0.6.7
typescript: 5.0.4
util: ^0.12.5
@@ -11338,6 +11598,13 @@ __metadata:
languageName: node
linkType: hard
+"resolve-pkg-maps@npm:^1.0.0":
+ version: 1.0.0
+ resolution: "resolve-pkg-maps@npm:1.0.0"
+ checksum: 1012afc566b3fdb190a6309cc37ef3b2dcc35dff5fa6683a9d00cd25c3247edfbc4691b91078c97adc82a29b77a2660c30d791d65dab4fc78bfc473f60289977
+ languageName: node
+ linkType: hard
+
"resolve.exports@npm:^2.0.0":
version: 2.0.2
resolution: "resolve.exports@npm:2.0.2"
@@ -11727,13 +11994,6 @@ __metadata:
languageName: node
linkType: hard
-"simple-js-task-queue@npm:^0.2.2":
- version: 0.2.2
- resolution: "simple-js-task-queue@npm:0.2.2"
- checksum: bd56fcbea3ecbc081238f02c8892ebbbb692468ecdac9f0d229e96dea85db308889adf1ef128346a3acc8585913813f2415eed0aa4336be6467a4995cf42f39e
- languageName: node
- linkType: hard
-
"simple-plist@npm:^1.1.0":
version: 1.4.0
resolution: "simple-plist@npm:1.4.0"
@@ -12209,6 +12469,18 @@ __metadata:
languageName: node
linkType: hard
+"taskon@npm:^0.3.0":
+ version: 0.3.0
+ resolution: "taskon@npm:0.3.0"
+ dependencies:
+ tsx: ^4.19.2
+ dependenciesMeta:
+ tsx:
+ optional: true
+ checksum: 7632189a17a8f51a2528e9ed580f9b6892d36ae266ca8e1d81c4779db83252a177f561d37f324d4806027e7eca98a8ad43dde2c5e5e6355a73e9013d4ddf3fc3
+ languageName: node
+ linkType: hard
+
"temp@npm:^0.8.4":
version: 0.8.4
resolution: "temp@npm:0.8.4"
@@ -12368,6 +12640,22 @@ __metadata:
languageName: node
linkType: hard
+"tsx@npm:^4.19.2":
+ version: 4.19.2
+ resolution: "tsx@npm:4.19.2"
+ dependencies:
+ esbuild: ~0.23.0
+ fsevents: ~2.3.3
+ get-tsconfig: ^4.7.5
+ dependenciesMeta:
+ fsevents:
+ optional: true
+ bin:
+ tsx: dist/cli.mjs
+ checksum: 7f9f1b338a73297725a9217cedaaad862f7c81d5264093c74b98a71491ad5413b11248d604c0e650f4f7da6f365249f1426fdb58a1325ab9e15448156b1edff6
+ languageName: node
+ linkType: hard
+
"type-check@npm:^0.4.0, type-check@npm:~0.4.0":
version: 0.4.0
resolution: "type-check@npm:0.4.0"