diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 724d0252e..4316e8668 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -50,6 +50,7 @@
+
Unit) {
if (!isWriteable()) return
zapPaymentRequest?.let {
- val event = LnZapPaymentRequestEvent.create(lnInvoice, it.pubKeyHex, it.secret?.toByteArray() ?: loggedIn.privKey!!)
+ val event = LnZapPaymentRequestEvent.create(bolt11, it.pubKeyHex, it.secret?.toByteArray() ?: loggedIn.privKey!!)
- Client.send(event, it.relayUri)
+ val wcListener = NostrLnZapPaymentResponseDataSource(it.pubKeyHex, loggedIn.pubKey.toHexKey(), event.id)
+ wcListener.start()
+
+ LocalCache.consume(event, onResponse)
+ Client.send(event, it.relayUri, wcListener.feedTypes) {
+ wcListener.destroy()
+ }
}
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt
index 140a30849..4554deb62 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt
@@ -31,6 +31,8 @@ object LocalCache {
val channels = ConcurrentHashMap()
val addressables = ConcurrentHashMap(100)
+ val awaitingPaymentRequests = ConcurrentHashMap Unit>(10)
+
fun checkGetOrCreateUser(key: String): User? {
if (isValidHexNpub(key)) {
return getOrCreateUser(key)
@@ -675,6 +677,22 @@ object LocalCache {
refreshObservers(note)
}
+ fun consume(event: LnZapPaymentRequestEvent) {
+ // Does nothing without a response callback.
+ }
+
+ fun consume(event: LnZapPaymentRequestEvent, onResponse: (LnZapPaymentResponseEvent) -> Unit) {
+ awaitingPaymentRequests.put(event.id, onResponse)
+ }
+
+ fun consume(event: LnZapPaymentResponseEvent) {
+ val responseCallback = awaitingPaymentRequests[event.requestId()]
+
+ if (responseCallback != null) {
+ responseCallback(event)
+ }
+ }
+
fun findUsersStartingWith(username: String): List {
return users.values.filter {
(it.anyNameStartsWith(username)) ||
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/Nip47.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/Nip47.kt
index 4fb1bdcde..854cf4b1c 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/Nip47.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/Nip47.kt
@@ -14,7 +14,7 @@ object Nip47 {
val url = Uri.parse(uri)
- if (url.scheme != "nostrwalletconnect") {
+ if (url.scheme != "nostrwalletconnect" || url.scheme != "nostr+walletconnect") {
throw IllegalArgumentException("Not a Wallet Connect QR Code")
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt
index cb867066c..1d2327426 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt
@@ -88,6 +88,7 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
RepostEvent.kind,
ReportEvent.kind,
LnZapEvent.kind,
+ LnZapPaymentResponseEvent.kind,
ChannelMessageEvent.kind,
BadgeAwardEvent.kind
),
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt
index 61fa8bedf..206a6538a 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt
@@ -35,6 +35,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import java.util.Date
import java.util.UUID
+import kotlin.Error
abstract class NostrDataSource(val debugName: String) {
private var subscriptions = mapOf()
@@ -81,6 +82,8 @@ abstract class NostrDataSource(val debugName: String) {
LocalCache.consume(event)
}
is LnZapRequestEvent -> LocalCache.consume(event)
+ is LnZapPaymentRequestEvent -> LocalCache.consume(event)
+ is LnZapPaymentResponseEvent -> LocalCache.consume(event)
is LongTextNoteEvent -> LocalCache.consume(event, relay)
is MetadataEvent -> LocalCache.consume(event)
is PrivateDmEvent -> LocalCache.consume(event, relay)
@@ -129,6 +132,11 @@ abstract class NostrDataSource(val debugName: String) {
Client.subscribe(clientListener)
}
+ fun destroy() {
+ stop()
+ Client.unsubscribe(clientListener)
+ }
+
open fun start() {
println("DataSource: ${this.javaClass.simpleName} Start")
resetFilters()
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrLnZapPaymentResponseDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrLnZapPaymentResponseDataSource.kt
new file mode 100644
index 000000000..faa301cce
--- /dev/null
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrLnZapPaymentResponseDataSource.kt
@@ -0,0 +1,39 @@
+package com.vitorpamplona.amethyst.service
+
+import com.vitorpamplona.amethyst.service.model.LnZapPaymentResponseEvent
+import com.vitorpamplona.amethyst.service.relays.FeedType
+import com.vitorpamplona.amethyst.service.relays.JsonFilter
+import com.vitorpamplona.amethyst.service.relays.TypedFilter
+
+class NostrLnZapPaymentResponseDataSource(
+ private var fromServiceHex: String,
+ private var toUserHex: String,
+ private var replyingToHex: String,
+): NostrDataSource("LnZapPaymentResponseFeed") {
+
+ val feedTypes = setOf(FeedType.WALLET_CONNECT)
+
+ private fun createWalletConnectServiceWatcher(): TypedFilter {
+ // downloads all the reactions to a given event.
+ return TypedFilter(
+ types = feedTypes,
+ filter = JsonFilter(
+ kinds = listOf(LnZapPaymentResponseEvent.kind),
+ authors = listOf(fromServiceHex),
+ tags = mapOf(
+ "e" to listOf(replyingToHex),
+ "p" to listOf(toUserHex)
+ ),
+ limit = 1
+ )
+ )
+ }
+
+ val channel = requestNewChannel()
+
+ override fun updateChannelFilters() {
+ val wc = createWalletConnectServiceWatcher()
+
+ channel.typedFilters = listOfNotNull(wc).ifEmpty { null }
+ }
+}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/Event.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/Event.kt
index 6d857328c..9b63fa799 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/Event.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/Event.kt
@@ -225,6 +225,7 @@ open class Event(
FileHeaderEvent.kind -> FileHeaderEvent(id, pubKey, createdAt, tags, content, sig)
LnZapEvent.kind -> LnZapEvent(id, pubKey, createdAt, tags, content, sig)
LnZapPaymentRequestEvent.kind -> LnZapPaymentRequestEvent(id, pubKey, createdAt, tags, content, sig)
+ LnZapPaymentResponseEvent.kind -> LnZapPaymentResponseEvent(id, pubKey, createdAt, tags, content, sig)
LnZapRequestEvent.kind -> LnZapRequestEvent(id, pubKey, createdAt, tags, content, sig)
LongTextNoteEvent.kind -> LongTextNoteEvent(id, pubKey, createdAt, tags, content, sig)
MetadataEvent.kind -> MetadataEvent(id, pubKey, createdAt, tags, content, sig)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentRequestEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentRequestEvent.kt
index c35c75fda..e1d95ed87 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentRequestEvent.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentRequestEvent.kt
@@ -1,6 +1,7 @@
package com.vitorpamplona.amethyst.service.model
import android.util.Log
+import com.google.gson.annotations.SerializedName
import com.vitorpamplona.amethyst.model.HexKey
import com.vitorpamplona.amethyst.model.toByteArray
import com.vitorpamplona.amethyst.model.toHexKey
@@ -16,6 +17,8 @@ class LnZapPaymentRequestEvent(
sig: HexKey
) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
+ fun walletServicePubKey() = tags.firstOrNull() { it.size > 1 && it[0] == "p" }?.get(1)
+
fun lnInvoice(privKey: ByteArray): String? {
return try {
val sharedSecret = Utils.getSharedSecret(privKey, pubKey.toByteArray())
@@ -37,9 +40,10 @@ class LnZapPaymentRequestEvent(
createdAt: Long = Date().time / 1000
): LnZapPaymentRequestEvent {
val pubKey = Utils.pubkeyCreate(privateKey)
+ val serializedRequest = gson.toJson(PayInvoiceMethod(lnInvoice))
val content = Utils.encrypt(
- lnInvoice,
+ serializedRequest,
privateKey,
walletServicePubkey.toByteArray()
)
@@ -53,3 +57,21 @@ class LnZapPaymentRequestEvent(
}
}
}
+
+// REQUEST OBJECTS
+
+abstract class Request(val method: String, val params: Params)
+abstract class Params
+
+
+// PayInvoice Call
+
+class PayInvoiceMethod(bolt11: String): Request("pay_invoice", PayInvoiceParams(bolt11)) {
+ class PayInvoiceParams(val invoice: String): Params()
+}
+
+
+
+
+
+
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentResponseEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentResponseEvent.kt
new file mode 100644
index 000000000..1b6889878
--- /dev/null
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentResponseEvent.kt
@@ -0,0 +1,83 @@
+package com.vitorpamplona.amethyst.service.model
+
+import android.util.Log
+import com.google.gson.annotations.SerializedName
+import com.google.gson.reflect.TypeToken
+import com.vitorpamplona.amethyst.model.HexKey
+import com.vitorpamplona.amethyst.model.toByteArray
+import com.vitorpamplona.amethyst.model.toHexKey
+import nostr.postr.Utils
+import java.util.Date
+
+class LnZapPaymentResponseEvent(
+ id: HexKey,
+ pubKey: HexKey,
+ createdAt: Long,
+ tags: List>,
+ content: String,
+ sig: HexKey
+) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
+
+ fun requestAuthor() = tags.firstOrNull() { it.size > 1 && it[0] == "p" }?.get(1)
+ fun requestId() = tags.firstOrNull() { it.size > 1 && it[0] == "e" }?.get(1)
+
+ fun response(): Response? = try {
+ if (content.isNotEmpty()) {
+ gson.fromJson(content, Response::class.java)
+ } else {
+ null
+ }
+ } catch (e: Exception) {
+ Log.w("LnZapPaymentResponseEvent", "Can't parse content as a payment response: $content", e)
+ null
+ }
+
+ companion object {
+ const val kind = 23195
+ }
+}
+
+// RESPONSE OBJECTS
+abstract class Response(
+ @SerializedName("result_type")
+ val resultType: String
+)
+
+// PayInvoice Call
+
+class PayInvoiceSuccessResponse(val result: PayInvoiceResultParams):
+ Response("pay_invoice")
+{
+ class PayInvoiceResultParams(val preimage: String)
+}
+
+class PayInvoiceErrorResponse(val error: PayInvoiceErrorParams? = null):
+ Response("pay_invoice")
+{
+ class PayInvoiceErrorParams(val code: ErrorType?, val message: String?)
+
+ enum class ErrorType {
+ @SerializedName(value = "rate_limited", alternate = ["RATE_LIMITED"])
+ RATE_LIMITED, // The client is sending commands too fast. It should retry in a few seconds.
+ @SerializedName(value = "not_implemented", alternate = ["NOT_IMPLEMENTED"])
+ NOT_IMPLEMENTED, // The command is not known or is intentionally not implemented.
+ @SerializedName(value = "insufficient_balance", alternate = ["INSUFFICIENT_BALANCE"])
+ INSUFFICIENT_BALANCE, // The wallet does not have enough funds to cover a fee reserve or the payment amount.
+ @SerializedName(value = "quota_exceeded", alternate = ["QUOTA_EXCEEDED"])
+ QUOTA_EXCEEDED, // The wallet has exceeded its spending quota.
+ @SerializedName(value = "restricted", alternate = ["RESTRICTED"])
+ RESTRICTED, // This public key is not allowed to do this operation.
+ @SerializedName(value = "unauthorized", alternate = ["UNAUTHORIZED"])
+ UNAUTHORIZED, // This public key has no wallet connected.
+ @SerializedName(value = "internal", alternate = ["INTERNAL"])
+ INTERNAL, // An internal error.
+ @SerializedName(value = "other", alternate = ["OTHER"])
+ OTHER, // Other error.
+ }
+}
+
+
+
+
+
+
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt
index 6f841deb6..ddcc98db1 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt
@@ -1,11 +1,14 @@
package com.vitorpamplona.amethyst.service.relays
+import com.vitorpamplona.amethyst.service.NostrDataSource
import com.vitorpamplona.amethyst.service.model.Event
import com.vitorpamplona.amethyst.service.model.EventInterface
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import java.util.UUID
/**
@@ -64,22 +67,43 @@ object Client : RelayPool.Listener {
RelayPool.sendFilterOnlyIfDisconnected()
}
- fun send(signedEvent: EventInterface, relay: String? = null) {
+ fun send(signedEvent: EventInterface, relay: String? = null, feedTypes: Set? = null, onDone: (() -> Unit)? = null) {
if (relay == null) {
RelayPool.send(signedEvent)
} else {
- val useConnectedRelay = relays.filter { it.url == relay }
+ val useConnectedRelayIfPresent = relays.filter { it.url == relay }
- if (useConnectedRelay.isNotEmpty()) {
- useConnectedRelay.forEach {
+ if (useConnectedRelayIfPresent.isNotEmpty()) {
+ useConnectedRelayIfPresent.forEach {
it.send(signedEvent)
}
} else {
/** temporary connection */
- Relay(relay, false, true, emptySet()).requestAndWatch() {
- it.send(signedEvent)
- it.disconnect()
- }
+ newSporadicRelay(relay, feedTypes,
+ onConnected = { relay ->
+ relay.send(signedEvent)
+ },
+ onDone = onDone
+ )
+ }
+ }
+ }
+
+ @OptIn(DelicateCoroutinesApi::class)
+ fun newSporadicRelay(url: String, feedTypes: Set?, onConnected: (Relay) -> Unit, onDone: (() -> Unit)?) {
+ val relay = Relay(url, true, true, feedTypes ?: emptySet())
+ RelayPool.addRelay(relay)
+
+ relay.requestAndWatch {
+ onConnected(relay)
+
+ GlobalScope.launch(Dispatchers.IO) {
+ delay(10000) // waits for a reply
+ relay.disconnect()
+ RelayPool.removeRelay(relay)
+
+ if (onDone != null)
+ onDone()
}
}
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt
index 87407844b..279c1bbf0 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt
@@ -3,8 +3,10 @@ package com.vitorpamplona.amethyst.service.relays
import android.util.Log
import com.google.gson.JsonElement
import com.vitorpamplona.amethyst.BuildConfig
+import com.vitorpamplona.amethyst.service.NostrAccountDataSource
import com.vitorpamplona.amethyst.service.model.Event
import com.vitorpamplona.amethyst.service.model.EventInterface
+import com.vitorpamplona.amethyst.service.model.LnZapPaymentResponseEvent
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
@@ -13,7 +15,7 @@ import okhttp3.WebSocketListener
import java.util.Date
enum class FeedType {
- FOLLOWS, PUBLIC_CHATS, PRIVATE_DMS, GLOBAL, SEARCH
+ FOLLOWS, PUBLIC_CHATS, PRIVATE_DMS, GLOBAL, SEARCH, WALLET_CONNECT
}
class Relay(
@@ -93,11 +95,18 @@ class Relay(
val type = msg[0].asString
val channel = msg[1].asString
+ Log.w("Relay", "New Message $type, $url, $channel, ${msg[2]}")
+
when (type) {
"EVENT" -> {
+ val event = Event.fromJson(msg[2], Client.lenient)
+ if (event.kind == LnZapPaymentResponseEvent.kind) {
+ println("This " + event.toJson())
+ }
+
// Log.w("Relay", "Relay onEVENT $url, $channel")
listeners.forEach {
- it.onEvent(this@Relay, channel, Event.fromJson(msg[2], Client.lenient))
+ it.onEvent(this@Relay, channel, event)
if (afterEOSE) {
it.onRelayStateChange(this@Relay, Type.EOSE, channel)
}
@@ -109,15 +118,15 @@ class Relay(
it.onRelayStateChange(this@Relay, Type.EOSE, channel)
}
"NOTICE" -> listeners.forEach {
- // Log.w("Relay", "Relay onNotice $url, $channel")
+ Log.w("Relay", "Relay onNotice $url, $channel")
it.onError(this@Relay, channel, Error("Relay sent notice: " + channel))
}
"OK" -> listeners.forEach {
- // Log.w("Relay", "Relay onOK $url, $channel")
+ Log.w("Relay", "Relay on OK $url, $channel")
it.onSendResponse(this@Relay, msg[1].asString, msg[2].asBoolean, msg[3].asString)
}
else -> listeners.forEach {
- // Log.w("Relay", "Relay something else $url, $channel")
+ Log.w("Relay", "Relay something else $url, $channel")
it.onError(
this@Relay,
channel,
@@ -213,6 +222,26 @@ class Relay(
}
}
+ fun sendUnregisteredFilter(request: JsonFilter, subscriptionId: String?) {
+ if (read) {
+ if (isConnected()) {
+ if (isReady) {
+ val request = """["REQ","${subscriptionId ?: ""}",${request.toJson(url)}]"""
+ println("FILTERSSENT $url $request")
+ socket?.send(request)
+ eventUploadCounterInBytes += request.bytesUsedInMemory()
+ afterEOSE = false
+ }
+ } else {
+ // waits 60 seconds to reconnect after disconnected.
+ if (Date().time / 1000 > closingTime + 60) {
+ // sends all filters after connection is successful.
+ requestAndWatch()
+ }
+ }
+ }
+ }
+
fun sendFilterOnlyIfDisconnected() {
if (socket == null) {
// waits 60 seconds to reconnect after disconnected.
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt
index c88352b6c..6f3a89df8 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt
@@ -15,6 +15,7 @@ import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
import com.vitorpamplona.amethyst.service.model.LnZapEvent
+import com.vitorpamplona.amethyst.service.model.PayInvoiceErrorResponse
import com.vitorpamplona.amethyst.service.model.ReportEvent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
@@ -80,7 +81,22 @@ class AccountViewModel(private val account: Account) : ViewModel() {
onSuccess = {
onProgress(0.7f)
if (account.hasWalletConnectSetup()) {
- account.sendZapPaymentRequestFor(it)
+ account.sendZapPaymentRequestFor(
+ bolt11 = it,
+ onResponse = {
+ val response = it.response()
+ if (response is PayInvoiceErrorResponse) {
+ onProgress(0.0f)
+ onError(
+ response.error?.message
+ ?: response.error?.code?.toString()
+ ?: "Error parsing error message"
+ )
+ } else {
+ // awaits for confirmation from Receiver or timeout.
+ }
+ },
+ )
onProgress(0.8f)
// Awaits for the event to come back to LocalCache.
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt
index 4d9bf93cc..040063733 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt
@@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn
import android.content.Intent
import android.net.Uri
+import android.widget.Toast
import androidx.compose.foundation.*
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.layout.*
@@ -59,6 +60,9 @@ import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource
import com.vitorpamplona.amethyst.service.model.BadgeDefinitionEvent
import com.vitorpamplona.amethyst.service.model.BadgeProfilesEvent
import com.vitorpamplona.amethyst.service.model.IdentityClaim
+import com.vitorpamplona.amethyst.service.model.LnZapPaymentResponseEvent
+import com.vitorpamplona.amethyst.service.model.PayInvoiceErrorResponse
+import com.vitorpamplona.amethyst.service.model.PayInvoiceSuccessResponse
import com.vitorpamplona.amethyst.service.model.ReportEvent
import com.vitorpamplona.amethyst.ui.actions.NewUserMetadataView
import com.vitorpamplona.amethyst.ui.components.DisplayNip05ProfileStatus
@@ -420,6 +424,7 @@ private fun DrawAdditionalInfo(baseUser: User, account: Account, accountViewMode
val uri = LocalUriHandler.current
val clipboardManager = LocalClipboardManager.current
val context = LocalContext.current
+ val scope = rememberCoroutineScope()
Row(verticalAlignment = Alignment.Bottom) {
user.bestDisplayName()?.let {
@@ -561,7 +566,28 @@ private fun DrawAdditionalInfo(baseUser: User, account: Account, accountViewMode
onSuccess = {
// pay directly
if (account.hasWalletConnectSetup()) {
- account.sendZapPaymentRequestFor(it)
+ account.sendZapPaymentRequestFor(it) {
+ val response = it.response()
+ if (response is PayInvoiceSuccessResponse) {
+ scope.launch {
+ Toast.makeText(
+ context,
+ "Payment Successful", // Turn this into a UI animation
+ Toast.LENGTH_LONG
+ ).show()
+ }
+ } else if (response is PayInvoiceErrorResponse) {
+ scope.launch {
+ Toast.makeText(
+ context,
+ response.error?.message
+ ?: response.error?.code?.toString()
+ ?: "Error parsing error message",
+ Toast.LENGTH_LONG
+ ).show()
+ }
+ }
+ }
} else {
runCatching {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("lightning:$it"))