diff --git a/src/models/NwcStore.ts b/src/models/NwcStore.ts
index 9055e5fb..efe15c16 100644
--- a/src/models/NwcStore.ts
+++ b/src/models/NwcStore.ts
@@ -81,6 +81,7 @@ const getConnectionRelays = function () {
const MIN_LIGHTNING_FEE = 2 // sats
const LIGHTNING_FEE_PERCENT = 1
+const MAX_MULTI_PAY_INVOICES = 5
const getSupportedMethods = function () {
return [
@@ -181,6 +182,10 @@ export const NwcConnectionModel = types.model('NwcConnection', {
body = 'Pay invoice error: '
}
+ if(nwcResponse.result_type === 'multi_pay_invoice') {
+ body = 'Multi pay invoice error: '
+ }
+
if(nwcResponse.result_type === 'get_balance') {
body = 'Get balance error: '
}
@@ -201,8 +206,7 @@ export const NwcConnectionModel = types.model('NwcConnection', {
`${self.name} - Nostr Wallet Connect`,
body + (nwcResponse as NwcError).error.message,
nwcPngUrl
- )
-
+ )
}
yield NostrClient.publish(
@@ -210,6 +214,123 @@ export const NwcConnectionModel = types.model('NwcConnection', {
self.connectionRelays
)
}),
+ payInvoice: flow(function* payInvoice(nwcRequest: NwcRequest, encodedInvoice: string, requestEvent: NostrEvent) {
+ log.debug('[Nwc.payInvoice] start')
+
+ try {
+ const walletStore = self.getWalletStore()
+ const proofsStore = self.getProofsStore()
+
+ const invoice = LightningUtils.decodeInvoice(encodedInvoice)
+
+ const {
+ amount: amountToPay,
+ expiry,
+ description,
+ timestamp
+ } = LightningUtils.getInvoiceData(invoice)
+
+ const invoiceExpiry = addSeconds(new Date(timestamp as number * 1000), expiry as number)
+
+ // Calculated on device to avoid mintQuote call for minibits mint
+ const feeReserve = Math.max(MIN_LIGHTNING_FEE, amountToPay * LIGHTNING_FEE_PERCENT / 100)
+ const totalAmountToPay = amountToPay + feeReserve
+
+ let mintBalance: MintBalance | undefined = undefined
+ const minibitsBalance = proofsStore.getMintBalance(MINIBITS_MINT_URL)
+
+ if(minibitsBalance && minibitsBalance.balances.sat! >= totalAmountToPay) {
+ mintBalance = minibitsBalance
+ } else {
+ mintBalance = proofsStore.getMintBalanceWithMaxBalance('sat')
+ }
+
+ const availableBalanceSat = mintBalance?.balances.sat || 0
+
+ if(!mintBalance || availableBalanceSat < totalAmountToPay) {
+ const message = `Insufficient balance to pay this invoice.`
+ return {
+ result_type: nwcRequest.method,
+ error: { code: 'INSUFFICIENT_BALANCE', message}
+ } as NwcError
+ }
+
+ if(totalAmountToPay > self.remainingDailyLimit) {
+ const message = `Your remaining daily limit of ${self.remainingDailyLimit} SAT would be exceeded with this payment.`
+ return {
+ result_type: nwcRequest.method,
+ error: { code: 'QUOTA_EXCEEDED', message}
+ } as NwcError
+ }
+
+ const meltQuote: MeltQuoteResponse = yield walletStore.createLightningMeltQuote(
+ mintBalance.mintUrl,
+ 'sat',
+ encodedInvoice,
+ )
+
+ const result = yield transferTask(
+ mintBalance,
+ amountToPay,
+ 'sat',
+ meltQuote,
+ description || '',
+ invoiceExpiry as Date,
+ encodedInvoice,
+ 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: nwcRequest.method,
+ 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: nwcRequest.method,
+ error: { code: 'INTERNAL', message: result.message}
+ } as NwcError
+ }
+
+ return nwcResponse
+
+ } catch (e: any) {
+ log.error(`[NwcConnection.handlePayInvoice] ${e.message}`)
+
+ return {
+ result_type: nwcRequest.method,
+ error: { code: 'INTERNAL', message: e.message}
+ } as NwcError
+ }
+ }),
}))
.actions(self => ({
handleGetInfo (nwcRequest: NwcRequest): NwcResponse {
@@ -287,7 +408,6 @@ export const NwcConnectionModel = types.model('NwcConnection', {
connection: self.name,
amountMsat: nwcRequest.params.amount,
})
-
const proofsStore = self.getProofsStore()
const mintBalance = proofsStore.getMintBalanceWithMaxBalance('sat')
@@ -388,128 +508,45 @@ export const NwcConnectionModel = types.model('NwcConnection', {
} as NwcResponse
},
handlePayInvoice: flow(function* handlePayInvoice(nwcRequest: NwcRequest, requestEvent: NostrEvent) {
- log.debug('[Nwc.handlePayInvoice] start')
-
- try {
- const encoded = nwcRequest.params.invoice
- const walletStore = self.getWalletStore()
- const proofsStore = self.getProofsStore()
-
- // reset daily limit if day changed while keeping live connection
- if(!isSameDay(self.currentDay, new Date())) {
- self.setRemainingDailyLimit(self.dailyLimit)
- self.setCurrentDay()
- }
+ log.debug('[Nwc.handlePayInvoice] start')
- const invoice = LightningUtils.decodeInvoice(encoded)
-
- const {
- amount: amountToPay,
- expiry,
- description,
- timestamp
- } = LightningUtils.getInvoiceData(invoice)
-
- const invoiceExpiry = addSeconds(new Date(timestamp as number * 1000), expiry as number)
-
- // Calculated on device to avoid mintQuote call for minibits mint
- const feeReserve = Math.max(MIN_LIGHTNING_FEE, amountToPay * LIGHTNING_FEE_PERCENT / 100)
- const totalAmountToPay = amountToPay + feeReserve
-
- let mintBalance: MintBalance | undefined = undefined
- const minibitsBalance = proofsStore.getMintBalance(MINIBITS_MINT_URL)
-
- if(minibitsBalance && minibitsBalance.balances.sat! >= totalAmountToPay) {
- mintBalance = minibitsBalance
- } else {
- mintBalance = proofsStore.getMintBalanceWithMaxBalance('sat')
- }
-
- const availableBalanceSat = mintBalance?.balances.sat || 0
-
- if(!mintBalance || availableBalanceSat < totalAmountToPay) {
- const message = `Insufficient balance to pay this invoice.`
- return {
- result_type: nwcRequest.method,
- error: { code: 'INSUFFICIENT_BALANCE', message}
- } as NwcError
- }
-
- if(totalAmountToPay > self.remainingDailyLimit) {
- const message = `Your remaining daily limit of ${self.remainingDailyLimit} SAT would be exceeded with this payment.`
- return {
- result_type: nwcRequest.method,
- error: { code: 'QUOTA_EXCEEDED', message}
- } as NwcError
- }
-
- const meltQuote: MeltQuoteResponse = yield walletStore.createLightningMeltQuote(
- mintBalance.mintUrl,
- 'sat',
- encoded,
- )
-
- const result = yield transferTask(
- mintBalance,
- amountToPay,
- 'sat',
- meltQuote,
- description || '',
- invoiceExpiry as Date,
- encoded,
- 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}`)
-
- return {
- result_type: nwcRequest.method,
- error: { code: 'INTERNAL', message: e.message}
- } as NwcError
+ // reset daily limit if day changed while keeping live connection
+ if(!isSameDay(self.currentDay, new Date())) {
+ self.setRemainingDailyLimit(self.dailyLimit)
+ self.setCurrentDay()
}
+
+ const nwcResponse = yield self.payInvoice(nwcRequest, nwcRequest.params.invoice, requestEvent)
+ return nwcResponse as NwcResponse | NwcError
+ }),
+ handleMultiPayInvoice: flow(function* handleMultiPayInvoice(nwcRequest: NwcRequest, requestEvent: NostrEvent) {
+ log.debug('[Nwc.handleMultiPayInvoice] start')
+
+ const encodedInvoices: string[] = nwcRequest.params.invoices
+
+ if(encodedInvoices.length > MAX_MULTI_PAY_INVOICES) {
+ const nwcResponse = {
+ result_type: 'multi_pay_invoice',
+ error: { code: 'INTERNAL', message: 'Can not process more than 5 payments at once.'}
+ } as NwcError
+
+ return [nwcResponse] as NwcError[]
+ }
+
+ // reset daily limit if day changed while keeping live connection
+ if(!isSameDay(self.currentDay, new Date())) {
+ self.setRemainingDailyLimit(self.dailyLimit)
+ self.setCurrentDay()
+ }
+
+ const nwcResponses: (NwcResponse | NwcError)[] = []
+
+ for (const invoice of encodedInvoices) {
+ const nwcResponse = yield self.payInvoice(nwcRequest, invoice, requestEvent)
+ nwcResponses.push(nwcResponse)
+ }
+
+ return nwcResponses
})
}))
.actions(self => ({
@@ -527,29 +564,34 @@ export const NwcConnectionModel = types.model('NwcConnection', {
nwcRequest = decryptedNwcRequest
}
- let nwcResponse: NwcResponse | NwcError
+ let nwcResponse: NwcResponse | NwcError | undefined = undefined
+ let nwcResponses: (NwcResponse | NwcError)[] = []
log.trace('[Nwc.handleRequest] request event', {requestEvent})
log.trace('[Nwc.handleRequest] decrypted nwc command', {nwcRequest})
switch (nwcRequest.method) {
case 'get_info':
- nwcResponse = self.handleGetInfo(nwcRequest)
+ nwcResponse = self.handleGetInfo(nwcRequest)
break
case 'list_transactions':
- nwcResponse = self.handleListTransactions(nwcRequest)
+ nwcResponse = self.handleListTransactions(nwcRequest)
break
case 'get_balance':
- nwcResponse = self.handleGetBalance(nwcRequest)
+ nwcResponse = self.handleGetBalance(nwcRequest)
break
case 'make_invoice':
- nwcResponse = yield self.handleMakeInvoice(nwcRequest, requestEvent)
+ nwcResponse = yield self.handleMakeInvoice(nwcRequest, requestEvent)
break
case 'lookup_invoice':
- nwcResponse = self.handleLookupInvoice(nwcRequest)
+ nwcResponse = self.handleLookupInvoice(nwcRequest)
break
case 'pay_invoice':
- nwcResponse = yield self.handlePayInvoice(nwcRequest, requestEvent)
+ nwcResponse = yield self.handlePayInvoice(nwcRequest, requestEvent)
+ break
+ case 'multi_pay_invoice':
+ const responses = yield self.handleMultiPayInvoice(nwcRequest, requestEvent)
+ nwcResponses = [...responses]
break
default:
const message = `NWC method ${nwcRequest.method} is unknown or not yet supported.`
@@ -561,11 +603,16 @@ export const NwcConnectionModel = types.model('NwcConnection', {
log.error(message, {nwcRequest})
}
-
- yield self.sendResponse(nwcResponse, requestEvent)
+ // support for multiple responses from one nwc request (multi_pay_invoice)
+ if(nwcResponse) {
+ nwcResponses.push(nwcResponse)
+ }
- return nwcResponse
+ for (const response of nwcResponses) {
+ yield self.sendResponse(response, requestEvent)
+ }
+ return nwcResponses[0]
}),
}))
@@ -776,9 +823,6 @@ export const NwcStoreModel = types
const nwcResponse: NwcResponse | NwcError =
yield targetConnection.handleNwcRequestTask(event, decryptedNwcRequest)
- // prevent rare cases where this might not be called in SyncQueue._handleTaskResult
- //yield NotificationService.stopForegroundService()
-
return {
taskFunction: HANDLE_NWC_REQUEST_TASK,
message: (nwcResponse as NwcResponse).result || undefined ,