Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Reverse Swap notification #953

Merged
merged 2 commits into from
Jul 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ object Constants {
const val SHUTDOWN_DELAY_MS = 60 * 1000L

// Notification Channels
const val NOTIFICATION_CHANNEL_ADDRESS_TXS_CONFIRMED = "ADDRESS_TXS_CONFIRMED"
const val NOTIFICATION_CHANNEL_FOREGROUND_SERVICE = "FOREGROUND_SERVICE"
const val NOTIFICATION_CHANNEL_LNURL_PAY = "LNURL_PAY"
const val NOTIFICATION_CHANNEL_PAYMENT_RECEIVED = "PAYMENT_RECEIVED"
const val NOTIFICATION_CHANNEL_SWAP_TX_CONFIRMED = "SWAP_TX_CONFIRMED"

// Notification Ids
const val NOTIFICATION_ID_FOREGROUND_SERVICE = 100
Expand All @@ -28,6 +28,14 @@ object Constants {
const val MESSAGE_TYPE_PAYMENT_RECEIVED = "payment_received"

// Resource Identifiers
const val ADDRESS_TXS_CONFIRMED_NOTIFICATION_CHANNEL_DESCRIPTION =
"address_txs_confirmed_notification_channel_description"
const val ADDRESS_TXS_CONFIRMED_NOTIFICATION_CHANNEL_NAME =
"address_txs_confirmed_notification_channel_name"
const val ADDRESS_TXS_CONFIRMED_WORKGROUP_ID = "txs_confirmed"
const val ADDRESS_TXS_CONFIRMED_WORKGROUP_DESCRIPTION =
"address_txs_confirmed_work_group_description"
const val ADDRESS_TXS_CONFIRMED_WORKGROUP_NAME = "address_txs_confirmed_work_group_name"
const val FOREGROUND_SERVICE_NOTIFICATION_CHANNEL_DESCRIPTION =
"foreground_service_notification_channel_description"
const val FOREGROUND_SERVICE_NOTIFICATION_CHANNEL_NAME =
Expand Down Expand Up @@ -65,22 +73,21 @@ object Constants {
const val OFFLINE_PAYMENTS_WORKGROUP_DESCRIPTION =
"offline_payments_work_group_description"
const val OFFLINE_PAYMENTS_WORKGROUP_NAME = "offline_payments_work_group_name"
const val SWAP_TX_CONFIRMED_NOTIFICATION_CHANNEL_DESCRIPTION =
"swap_tx_confirmed_notification_channel_description"
const val SWAP_TX_CONFIRMED_NOTIFICATION_CHANNEL_NAME =
"swap_tx_confirmed_notification_channel_name"
const val SWAP_TX_CONFIRMED_NOTIFICATION_FAILURE_TEXT =
"swap_tx_confirmed_notification_failure_text"
const val SWAP_TX_CONFIRMED_NOTIFICATION_FAILURE_TITLE =
"swap_tx_confirmed_notification_failure_title"
const val SWAP_TX_CONFIRMED_NOTIFICATION_TITLE =
"swap_tx_confirmed_notification_title"
const val SWAP_TX_CONFIRMED_WORKGROUP_ID = "swap_tx"
const val SWAP_TX_CONFIRMED_WORKGROUP_DESCRIPTION =
"swap_tx_confirmed_work_group_description"
const val SWAP_TX_CONFIRMED_WORKGROUP_NAME = "swap_tx_confirmed_work_group_name"

// Resource Identifier Defaults
const val DEFAULT_ADDRESS_TXS_CONFIRMED_NOTIFICATION_CHANNEL_DESCRIPTION =
"Notifications for confirmed transactions when the application is in the background"
const val DEFAULT_ADDRESS_TXS_CONFIRMED_NOTIFICATION_CHANNEL_NAME =
"Confirmed Transactions"
const val DEFAULT_ADDRESS_TXS_CONFIRMED_WORKGROUP_DESCRIPTION =
"Required to handle confirmed transactions when the application is in the background"
const val DEFAULT_ADDRESS_TXS_CONFIRMED_WORKGROUP_NAME = "Confirmed Transactions"
const val DEFAULT_FOREGROUND_SERVICE_NOTIFICATION_CHANNEL_DESCRIPTION =
"Shown when the application is in the background"
const val DEFAULT_FOREGROUND_SERVICE_NOTIFICATION_CHANNEL_NAME =
Expand Down Expand Up @@ -114,17 +121,10 @@ object Constants {
const val DEFAULT_OFFLINE_PAYMENTS_WORKGROUP_DESCRIPTION =
"Required to receive payments when the application is in the background"
const val DEFAULT_OFFLINE_PAYMENTS_WORKGROUP_NAME = "Offline Payments"
const val DEFAULT_SWAP_TX_CONFIRMED_NOTIFICATION_CHANNEL_DESCRIPTION =
"Notifications for received swaps when the application is in the background"
const val DEFAULT_SWAP_TX_CONFIRMED_NOTIFICATION_CHANNEL_NAME =
"Received Swaps"
const val DEFAULT_SWAP_TX_CONFIRMED_NOTIFICATION_FAILURE_TEXT =
"Tap to complete swap"
const val DEFAULT_SWAP_TX_CONFIRMED_NOTIFICATION_FAILURE_TITLE =
"Swap Ongoing"
const val DEFAULT_SWAP_TX_CONFIRMED_NOTIFICATION_TITLE =
"Swap Confirmed"
const val DEFAULT_SWAP_TX_CONFIRMED_WORKGROUP_DESCRIPTION =
"Required to handle swaps when the application is in the background"
const val DEFAULT_SWAP_TX_CONFIRMED_WORKGROUP_NAME = "Swap Transactions"
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ import breez_sdk_notification.Constants.NOTIFICATION_ID_FOREGROUND_SERVICE
import breez_sdk_notification.Constants.SERVICE_TIMEOUT_MS
import breez_sdk_notification.Constants.SHUTDOWN_DELAY_MS
import breez_sdk_notification.NotificationHelper.Companion.notifyForegroundService
import breez_sdk_notification.job.ConfirmTransactionJob
import breez_sdk_notification.job.Job
import breez_sdk_notification.job.LnurlPayInfoJob
import breez_sdk_notification.job.LnurlPayInvoiceJob
import breez_sdk_notification.job.ReceivePaymentJob
import breez_sdk_notification.job.RedeemSwapJob
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -147,7 +147,7 @@ abstract class ForegroundService : SdkForegroundService, EventListener, Service(
return Message.createFromIntent(intent)?.let { message ->
message.payload?.let { payload ->
when (message.type) {
MESSAGE_TYPE_ADDRESS_TXS_CONFIRMED -> RedeemSwapJob(
MESSAGE_TYPE_ADDRESS_TXS_CONFIRMED -> ConfirmTransactionJob(
applicationContext,
this,
payload,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ import androidx.annotation.RequiresApi
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import breez_sdk_notification.Constants.ADDRESS_TXS_CONFIRMED_NOTIFICATION_CHANNEL_DESCRIPTION
import breez_sdk_notification.Constants.ADDRESS_TXS_CONFIRMED_NOTIFICATION_CHANNEL_NAME
import breez_sdk_notification.Constants.ADDRESS_TXS_CONFIRMED_WORKGROUP_DESCRIPTION
import breez_sdk_notification.Constants.ADDRESS_TXS_CONFIRMED_WORKGROUP_ID
import breez_sdk_notification.Constants.ADDRESS_TXS_CONFIRMED_WORKGROUP_NAME
import breez_sdk_notification.Constants.DEFAULT_ADDRESS_TXS_CONFIRMED_NOTIFICATION_CHANNEL_DESCRIPTION
import breez_sdk_notification.Constants.DEFAULT_ADDRESS_TXS_CONFIRMED_NOTIFICATION_CHANNEL_NAME
import breez_sdk_notification.Constants.DEFAULT_ADDRESS_TXS_CONFIRMED_WORKGROUP_DESCRIPTION
import breez_sdk_notification.Constants.DEFAULT_ADDRESS_TXS_CONFIRMED_WORKGROUP_NAME
import breez_sdk_notification.Constants.DEFAULT_FOREGROUND_SERVICE_NOTIFICATION_CHANNEL_DESCRIPTION
import breez_sdk_notification.Constants.DEFAULT_FOREGROUND_SERVICE_NOTIFICATION_CHANNEL_NAME
import breez_sdk_notification.Constants.DEFAULT_FOREGROUND_SERVICE_NOTIFICATION_TITLE
Expand All @@ -29,10 +38,6 @@ import breez_sdk_notification.Constants.DEFAULT_OFFLINE_PAYMENTS_WORKGROUP_DESCR
import breez_sdk_notification.Constants.DEFAULT_OFFLINE_PAYMENTS_WORKGROUP_NAME
import breez_sdk_notification.Constants.DEFAULT_PAYMENT_RECEIVED_NOTIFICATION_CHANNEL_DESCRIPTION
import breez_sdk_notification.Constants.DEFAULT_PAYMENT_RECEIVED_NOTIFICATION_CHANNEL_NAME
import breez_sdk_notification.Constants.DEFAULT_SWAP_TX_CONFIRMED_NOTIFICATION_CHANNEL_DESCRIPTION
import breez_sdk_notification.Constants.DEFAULT_SWAP_TX_CONFIRMED_NOTIFICATION_CHANNEL_NAME
import breez_sdk_notification.Constants.DEFAULT_SWAP_TX_CONFIRMED_WORKGROUP_DESCRIPTION
import breez_sdk_notification.Constants.DEFAULT_SWAP_TX_CONFIRMED_WORKGROUP_NAME
import breez_sdk_notification.Constants.FOREGROUND_SERVICE_NOTIFICATION_CHANNEL_DESCRIPTION
import breez_sdk_notification.Constants.FOREGROUND_SERVICE_NOTIFICATION_CHANNEL_NAME
import breez_sdk_notification.Constants.FOREGROUND_SERVICE_NOTIFICATION_TITLE
Expand All @@ -41,10 +46,10 @@ import breez_sdk_notification.Constants.LNURL_PAY_NOTIFICATION_CHANNEL_NAME
import breez_sdk_notification.Constants.LNURL_PAY_WORKGROUP_DESCRIPTION
import breez_sdk_notification.Constants.LNURL_PAY_WORKGROUP_ID
import breez_sdk_notification.Constants.LNURL_PAY_WORKGROUP_NAME
import breez_sdk_notification.Constants.NOTIFICATION_CHANNEL_ADDRESS_TXS_CONFIRMED
import breez_sdk_notification.Constants.NOTIFICATION_CHANNEL_FOREGROUND_SERVICE
import breez_sdk_notification.Constants.NOTIFICATION_CHANNEL_LNURL_PAY
import breez_sdk_notification.Constants.NOTIFICATION_CHANNEL_PAYMENT_RECEIVED
import breez_sdk_notification.Constants.NOTIFICATION_CHANNEL_SWAP_TX_CONFIRMED
import breez_sdk_notification.Constants.NOTIFICATION_COLOR
import breez_sdk_notification.Constants.NOTIFICATION_ICON
import breez_sdk_notification.Constants.NOTIFICATION_ID_FOREGROUND_SERVICE
Expand All @@ -53,11 +58,6 @@ import breez_sdk_notification.Constants.OFFLINE_PAYMENTS_WORKGROUP_ID
import breez_sdk_notification.Constants.OFFLINE_PAYMENTS_WORKGROUP_NAME
import breez_sdk_notification.Constants.PAYMENT_RECEIVED_NOTIFICATION_CHANNEL_DESCRIPTION
import breez_sdk_notification.Constants.PAYMENT_RECEIVED_NOTIFICATION_CHANNEL_NAME
import breez_sdk_notification.Constants.SWAP_TX_CONFIRMED_NOTIFICATION_CHANNEL_DESCRIPTION
import breez_sdk_notification.Constants.SWAP_TX_CONFIRMED_NOTIFICATION_CHANNEL_NAME
import breez_sdk_notification.Constants.SWAP_TX_CONFIRMED_WORKGROUP_DESCRIPTION
import breez_sdk_notification.Constants.SWAP_TX_CONFIRMED_WORKGROUP_ID
import breez_sdk_notification.Constants.SWAP_TX_CONFIRMED_WORKGROUP_NAME
import breez_sdk_notification.ResourceHelper.Companion.getColor
import breez_sdk_notification.ResourceHelper.Companion.getDrawable
import breez_sdk_notification.ResourceHelper.Companion.getString
Expand Down Expand Up @@ -194,20 +194,20 @@ class NotificationHelper {
group = LNURL_PAY_WORKGROUP_ID
}
val swapTxConfirmedNotificationChannel = NotificationChannel(
Copy link
Contributor

@erdemyerebasmaz erdemyerebasmaz Jul 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we rename the notification channel & notification channel group variable as well?

"${applicationId}.${NOTIFICATION_CHANNEL_SWAP_TX_CONFIRMED}",
"${applicationId}.${NOTIFICATION_CHANNEL_ADDRESS_TXS_CONFIRMED}",
getString(
context,
SWAP_TX_CONFIRMED_NOTIFICATION_CHANNEL_NAME,
DEFAULT_SWAP_TX_CONFIRMED_NOTIFICATION_CHANNEL_NAME
ADDRESS_TXS_CONFIRMED_NOTIFICATION_CHANNEL_NAME,
DEFAULT_ADDRESS_TXS_CONFIRMED_NOTIFICATION_CHANNEL_NAME
),
NotificationManager.IMPORTANCE_DEFAULT
).apply {
description = getString(
context,
SWAP_TX_CONFIRMED_NOTIFICATION_CHANNEL_DESCRIPTION,
DEFAULT_SWAP_TX_CONFIRMED_NOTIFICATION_CHANNEL_DESCRIPTION
ADDRESS_TXS_CONFIRMED_NOTIFICATION_CHANNEL_DESCRIPTION,
DEFAULT_ADDRESS_TXS_CONFIRMED_NOTIFICATION_CHANNEL_DESCRIPTION
)
group = SWAP_TX_CONFIRMED_WORKGROUP_ID
group = ADDRESS_TXS_CONFIRMED_WORKGROUP_ID
}
notificationManager.createNotificationChannels(
listOf(
Expand Down Expand Up @@ -241,11 +241,11 @@ class NotificationHelper {
),
)
val swapTxConfirmedNotificationChannelGroup = NotificationChannelGroup(
SWAP_TX_CONFIRMED_WORKGROUP_ID,
ADDRESS_TXS_CONFIRMED_WORKGROUP_ID,
getString(
context,
SWAP_TX_CONFIRMED_WORKGROUP_NAME,
DEFAULT_SWAP_TX_CONFIRMED_WORKGROUP_NAME
ADDRESS_TXS_CONFIRMED_WORKGROUP_NAME,
DEFAULT_ADDRESS_TXS_CONFIRMED_WORKGROUP_NAME
),
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
Expand All @@ -261,8 +261,8 @@ class NotificationHelper {
)
swapTxConfirmedNotificationChannelGroup.description = getString(
context,
SWAP_TX_CONFIRMED_WORKGROUP_DESCRIPTION,
DEFAULT_SWAP_TX_CONFIRMED_WORKGROUP_DESCRIPTION
ADDRESS_TXS_CONFIRMED_WORKGROUP_DESCRIPTION,
DEFAULT_ADDRESS_TXS_CONFIRMED_WORKGROUP_DESCRIPTION
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ package breez_sdk_notification.job
import android.content.Context
import breez_sdk.BlockingBreezServices
import breez_sdk.BreezEvent
import breez_sdk.ReverseSwapStatus
import breez_sdk_notification.Constants.DEFAULT_SWAP_TX_CONFIRMED_NOTIFICATION_FAILURE_TEXT
import breez_sdk_notification.Constants.DEFAULT_SWAP_TX_CONFIRMED_NOTIFICATION_FAILURE_TITLE
import breez_sdk_notification.Constants.DEFAULT_SWAP_TX_CONFIRMED_NOTIFICATION_TITLE
import breez_sdk_notification.Constants.NOTIFICATION_CHANNEL_SWAP_TX_CONFIRMED
import breez_sdk_notification.Constants.NOTIFICATION_CHANNEL_ADDRESS_TXS_CONFIRMED
import breez_sdk_notification.Constants.SWAP_TX_CONFIRMED_NOTIFICATION_FAILURE_TEXT
import breez_sdk_notification.Constants.SWAP_TX_CONFIRMED_NOTIFICATION_FAILURE_TITLE
import breez_sdk_notification.Constants.SWAP_TX_CONFIRMED_NOTIFICATION_TITLE
Expand All @@ -22,38 +23,63 @@ data class AddressTxsConfirmedRequest(
val address: String,
)

class RedeemSwapJob(
class ConfirmTransactionJob(
private val context: Context,
private val fgService: SdkForegroundService,
private val payload: String,
private val logger: ServiceLogger,
private var bitcoinAddress: String? = null,
) : Job {
companion object {
private const val TAG = "RedeemSwapJob"
private const val TAG = "ConfirmTransactionJob"
}

override fun start(breezSDK: BlockingBreezServices) {
try {
val request = Json.decodeFromString(AddressTxsConfirmedRequest.serializer(), payload)
this.bitcoinAddress = request.address
breezSDK.redeemSwap(request.address)
logger.log(TAG, "Found swap for ${request.address}", "INFO")
} catch (e: Exception) {
logger.log(TAG, "Failed to manually redeem swap notification: ${e.message}", "WARN")
notifyFailure()
logger.log(TAG, "Failed to decode payload: ${e.message}", "WARN")
}

this.bitcoinAddress?.let {address ->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be nice to be able to recognize whether it is a swap or reverse swap without trying both.
If we pass a query param app_data in the webhook url it returns back in the notification payload.
See here where it is grapped from the query param: https://github.com/breez/notify/blob/main/http/router.go#L40
And here how it is populated into the payload: https://github.com/breez/notify/blob/main/breezsdk/init.go#L43
Perhaps we can use it here to identify if it is a swap or reverse swap?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I thought that too, but the redeemSwap first checks there is a swap for that address so it's pretty inexpensive.

What if the application or NDS is already using the app_data param in someway? We would override it and potentially break NDS/app logic.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good argument. I am ok then to leave this inexpensive check.

try {
breezSDK.redeemSwap(address)
logger.log(TAG, "Found swap for $address", "INFO")
return
} catch (e: Exception) {
logger.log(TAG, "Failed to redeem swap: ${e.message}", "WARN")
}

try {
breezSDK.processReverseSwap(address)
logger.log(TAG, "Found reverse swap for $address", "INFO")
return
} catch (e: Exception) {
logger.log(TAG, "Failed to process reverse swap: ${e.message}", "WARN")
}
}

fgService.onFinished(this)
}

override fun onEvent(e: BreezEvent) {
this.bitcoinAddress?.let {address ->
when (e) {
is BreezEvent.ReverseSwapUpdated -> {
val revSwapInfo = e.details
logger.log(TAG, "Received reverse swap updated event: ${revSwapInfo.id} current address: $address status: ${revSwapInfo.status}", "TRACE")
if (revSwapInfo.status == ReverseSwapStatus.COMPLETED_SEEN || revSwapInfo.status == ReverseSwapStatus.COMPLETED_CONFIRMED) {
notifySuccess(address)
}
}

is BreezEvent.SwapUpdated -> {
val swapInfo = e.details
logger.log(TAG, "Received swap updated event: ${swapInfo.bitcoinAddress} current address: $address status: ${swapInfo.status}", "TRACE")
if (swapInfo.bitcoinAddress == address) {
if (swapInfo.paidMsat.toLong() > 0) {
notifySuccessAndShutdown(address)
notifySuccess(address)
}
}
}
Expand All @@ -67,11 +93,11 @@ class RedeemSwapJob(
notifyFailure()
}

private fun notifySuccessAndShutdown(address: String) {
logger.log(TAG, "Swap address $address redeemed successfully", "INFO")
private fun notifySuccess(address: String) {
logger.log(TAG, "Address $address processed successfully", "INFO")
notifyChannel(
context,
NOTIFICATION_CHANNEL_SWAP_TX_CONFIRMED,
NOTIFICATION_CHANNEL_ADDRESS_TXS_CONFIRMED,
getString(
context,
SWAP_TX_CONFIRMED_NOTIFICATION_TITLE,
Expand All @@ -83,10 +109,10 @@ class RedeemSwapJob(

private fun notifyFailure() {
this.bitcoinAddress?.let{address ->
logger.log(TAG, "Swap address $address not redeemed", "INFO")
logger.log(TAG, "Address $address processing failed", "INFO")
notifyChannel(
context,
NOTIFICATION_CHANNEL_SWAP_TX_CONFIRMED,
NOTIFICATION_CHANNEL_ADDRESS_TXS_CONFIRMED,
getString(
context,
SWAP_TX_CONFIRMED_NOTIFICATION_FAILURE_TITLE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import Foundation

struct Constants {
// Notification Threads
static let NOTIFICATION_THREAD_ADDRESS_TXS_CONFIRMED = "ADDRESS_TXS_CONFIRMED"
static let NOTIFICATION_THREAD_LNURL_PAY = "LNURL_PAY"
static let NOTIFICATION_THREAD_PAYMENT_RECEIVED = "PAYMENT_RECEIVED"
static let NOTIFICATION_THREAD_SWAP_TX_CONFIRMED = "SWAP_TX_CONFIRMED"

// Message Data
static let MESSAGE_DATA_TYPE = "notification_type"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ open class SDKNotificationService: UNNotificationServiceExtension {
self.logger.log(tag: TAG, line: "\(notificationType) data string: \(payload)", level: "INFO")
switch(notificationType) {
case Constants.MESSAGE_TYPE_ADDRESS_TXS_CONFIRMED:
return RedeemSwapTask(payload: payload, logger: self.logger, contentHandler: contentHandler, bestAttemptContent: bestAttemptContent)
return ConfirmTransactionTask(payload: payload, logger: self.logger, contentHandler: contentHandler, bestAttemptContent: bestAttemptContent)
case Constants.MESSAGE_TYPE_LNURL_PAY_INFO:
return LnurlPayInfoTask(payload: payload, logger: self.logger, config: self.config, contentHandler: contentHandler, bestAttemptContent: bestAttemptContent)
case Constants.MESSAGE_TYPE_LNURL_PAY_INVOICE:
Expand Down
Loading
Loading