diff --git a/android/app/build.gradle b/android/app/build.gradle
index a01b10a8..887eff87 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -96,8 +96,8 @@ android {
applicationId "com.minibits_wallet"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
- versionCode 58013
- versionName "0.1.9"
+ versionCode 58015
+ versionName "0.1.10"
}
splits {
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 1832f71c..3bf37591 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -2,6 +2,7 @@
+
}
+// Process nwc commands as long running headless tasks
+notifee.registerForegroundService(async (notification) => {
+ return new Promise(async (resolve) => {
+ log.trace('[registerForegroundService] Foreground service running for task:', notification)
+
+ try {
+ const {nwcStore} = rootStoreInstance
+ if(nwcStore.all.length === 0) {
+ await setupRootStore(rootStoreInstance)
+ }
+
+ nwcStore.handleNwcRequestFromNotification(notification.data).catch((e) => {
+ log.error('[registerForegroundService]', e.message)
+ })
+
+ } catch(e) {
+ log.error('[registerForegroundService]', e.message)
+ }
+ })
+})
// Setup notification listeners and handlers
messaging().onMessage(NotificationService.onForegroundNotification)
diff --git a/package.json b/package.json
index b83a8526..f1cb408c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "minibits_wallet",
- "version": "0.1.9-beta.39",
+ "version": "0.1.10-beta.2",
"private": true,
"scripts": {
"android:clean": "cd android && ./gradlew clean",
@@ -29,7 +29,7 @@
"@gandlaf21/bc-ur": "^1.1.12",
"@gandlaf21/bolt11-decode": "^3.1.1",
"@gocodingnow/rn-size-matters": "^0.0.4",
- "@notifee/react-native": "^9.0.2",
+ "@notifee/react-native": "^9.1.3",
"@react-native-clipboard/clipboard": "^1.14.1",
"@react-native-community/netinfo": "^11.4.0",
"@react-native-firebase/app": "^20.5.0",
diff --git a/src/App.tsx b/src/App.tsx
index 008aa8fd..4d426b9e 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -9,33 +9,30 @@ import {
CODEPUSH_PRODUCTION_DEPLOYMENT_KEY,
} from '@env'
import codePush from 'react-native-code-push'
-import messaging, { isAutoInitEnabled } from '@react-native-firebase/messaging'
import FlashMessage from "react-native-flash-message"
-import {
- initialWindowMetrics,
+import {
SafeAreaProvider,
} from 'react-native-safe-area-context'
import {
setSizeMattersBaseHeight,
setSizeMattersBaseWidth
} from '@gocodingnow/rn-size-matters'
+import RNExitApp from 'react-native-exit-app'
import {AppNavigator} from './navigation'
import {useInitialRootStore, useStores} from './models'
import {Database, KeyChain} from './services'
+import { initNotifications } from './services/notificationService'
import {ErrorBoundary} from './screens/ErrorScreen/ErrorBoundary'
import Config from './config'
import {log} from './services'
import {Env} from './utils/envtypes'
import AppError from './utils/AppError'
import { Image, View } from 'react-native'
-import { Text } from './components'
-import RNExitApp from 'react-native-exit-app'
+/* Init push notifications */
+initNotifications()
-// RN 0.73 screen rendering issue
-//import { enableFreeze, enableScreens } from 'react-native-screens';
-// enableScreens(false)
-
+/* Set default size ratio for styling */
setSizeMattersBaseWidth(375)
setSizeMattersBaseHeight(812)
@@ -99,20 +96,6 @@ function App(props: AppProps) {
}
- // FCM push notifications - set or refresh device token on app start
- await messaging().registerDeviceForRemoteMessages()
- const deviceToken = await messaging().getToken()
-
- log.debug('[useInitialRootStore]', {deviceToken})
-
- // Make sure profile has already been created (i.e. this is not first run)
- if(walletProfileStore.pubkey && deviceToken) {
- // if device token changed, update the server
- if(deviceToken !== walletProfileStore.device) {
- await walletProfileStore.setDevice(deviceToken)
- }
- }
-
// Set initial websocket to close as it might have remained open on last app close
relaysStore.resetStatuses()
log.trace('[useInitialRootStore]', 'App is ready to render')
diff --git a/src/models/NwcStore.ts b/src/models/NwcStore.ts
index ac86bd65..ed4f0884 100644
--- a/src/models/NwcStore.ts
+++ b/src/models/NwcStore.ts
@@ -45,7 +45,7 @@ type NwcError = {
}
}
-type NwcRequest = {
+export type NwcRequest = {
method: string,
params: any
}
@@ -68,7 +68,7 @@ type NwcTransaction = {
expires_at: number | null
}
-const nwcPngUrl = 'https://1044827509-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0JQfRPMJ4uO7z9wmnAOK%2Fuploads%2FVO76qdgzHHzWSDsHQXdu%2FGroup%201000001143%20(1).png?alt=media&token=0fdb70b7-bb19-4bed-a752-a0560585c2f4&width=512&dpr=1&quality=100&sign=57c41699&sv=1'
+export const nwcPngUrl = 'https://1044827509-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0JQfRPMJ4uO7z9wmnAOK%2Fuploads%2FVO76qdgzHHzWSDsHQXdu%2FGroup%201000001143%20(1).png?alt=media&token=0fdb70b7-bb19-4bed-a752-a0560585c2f4&width=512&dpr=1&quality=100&sign=57c41699&sv=1'
const getConnectionRelays = function () {
const minibitsRelays = NostrClient.getMinibitsRelays()
@@ -251,6 +251,8 @@ export const NwcConnectionModel = types.model('NwcConnection', {
// notify completed payment
const enabled = yield NotificationService.areNotificationsEnabled()
if(enabled) {
+ yield NotificationService.stopForegroundService()
+
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`,
@@ -307,9 +309,11 @@ export const NwcConnectionModel = types.model('NwcConnection', {
// 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.amount} SATS has been created.`,
+ `Invoice for ${result.paymentRequest.invoicedAmount} SATS has been created.`,
nwcPngUrl
)
}
@@ -514,38 +518,23 @@ export const NwcConnectionModel = types.model('NwcConnection', {
error: { code: 'QUOTA_EXCEEDED', message}
} as NwcError
}
+
+ const meltQuote: MeltQuoteResponse = yield walletStore.createLightningMeltQuote(
+ mintBalance.mintUrl,
+ 'sat',
+ encoded,
+ )
- if(isMinibitsMintSelected) {
- // Process the payment partly on the server side to avoid android background processing failures
- WalletTask.nwcTransfer(
- mintBalance,
- amountToPay,
- feeReserve,
- 'sat',
- description || '',
- invoiceExpiry as Date,
- encoded,
- requestEvent
- )
- } else {
- // Full process on device for other mints so that minibits server does not touch foreign ecash
- const meltQuote: MeltQuoteResponse = yield walletStore.createLightningMeltQuote(
- mintBalance.mintUrl,
- 'sat',
- encoded,
- )
-
- WalletTask.transfer(
- mintBalance,
- amountToPay,
- 'sat',
- meltQuote,
- description || '',
- invoiceExpiry as Date,
- encoded,
- requestEvent
- )
- }
+ WalletTask.transfer(
+ mintBalance,
+ amountToPay,
+ 'sat',
+ meltQuote,
+ description || '',
+ invoiceExpiry as Date,
+ encoded,
+ requestEvent
+ )
} catch (e: any) {
log.error(`[NwcConnection.handlePayInvoice] ${e.message}`)
@@ -558,11 +547,15 @@ export const NwcConnectionModel = types.model('NwcConnection', {
})
}))
.actions(self => ({
- handleRequest: flow(function* handleRequest(requestEvent: NostrEvent) {
- // decrypt with main wallet privKey. Event.pubkey = connectionPubkey
- const decryptedContent = yield NostrClient.decryptNip04(requestEvent.pubkey, requestEvent.content)
-
- const nwcRequest: NwcRequest = JSON.parse(decryptedContent)
+ handleRequest: flow(function* handleRequest(requestEvent: NostrEvent, decryptedNwcRequest?: NwcRequest) {
+ let nwcRequest: NwcRequest
+ if(!decryptedNwcRequest) {
+ const decryptedContent = yield NostrClient.decryptNip04(requestEvent.pubkey, requestEvent.content)
+ nwcRequest = JSON.parse(decryptedContent)
+ } else {
+ nwcRequest = decryptedNwcRequest
+ }
+
let nwcResponse: NwcResponse | NwcError
log.trace('[Nwc.handleRequest] request event', {requestEvent})
@@ -581,8 +574,9 @@ export const NwcConnectionModel = types.model('NwcConnection', {
case 'make_invoice':
nwcResponse = yield self.handleMakeInvoice(nwcRequest, requestEvent) as Promise
- // no early error, topup initiated, exit and create response in topup result event handler
- if(!nwcResponse) {
+ // no early error, topup initiated, register topup result event handler and exit
+ if(!nwcResponse) {
+ EventEmitter.on('ev_topupTask_result', self.handleTopupTaskResult)
return
}
break
@@ -593,8 +587,9 @@ export const NwcConnectionModel = types.model('NwcConnection', {
// only early errors are immediately returned
nwcResponse = yield self.handlePayInvoice(nwcRequest, requestEvent) as Promise
- // no early error, transfer initiated, exit and create response in transfer result event handler
- if(!nwcResponse) {
+ // no early error, transfer initiated, register transfer result event handler and exit
+ if(!nwcResponse) {
+ EventEmitter.on('ev_transferTask_result', self.handleTransferTaskResult)
return
}
break
@@ -648,34 +643,6 @@ export const NwcStoreModel = types
}
}))
.actions(self => ({
- handleTransferResult: flow(function* handleTransferResult(result: TransactionTaskResult) {
- // find connection the nwc request is sent to
- const targetConnection = self.nwcConnections.find(c =>
- c.connectionPubkey === result.nwcEvent?.pubkey
- )
-
- if(!targetConnection) {
- log.error('Missing connection matching event pubkey', {pubkey: result.nwcEvent?.pubkey})
- return
- }
-
- yield targetConnection.handleTransferTaskResult(result)
-
- }),
- handleTopupResult: flow(function* handleTopupResult(result: TransactionTaskResult) {
- // find connection the nwc request is sent to
- const targetConnection = self.nwcConnections.find(c =>
- c.connectionPubkey === result.nwcEvent?.pubkey
- )
-
- if(!targetConnection) {
- log.error('Missing connection matching event pubkey', {pubkey: result.nwcEvent?.pubkey})
- return
- }
-
- yield targetConnection.handleTopupTaskResult(result)
-
- }),
resetDailyLimits () {
for (const c of self.nwcConnections) {
if(!isSameDay(c.currentDay, new Date())) {
@@ -794,16 +761,12 @@ export const NwcStoreModel = types
}
})
- EventEmitter.on('ev_transferTask_result', self.handleTransferResult)
- EventEmitter.on('ev_nwcTransferTask_result', self.handleTransferResult)
- EventEmitter.on('ev_topupTask_result', self.handleTopupResult)
-
} catch (e: any) {
log.error(e.name, e.message)
return
}
},
- handleNwcRequestFromNotification: flow(function* handleNwcRequestFromNotification(event: NostrEvent) {
+ handleNwcRequestFromNotification: flow(function* handleNwcRequestFromNotification(event: NostrEvent, decryptedNwcRequest: NwcRequest) {
// find connection the nwc request is sent to
const targetConnection = self.nwcConnections.find(c =>
c.connectionPubkey === event.pubkey
@@ -814,12 +777,7 @@ export const NwcStoreModel = types
return
}
- yield targetConnection.handleRequest(event)
-
- EventEmitter.on('ev_transferTask_result', self.handleTransferResult)
- EventEmitter.on('ev_nwcTransferTask_result', self.handleTransferResult)
- EventEmitter.on('ev_topupTask_result', self.handleTopupResult)
-
+ yield targetConnection.handleRequest(event, decryptedNwcRequest)
})
}))
.views(self => ({
diff --git a/src/services/notificationService.ts b/src/services/notificationService.ts
index d3139ee0..3378a02b 100644
--- a/src/services/notificationService.ts
+++ b/src/services/notificationService.ts
@@ -1,16 +1,16 @@
-import notifee, { AndroidImportance, AuthorizationStatus } from '@notifee/react-native'
-import { colors } from '../theme';
-import { log } from './logService';
+import notifee, { AndroidColor, AndroidImportance, AuthorizationStatus } from '@notifee/react-native'
+import { colors } from '../theme'
+import { log } from './logService'
import {
MINIBIT_SERVER_NOSTR_PUBKEY,
} from '@env'
-import { FirebaseMessagingTypes } from '@react-native-firebase/messaging'
+import messaging, { FirebaseMessagingTypes } from '@react-native-firebase/messaging'
import { MintUnit, formatCurrency, getCurrency } from './wallet/currency'
import { NostrClient, NostrEvent, NostrProfile } from './nostrService'
import AppError, { Err } from '../utils/AppError'
import { Platform } from 'react-native'
-import { RootStoreModel, rootStoreInstance, setupRootStore } from '../models';
-import { ProofsStoreModel } from '../models/ProofsStore';
+import { rootStoreInstance, setupRootStore } from '../models'
+import { NwcRequest, nwcPngUrl } from '../models/NwcStore';
export type NotifyReceiveToLnurlData = {
type: 'NotifyReceiveToLnurlData',
@@ -29,6 +29,60 @@ export type NotifyNwcRequestData = {
}
}
+const DEFAULT_CHANNEL_ID = 'default'
+const DEFAULT_CHANNEL_NAME = 'Minibits notifications'
+
+const NWC_CHANNEL_ID = 'nwcDefault';
+const NWC_CHANNEL_NAME = 'Minibits NWC payment'
+
+export const initNotifications = async () => {
+ let enabled = await areNotificationsEnabled()
+ log.trace(`[initNotifications] Push notifications are ${enabled ? 'enabled' : 'disabled'}.`)
+
+ if(!enabled) return
+ const {walletProfileStore} = rootStoreInstance
+
+ await messaging().registerDeviceForRemoteMessages()
+ const deviceToken = await messaging().getToken()
+ log.trace(`[initNotifications] Device token: ${deviceToken}`)
+
+ if(walletProfileStore.pubkey && deviceToken) {
+ // if device token changed, update the server
+ if(deviceToken !== walletProfileStore.device) {
+ await walletProfileStore.setDevice(deviceToken)
+ }
+ }
+
+ messaging().onTokenRefresh(token => {
+ if(token !== walletProfileStore.device) {
+ walletProfileStore.setDevice(token)
+ }
+ })
+
+ notifee.isChannelCreated(DEFAULT_CHANNEL_ID).then(isCreated => {
+ if (!isCreated) {
+ notifee.createChannel({
+ id: DEFAULT_CHANNEL_ID,
+ name: DEFAULT_CHANNEL_NAME,
+ vibration: true,
+ importance: AndroidImportance.HIGH,
+ })
+ }
+ })
+
+ notifee.isChannelCreated(NWC_CHANNEL_ID).then(isCreated => {
+ if (!isCreated) {
+ notifee.createChannel({
+ id: NWC_CHANNEL_ID,
+ name: NWC_CHANNEL_NAME,
+ sound: 'default',
+ })
+ }
+ })
+
+
+}
+
// Remote notification receive handler
const onForegroundNotification = async function(remoteMessage: FirebaseMessagingTypes.RemoteMessage) {
log.trace('[onForegroundNotification]', {remoteMessage})
@@ -92,15 +146,37 @@ const _receiveToLnurlHandler = async function(remoteData: NotifyReceiveToLnurlDa
const _nwcRequestHandler = async function(remoteData: NotifyNwcRequestData) {
const {requestEvent} = remoteData.data
const {nwcStore} = rootStoreInstance
-
- // If app is killed, state is not loaded
+
if(nwcStore.all.length === 0) {
await setupRootStore(rootStoreInstance)
}
- log.trace('[_nwcRequestHandler]', {connections: nwcStore.all})
-
- await nwcStore.handleNwcRequestFromNotification(requestEvent)
+ const decryptedContent = await NostrClient.decryptNip04(requestEvent.pubkey, requestEvent.content)
+ const nwcRequest: NwcRequest = JSON.parse(decryptedContent)
+
+ if(nwcRequest.method === 'pay_invoice' || nwcRequest.method === 'make_invoice') {
+
+ log.trace('[_nwcRequestHandler] Starting foreground service')
+
+ const {tags, ...cleanedRequestEvent} = requestEvent
+
+ await notifee.displayNotification({
+ title: NWC_CHANNEL_NAME,
+ body: 'Nostr wallet command is being processed.',
+ android: {
+ channelId: NWC_CHANNEL_ID,
+ asForegroundService: true,
+ largeIcon: nwcPngUrl,
+ importance: AndroidImportance.HIGH,
+ progress: {
+ indeterminate: true,
+ },
+ },
+ data: cleanedRequestEvent, // Pass the task data to the foreground service
+ })
+ } else {
+ await nwcStore.handleNwcRequestFromNotification(requestEvent, nwcRequest)
+ }
}
const _getRemoteData = async function(remoteMessage: FirebaseMessagingTypes.RemoteMessage) {
@@ -130,21 +206,13 @@ const createLocalNotification = async function (title: string, body: string, lar
await notifee.requestPermission()
}
- // Create a channel (required for Android)
- const channelId = await notifee.createChannel({
- id: 'default',
- name: 'Minibits notifications',
- vibration: true,
- importance: AndroidImportance.HIGH,
- })
-
// Display a notification
if(largeIcon) {
const notificationId = await notifee.displayNotification({
title,
body,
android: {
- channelId,
+ channelId: DEFAULT_CHANNEL_ID,
color: colors.palette.success200,
largeIcon,
pressAction: {
@@ -159,7 +227,7 @@ const createLocalNotification = async function (title: string, body: string, lar
title,
body,
android: {
- channelId,
+ channelId: DEFAULT_CHANNEL_ID,
color: colors.palette.success200,
pressAction: {
id: 'default',
@@ -213,11 +281,16 @@ const areNotificationsEnabled = async function (): Promise {
return false
}
+const stopForegroundService = async function (): Promise {
+ await notifee.stopForegroundService()
+}
+
export const NotificationService = {
- onForegroundNotification,
- onBackgroundNotification,
createLocalNotification,
+ onBackgroundNotification,
+ onForegroundNotification,
// updateLocalNotification,
// cancelNotification,
- areNotificationsEnabled
+ areNotificationsEnabled,
+ stopForegroundService
}
\ No newline at end of file
diff --git a/src/services/wallet/nwcTransferTask.ts b/src/services/wallet/nwcTransferTask.ts
deleted file mode 100644
index 37dcbefb..00000000
--- a/src/services/wallet/nwcTransferTask.ts
+++ /dev/null
@@ -1,393 +0,0 @@
-import {CashuUtils, ProofV3, TokenEntryV3} from '../cashu/cashuUtils'
-import AppError, {Err} from '../../utils/AppError'
-import {MeltQuoteResponse, MeltQuoteState} from '@cashu/cashu-ts'
-import {rootStoreInstance} from '../../models'
-import { TransactionTaskResult, WalletTask } from '../walletService'
-import { MintBalance, MintProofsCounter } from '../../models/Mint'
-import { Proof } from '../../models/Proof'
-import { Transaction, TransactionData, TransactionStatus, TransactionType } from '../../models/Transaction'
-import { log } from '../logService'
-import { WalletUtils } from './utils'
-import {isBefore} from 'date-fns'
-import { MintUnit, formatCurrency, getCurrency } from './currency'
-import { NostrEvent } from '../nostrService'
-import { MinibitsClient } from '../minibitsService'
-import { getSnapshot } from 'mobx-state-tree'
-import { MINIBITS_MINT_URL } from '@env'
-import { receiveSync } from './receiveTask'
-
-const {
- transactionsStore,
- mintsStore,
- proofsStore,
- walletStore
-} = rootStoreInstance
-
-const NWC_TRANSFER = 'nwcTransferTask'
-
-export const nwcTransferTask = async function (
- mintBalanceToTransferFrom: MintBalance,
- amountToTransfer: number,
- feeReserve: number,
- unit: MintUnit,
- memo: string,
- invoiceExpiry: Date,
- encodedInvoice: string,
- nwcEvent: NostrEvent
-) : Promise {
-
- const mintUrl = mintBalanceToTransferFrom.mintUrl
- const mintInstance = mintsStore.findByUrl(mintUrl)
-
- if(mintUrl !== MINIBITS_MINT_URL) {
- throw new AppError(
- Err.VALIDATION_ERROR,
- 'Payment of NWC invoice on the Minibits server is supported only for ecash issued by Minibits mint.',
- {mintBalanceToTransferFrom}
- )
- }
-
- // TODO refresh - balance might be outdated if it waits in queue before other txs
- log.debug('[nwcTransfer]', 'mintBalanceToTransferFrom', {mintBalanceToTransferFrom})
- log.debug('[nwcTransfer]', 'amountToTransfer', {amountToTransfer, feeReserve})
-
- // create draft transaction
- const transactionData: TransactionData[] = [
- {
- status: TransactionStatus.DRAFT,
- mintBalanceToTransferFrom,
- amountToTransfer,
- feeReserve,
- unit,
- encodedInvoice,
- isNwc: nwcEvent ? true : false,
- createdAt: new Date(),
- }
- ]
-
- let transaction: Transaction | undefined = undefined
- let meltQuote: MeltQuoteResponse | undefined = undefined
- let proofsToMeltFrom: Proof[] = []
- let proofsToMeltFromAmount: number = 0
- let meltFeeReserve: number = 0
-
- try {
- const newTransaction = {
- type: TransactionType.NWC_TRANSFER,
- amount: amountToTransfer,
- fee: feeReserve,
- unit,
- data: JSON.stringify(transactionData),
- memo,
- mint: mintBalanceToTransferFrom.mintUrl,
- status: TransactionStatus.DRAFT,
- }
-
- // store tx in db and in the model
- transaction = await transactionsStore.addTransaction(newTransaction)
- const transactionId = transaction.id
-
- if (amountToTransfer + feeReserve > mintBalanceToTransferFrom.balances[unit]!) {
- throw new AppError(
- Err.VALIDATION_ERROR,
- 'Mint balance is insufficient to cover the amount to transfer with the expected Lightning fees.',
- {transactionId}
- )
- }
-
- if(isBefore(invoiceExpiry, new Date())) {
- throw new AppError(
- Err.VALIDATION_ERROR,
- 'This invoice has already expired and can not be paid.',
- {invoiceExpiry, transactionId}
- )
- }
-
- if (!mintInstance) {
- throw new AppError(
- Err.VALIDATION_ERROR,
- 'Could not find mint', {mintUrl, transactionId}
- )
- }
-
- // calculate fees charged by mint for melt transaction to prepare enough proofs
- const proofsFromMint = proofsStore.getByMint(mintUrl, {isPending: false, unit})
- const totalAmountFromMint = CashuUtils.getProofsAmount(proofsFromMint)
-
- proofsToMeltFrom = CashuUtils.getProofsToSend(
- amountToTransfer + feeReserve,
- proofsFromMint
- )
-
- proofsToMeltFromAmount = CashuUtils.getProofsAmount(proofsToMeltFrom)
- meltFeeReserve = mintInstance.getMintFeeReserve(proofsToMeltFrom)
- const amountWithFees = amountToTransfer + feeReserve + meltFeeReserve
-
- if (totalAmountFromMint < amountWithFees) {
- throw new AppError(
- Err.VALIDATION_ERROR,
- 'There is not enough funds to send this amount.',
- {totalAmountFromMint, amountWithFees, transactionId, caller: 'nwcTransferTask'},
- )
- }
-
- // exact match or min number of proofs that matches the amount
- if(meltFeeReserve > 0) {
- proofsToMeltFrom = CashuUtils.getProofsToSend(
- amountWithFees,
- proofsFromMint
- )
-
- proofsToMeltFromAmount = CashuUtils.getProofsAmount(proofsToMeltFrom)
- }
-
- proofsStore.removeProofs(proofsToMeltFrom)
- WalletUtils.addCashuProofs(
- mintUrl,
- proofsToMeltFrom,
- {
- unit,
- transactionId: transaction.id,
- isPending: true
- }
- )
-
- log.trace('[nwcTransfer]', 'Prepared proofsToMeltFrom proofs', {
- proofsToMeltFromAmount,
- transactionId,
- unit,
- })
-
- // Update transaction status
- transactionData.push({
- status: TransactionStatus.PREPARED,
- proofsToMeltFromAmount,
- lightningFeeReserve: feeReserve,
- meltFeeReserve,
- createdAt: new Date(),
- })
-
- transaction.setStatus(
- TransactionStatus.PREPARED,
- JSON.stringify(transactionData),
- )
-
- const cleanedproofsToMeltFrom = proofsToMeltFrom.map(proof => {
- const {mintUrl, unit, tId, ...rest} = getSnapshot(proof)
- return rest
- })
-
- const inputTokenEntry: TokenEntryV3 = {
- mint: mintUrl,
- proofs: cleanedproofsToMeltFrom,
- }
-
- const tokenToPayFrom = {
- token: [inputTokenEntry],
- unit
- }
-
- const inputToken = CashuUtils.encodeToken(tokenToPayFrom)
-
- transaction.setInputToken(inputToken)
-
- const result = await MinibitsClient.payNwcTransfer(
- encodedInvoice,
- {
- token: [inputTokenEntry],
- unit
- }
- )
-
- meltQuote = result.meltQuote
- const tokenToReturn = result.tokenToReturn // this is server wallet ecash, needs swap
-
- if (meltQuote.state === MeltQuoteState.PAID) {
-
- log.debug('[nwcTransfer] Invoice PAID', {
- transactionId
- })
-
- // Save preimage asap
- if(meltQuote.payment_preimage) {
- transaction.setProof(meltQuote.payment_preimage)
- }
-
- // If nothing was returned, all reserves were spent on fees
- let totalFeePaid = proofsToMeltFromAmount - amountToTransfer
- let lightningFeePaid = totalFeePaid - meltFeeReserve
- let meltFeePaid = meltFeeReserve
- let swapFeePaid = 0
- let returnedAmount
-
- if (tokenToReturn) {
- // Save returned token in case receive fails
- const outputTokenBeforeSwap = CashuUtils.encodeToken(tokenToReturn)
- transaction.setOutputToken(outputTokenBeforeSwap)
-
- // Swap returned proofs as they were issued to the server wallet thus are not linked to the wallet seed
- const receiveResult = await receiveSync(
- mintUrl,
- tokenToReturn,
- memo,
- transactionId
- )
-
- swapFeePaid = receiveResult.swapFeePaid
- totalFeePaid = totalFeePaid - receiveResult.receivedAmount
- lightningFeePaid = totalFeePaid - meltFeeReserve - swapFeePaid
- returnedAmount = receiveResult.receivedAmount
-
- // re-save with swapped token
- transaction.setOutputToken(receiveResult.outputToken)
- }
-
- // spend pending proofsToMeltFrom reserved for transaction
- // only after rhe receive passed, in case of exception they remain pending
- // and are sorted out by syncStateWithMintSync method
- proofsStore.removeProofs(proofsToMeltFrom as Proof[], true, false)
-
- // Save final fee in db
- if(totalFeePaid !== transaction.fee) {
- transaction.setFee(totalFeePaid)
- }
-
- // Update transaction status
- transactionData.push({
- status: TransactionStatus.COMPLETED,
- lightningFeeReserve: meltQuote.fee_reserve,
- lightningFeePaid,
- meltFeePaid,
- swapFeePaid,
- returnedAmount,
- preimage: meltQuote.payment_preimage,
- createdAt: new Date(),
- })
-
- transaction.setStatus(
- TransactionStatus.COMPLETED,
- JSON.stringify(transactionData),
- )
-
- const balanceAfter = proofsStore.getUnitBalance(unit)?.unitBalance!
- transaction.setBalanceAfter(balanceAfter)
-
- return {
- taskFunction: NWC_TRANSFER,
- 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}.`,
- lightningFeePaid,
- meltFeePaid,
- swapFeePaid,
- totalFeePaid,
- meltQuote,
- preimage: meltQuote.payment_preimage,
- nwcEvent
- } as TransactionTaskResult
-
- } else if(meltQuote.state === MeltQuoteState.PENDING) {
-
- log.debug('[transfer] Invoice PENDING', {
- meltQuote,
- transactionId
- })
-
- return {
- taskFunction: NWC_TRANSFER,
- mintUrl,
- transaction,
- message: `Lightning payment did not complete in time. Your ecash will remain pending until the payment completes or fails.`,
- meltQuote,
- nwcEvent
- } as TransactionTaskResult
-
- } else {
- // throw so that proper state of proofs is synced inside the catch block
- throw new AppError(Err.MINT_ERROR, 'Lightning payment has not been paid.', {
- state: meltQuote.state,
- transactionId
- })
- }
-
- } catch (e: any) {
- if (transaction) {
-
- let message = e.message
-
- if (proofsToMeltFrom.length > 0) {
- // checks and syncs with the mint the real status of the proofs involved in transaction
- await WalletTask.syncStateWithMintSync(
- {
- proofsToSync: proofsToMeltFrom,
- mintUrl,
- isPending: true
- }
- )
-
- // force refresh just in case above method did not update the model?
- const refreshed = transactionsStore.findById(transaction.id)
-
- if(refreshed?.status === TransactionStatus.PENDING) {
- log.warn('[transfer]', 'proofsToPay from transfer with error are pending by mint', {
- proofsToMeltFromAmount,
- unit,
- transactionId: transaction.id
- })
-
- message = 'Lightning payment did not complete in time. Your ecash will remain pending until the payment completes or fails.'
- // needs to be returned as error
-
- } else if(refreshed?.status === TransactionStatus.COMPLETED) {
- // Likely receiving of change failed due to wallet error. We keep completed as status.
- log.error('[nwcTransfer]', 'NWC Transfer throwed error but the payment suceeded', {
- error: e.message,
- proofsToMeltFromAmount,
- unit,
- transactionId: transaction.id
- })
-
- if(meltQuote?.payment_preimage) {
- refreshed.setProof(meltQuote?.payment_preimage)
- }
-
- const message = `Lightning invoice has been successfully paid, however some error occured: ${e.message}`
-
- return {
- taskFunction: NWC_TRANSFER,
- mintUrl,
- transaction,
- message,
- meltQuote,
- preimage: meltQuote?.payment_preimage,
- nwcEvent
- } as TransactionTaskResult
-
- }
- }
-
- transactionData.push({
- status: TransactionStatus.ERROR,
- error: WalletUtils.formatError(e),
- createdAt: new Date()
- })
-
- transaction.setStatus(
- TransactionStatus.ERROR,
- JSON.stringify(transactionData),
- )
- }
-
- log.error('[transfer]', e.name, e.message, e.params)
-
- return {
- taskFunction: NWC_TRANSFER,
- mintUrl,
- transaction,
- message: e.message,
- error: WalletUtils.formatError(e),
- meltQuote,
- nwcEvent
- } as TransactionTaskResult
- }
-}
\ No newline at end of file
diff --git a/src/services/walletService.ts b/src/services/walletService.ts
index 9d4f1461..0b2462cb 100644
--- a/src/services/walletService.ts
+++ b/src/services/walletService.ts
@@ -26,7 +26,6 @@ import { receiveTask, receiveOfflinePrepareTask, receiveOfflineCompleteTask} fro
import { sendTask } from './wallet/sendTask'
import { topupTask } from './wallet/topupTask'
import { transferTask } from './wallet/transferTask'
-import { nwcTransferTask } from './wallet/nwcTransferTask'
import { revertTask } from './wallet/revertTask'
import { WalletUtils } from './wallet/utils'
import { NotificationService } from './notificationService'
@@ -73,16 +72,6 @@ type WalletTaskService = {
encodedInvoice: string,
nwcEvent?: NostrEvent
) => Promise
- nwcTransfer: (
- mintBalanceToTransferFrom: MintBalance,
- amountToTransfer: number,
- feeReserve: number,
- unit: MintUnit,
- memo: string,
- invoiceExpiry: Date,
- encodedInvoice: string,
- nwcEvent: NostrEvent
- ) => Promise
receive: (
token: TokenV3,
amountToReceive: number,
@@ -212,34 +201,6 @@ const transfer = async function (
}
-const nwcTransfer = async function (
- mintBalanceToTransferFrom: MintBalance,
- amountToTransfer: number,
- feeReserve: number,
- unit: MintUnit,
- memo: string,
- invoiceExpiry: Date,
- encodedInvoice: string,
- nwcEvent: NostrEvent
-): Promise {
- const now = new Date().getTime()
- SyncQueue.addPrioritizedTask(
- `nwcTransferTask-${now}`,
- async () => await nwcTransferTask(
- mintBalanceToTransferFrom,
- amountToTransfer,
- feeReserve,
- unit,
- memo,
- invoiceExpiry,
- encodedInvoice,
- nwcEvent
- )
- )
- return
-}
-
-
const receive = async function (
token: TokenV3,
amountToReceive: number,
@@ -650,8 +611,7 @@ const _syncStateWithMintTask = async function (
isPending
})
- // If some of the pending by wallet proofs are either pending nor spent by mint
- // can happen in nwcTransfer if lightning payment succeeds but then receive of returned token fails
+ // If some of the pending by wallet proofs are neither pending nor spent by mint
if (unspentByMintProofs.length > 0 && isPending) {
// remove it from pending proofs in the wallet
proofsStore.removeProofs(unspentByMintProofs as Proof[], true, true)
@@ -1863,8 +1823,7 @@ export const WalletTask: WalletTaskService = {
receiveOfflineComplete,
send,
sendAll,
- transfer,
- nwcTransfer,
+ transfer,
topup,
revert
}
diff --git a/yarn.lock b/yarn.lock
index 0ed83388..dcddb96b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2882,12 +2882,12 @@ __metadata:
languageName: node
linkType: hard
-"@notifee/react-native@npm:^9.0.2":
- version: 9.0.2
- resolution: "@notifee/react-native@npm:9.0.2"
+"@notifee/react-native@npm:^9.1.3":
+ version: 9.1.3
+ resolution: "@notifee/react-native@npm:9.1.3"
peerDependencies:
react-native: "*"
- checksum: 2b46e360096dffea51803ff28a5e106d0bdea09651a72487728ee925f1748b2a374377c4db9e85f62fda8d7e16b0e0a2d7dcd4078c0342bac522bec364ccf21a
+ checksum: cf37a6ab404937be62b2c4a94fdd8ee735eb1a8adfc8fc087dd4a3ce6d8234bc30b3417a4be180de5da2ead2d1a3c1ddb8519cb98d24d062fd4d858ec39d9337
languageName: node
linkType: hard
@@ -9436,7 +9436,7 @@ __metadata:
"@gandlaf21/bc-ur": ^1.1.12
"@gandlaf21/bolt11-decode": ^3.1.1
"@gocodingnow/rn-size-matters": ^0.0.4
- "@notifee/react-native": ^9.0.2
+ "@notifee/react-native": ^9.1.3
"@react-native-clipboard/clipboard": ^1.14.1
"@react-native-community/netinfo": ^11.4.0
"@react-native-firebase/app": ^20.5.0