Support for the new JSON-based Wallet Connect API

This commit is contained in:
Vitor Pamplona
2023-04-24 18:51:57 -04:00
parent 3d93fe3d18
commit f8319c720a
15 changed files with 85 additions and 78 deletions
@@ -167,17 +167,24 @@ class Account(
return zapPaymentRequest != null
}
fun sendZapPaymentRequestFor(bolt11: String, onResponse: (LnZapPaymentResponseEvent) -> Unit) {
fun sendZapPaymentRequestFor(bolt11: String, onResponse: (Response?) -> Unit) {
if (!isWriteable()) return
zapPaymentRequest?.let {
val event = LnZapPaymentRequestEvent.create(bolt11, it.pubKeyHex, it.secret?.toByteArray() ?: loggedIn.privKey!!)
zapPaymentRequest?.let { nip47 ->
val event = LnZapPaymentRequestEvent.create(bolt11, nip47.pubKeyHex, nip47.secret?.toByteArray() ?: loggedIn.privKey!!)
val wcListener = NostrLnZapPaymentResponseDataSource(it.pubKeyHex, loggedIn.pubKey.toHexKey(), event.id)
val wcListener = NostrLnZapPaymentResponseDataSource(nip47.pubKeyHex, event.pubKey, event.id)
wcListener.start()
LocalCache.consume(event, onResponse)
Client.send(event, it.relayUri, wcListener.feedTypes) {
LocalCache.consume(event) {
// After the response is received.
val privKey = nip47.secret?.toByteArray()
if (privKey != null) {
onResponse(it.response(privKey, event.pubKey.toByteArray()))
}
}
Client.send(event, nip47.relayUri, wcListener.feedTypes) {
wcListener.destroy()
}
}
@@ -13,8 +13,8 @@ import com.vitorpamplona.amethyst.service.model.ReactionEvent
import com.vitorpamplona.amethyst.service.model.ReportEvent
import com.vitorpamplona.amethyst.service.model.RepostEvent
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
import com.vitorpamplona.amethyst.service.relays.COMMON_FEED_TYPES
import com.vitorpamplona.amethyst.service.relays.EOSEAccount
import com.vitorpamplona.amethyst.service.relays.FeedType
import com.vitorpamplona.amethyst.service.relays.JsonFilter
import com.vitorpamplona.amethyst.service.relays.TypedFilter
@@ -25,7 +25,7 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
fun createAccountContactListFilter(): TypedFilter {
return TypedFilter(
types = FeedType.values().toSet(),
types = COMMON_FEED_TYPES,
filter = JsonFilter(
kinds = listOf(ContactListEvent.kind),
authors = listOf(account.userProfile().pubkeyHex),
@@ -36,7 +36,7 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
fun createAccountMetadataFilter(): TypedFilter {
return TypedFilter(
types = FeedType.values().toSet(),
types = COMMON_FEED_TYPES,
filter = JsonFilter(
kinds = listOf(MetadataEvent.kind),
authors = listOf(account.userProfile().pubkeyHex),
@@ -47,7 +47,7 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
fun createAccountAcceptedAwardsFilter(): TypedFilter {
return TypedFilter(
types = FeedType.values().toSet(),
types = COMMON_FEED_TYPES,
filter = JsonFilter(
kinds = listOf(BadgeProfilesEvent.kind),
authors = listOf(account.userProfile().pubkeyHex),
@@ -58,7 +58,7 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
fun createAccountBookmarkListFilter(): TypedFilter {
return TypedFilter(
types = FeedType.values().toSet(),
types = COMMON_FEED_TYPES,
filter = JsonFilter(
kinds = listOf(BookmarkListEvent.kind),
authors = listOf(account.userProfile().pubkeyHex),
@@ -69,7 +69,7 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
fun createAccountReportsFilter(): TypedFilter {
return TypedFilter(
types = FeedType.values().toSet(),
types = COMMON_FEED_TYPES,
filter = JsonFilter(
kinds = listOf(ReportEvent.kind),
authors = listOf(account.userProfile().pubkeyHex),
@@ -79,7 +79,7 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
}
fun createNotificationFilter() = TypedFilter(
types = FeedType.values().toSet(),
types = COMMON_FEED_TYPES,
filter = JsonFilter(
kinds = listOf(
TextNoteEvent.kind,
@@ -5,6 +5,7 @@ import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
import com.vitorpamplona.amethyst.service.relays.COMMON_FEED_TYPES
import com.vitorpamplona.amethyst.service.relays.EOSEAccount
import com.vitorpamplona.amethyst.service.relays.FeedType
import com.vitorpamplona.amethyst.service.relays.JsonFilter
@@ -43,7 +44,7 @@ object NostrChatroomListDataSource : NostrDataSource("MailBoxFeed") {
)
fun createMyChannelsFilter() = TypedFilter(
types = FeedType.values().toSet(), // Metadata comes from any relay
types = COMMON_FEED_TYPES, // Metadata comes from any relay
filter = JsonFilter(
kinds = listOf(ChannelCreateEvent.kind),
ids = account.followingChannels.toList(),
@@ -54,7 +55,7 @@ object NostrChatroomListDataSource : NostrDataSource("MailBoxFeed") {
fun createLastChannelInfoFilter(): List<TypedFilter> {
return account.followingChannels.map {
TypedFilter(
types = FeedType.values().toSet(), // Metadata comes from any relay
types = COMMON_FEED_TYPES, // Metadata comes from any relay
filter = JsonFilter(
kinds = listOf(ChannelMetadataEvent.kind),
tags = mapOf("e" to listOf(it)),
@@ -4,7 +4,7 @@ import androidx.compose.ui.text.capitalize
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
import com.vitorpamplona.amethyst.service.relays.FeedType
import com.vitorpamplona.amethyst.service.relays.COMMON_FEED_TYPES
import com.vitorpamplona.amethyst.service.relays.JsonFilter
import com.vitorpamplona.amethyst.service.relays.TypedFilter
@@ -15,7 +15,7 @@ object NostrHashtagDataSource : NostrDataSource("SingleHashtagFeed") {
val hashToLoad = hashtagToWatch ?: return null
return TypedFilter(
types = FeedType.values().toSet(),
types = COMMON_FEED_TYPES,
filter = JsonFilter(
tags = mapOf(
"t" to listOf(
@@ -2,7 +2,7 @@ package com.vitorpamplona.amethyst.service
import com.vitorpamplona.amethyst.model.decodePublicKey
import com.vitorpamplona.amethyst.service.model.*
import com.vitorpamplona.amethyst.service.relays.FeedType
import com.vitorpamplona.amethyst.service.relays.COMMON_FEED_TYPES
import com.vitorpamplona.amethyst.service.relays.JsonFilter
import com.vitorpamplona.amethyst.service.relays.TypedFilter
import nostr.postr.bechToBytes
@@ -34,7 +34,7 @@ object NostrSearchEventOrUserDataSource : NostrDataSource("SingleEventFeed") {
return listOfNotNull(
hexToWatch?.let {
TypedFilter(
types = FeedType.values().toSet(),
types = COMMON_FEED_TYPES,
filter = JsonFilter(
ids = listOfNotNull(hexToWatch)
)
@@ -42,7 +42,7 @@ object NostrSearchEventOrUserDataSource : NostrDataSource("SingleEventFeed") {
},
hexToWatch?.let {
TypedFilter(
types = FeedType.values().toSet(),
types = COMMON_FEED_TYPES,
filter = JsonFilter(
kinds = listOf(MetadataEvent.kind),
authors = listOfNotNull(hexToWatch)
@@ -50,7 +50,7 @@ object NostrSearchEventOrUserDataSource : NostrDataSource("SingleEventFeed") {
)
},
TypedFilter(
types = FeedType.values().toSet(),
types = COMMON_FEED_TYPES,
filter = JsonFilter(
kinds = listOf(MetadataEvent.kind),
search = mySearchString,
@@ -58,7 +58,7 @@ object NostrSearchEventOrUserDataSource : NostrDataSource("SingleEventFeed") {
)
),
TypedFilter(
types = FeedType.values().toSet(),
types = COMMON_FEED_TYPES,
filter = JsonFilter(
kinds = listOf(TextNoteEvent.kind, LongTextNoteEvent.kind, PollNoteEvent.kind, ChannelMetadataEvent.kind, ChannelCreateEvent.kind, ChannelMessageEvent.kind),
search = mySearchString,
@@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.service
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
import com.vitorpamplona.amethyst.service.relays.COMMON_FEED_TYPES
import com.vitorpamplona.amethyst.service.relays.FeedType
import com.vitorpamplona.amethyst.service.relays.JsonFilter
import com.vitorpamplona.amethyst.service.relays.TypedFilter
@@ -40,7 +41,7 @@ object NostrSingleChannelDataSource : NostrDataSource("SingleChannelFeed") {
// downloads linked events to this event.
return TypedFilter(
types = FeedType.values().toSet(),
types = COMMON_FEED_TYPES,
filter = JsonFilter(
kinds = listOf(ChannelCreateEvent.kind),
ids = interestedEvents.toList()
@@ -3,8 +3,8 @@ package com.vitorpamplona.amethyst.service
import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.model.*
import com.vitorpamplona.amethyst.service.relays.COMMON_FEED_TYPES
import com.vitorpamplona.amethyst.service.relays.EOSETime
import com.vitorpamplona.amethyst.service.relays.FeedType
import com.vitorpamplona.amethyst.service.relays.JsonFilter
import com.vitorpamplona.amethyst.service.relays.TypedFilter
@@ -22,7 +22,7 @@ object NostrSingleEventDataSource : NostrDataSource("SingleEventFeed") {
return addressesToWatch.mapNotNull {
it.address()?.let { aTag ->
TypedFilter(
types = FeedType.values().toSet(),
types = COMMON_FEED_TYPES,
filter = JsonFilter(
kinds = listOf(
TextNoteEvent.kind, LongTextNoteEvent.kind,
@@ -49,7 +49,7 @@ object NostrSingleEventDataSource : NostrDataSource("SingleEventFeed") {
return addressesToWatch.mapNotNull {
it.address()?.let { aTag ->
TypedFilter(
types = FeedType.values().toSet(),
types = COMMON_FEED_TYPES,
filter = JsonFilter(
kinds = listOf(aTag.kind),
tags = mapOf("d" to listOf(aTag.dTag)),
@@ -69,7 +69,7 @@ object NostrSingleEventDataSource : NostrDataSource("SingleEventFeed") {
return reactionsToWatch.map {
TypedFilter(
types = FeedType.values().toSet(),
types = COMMON_FEED_TYPES,
filter = JsonFilter(
kinds = listOf(
TextNoteEvent.kind,
@@ -108,7 +108,7 @@ object NostrSingleEventDataSource : NostrDataSource("SingleEventFeed") {
// downloads linked events to this event.
return listOf(
TypedFilter(
types = FeedType.values().toSet(),
types = COMMON_FEED_TYPES,
filter = JsonFilter(
kinds = listOf(
TextNoteEvent.kind, LongTextNoteEvent.kind, PollNoteEvent.kind,
@@ -3,8 +3,8 @@ package com.vitorpamplona.amethyst.service
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.model.MetadataEvent
import com.vitorpamplona.amethyst.service.model.ReportEvent
import com.vitorpamplona.amethyst.service.relays.COMMON_FEED_TYPES
import com.vitorpamplona.amethyst.service.relays.EOSETime
import com.vitorpamplona.amethyst.service.relays.FeedType
import com.vitorpamplona.amethyst.service.relays.JsonFilter
import com.vitorpamplona.amethyst.service.relays.TypedFilter
@@ -16,7 +16,7 @@ object NostrSingleUserDataSource : NostrDataSource("SingleUserFeed") {
return usersToWatch.filter { it.info?.latestMetadata == null }.map {
TypedFilter(
types = FeedType.values().toSet(),
types = COMMON_FEED_TYPES,
filter = JsonFilter(
kinds = listOf(MetadataEvent.kind),
authors = listOf(it.pubkeyHex),
@@ -31,7 +31,7 @@ object NostrSingleUserDataSource : NostrDataSource("SingleUserFeed") {
return usersToWatch.map {
TypedFilter(
types = FeedType.values().toSet(),
types = COMMON_FEED_TYPES,
filter = JsonFilter(
kinds = listOf(ReportEvent.kind),
tags = mapOf("p" to listOf(it.pubkeyHex)),
@@ -1,7 +1,7 @@
package com.vitorpamplona.amethyst.service
import com.vitorpamplona.amethyst.model.ThreadAssembler
import com.vitorpamplona.amethyst.service.relays.FeedType
import com.vitorpamplona.amethyst.service.relays.COMMON_FEED_TYPES
import com.vitorpamplona.amethyst.service.relays.JsonFilter
import com.vitorpamplona.amethyst.service.relays.TypedFilter
@@ -18,7 +18,7 @@ object NostrThreadDataSource : NostrDataSource("SingleThreadFeed") {
.ifEmpty { null } ?: return null
return TypedFilter(
types = FeedType.values().toSet(),
types = COMMON_FEED_TYPES,
filter = JsonFilter(
ids = eventsToLoad.map { it.substring(0, 8) }
)
@@ -3,7 +3,7 @@ package com.vitorpamplona.amethyst.service
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.model.*
import com.vitorpamplona.amethyst.service.relays.FeedType
import com.vitorpamplona.amethyst.service.relays.COMMON_FEED_TYPES
import com.vitorpamplona.amethyst.service.relays.JsonFilter
import com.vitorpamplona.amethyst.service.relays.TypedFilter
@@ -22,7 +22,7 @@ object NostrUserProfileDataSource : NostrDataSource("UserProfileFeed") {
fun createUserInfoFilter() = user?.let {
TypedFilter(
types = FeedType.values().toSet(),
types = COMMON_FEED_TYPES,
filter = JsonFilter(
kinds = listOf(MetadataEvent.kind),
authors = listOf(it.pubkeyHex),
@@ -33,7 +33,7 @@ object NostrUserProfileDataSource : NostrDataSource("UserProfileFeed") {
fun createUserPostsFilter() = user?.let {
TypedFilter(
types = FeedType.values().toSet(),
types = COMMON_FEED_TYPES,
filter = JsonFilter(
kinds = listOf(TextNoteEvent.kind, RepostEvent.kind, LongTextNoteEvent.kind, PollNoteEvent.kind),
authors = listOf(it.pubkeyHex),
@@ -44,7 +44,7 @@ object NostrUserProfileDataSource : NostrDataSource("UserProfileFeed") {
fun createUserReceivedZapsFilter() = user?.let {
TypedFilter(
types = FeedType.values().toSet(),
types = COMMON_FEED_TYPES,
filter = JsonFilter(
kinds = listOf(LnZapEvent.kind),
tags = mapOf("p" to listOf(it.pubkeyHex))
@@ -54,7 +54,7 @@ object NostrUserProfileDataSource : NostrDataSource("UserProfileFeed") {
fun createFollowFilter() = user?.let {
TypedFilter(
types = FeedType.values().toSet(),
types = COMMON_FEED_TYPES,
filter = JsonFilter(
kinds = listOf(ContactListEvent.kind),
authors = listOf(it.pubkeyHex),
@@ -65,7 +65,7 @@ object NostrUserProfileDataSource : NostrDataSource("UserProfileFeed") {
fun createFollowersFilter() = user?.let {
TypedFilter(
types = FeedType.values().toSet(),
types = COMMON_FEED_TYPES,
filter = JsonFilter(
kinds = listOf(ContactListEvent.kind),
tags = mapOf("p" to listOf(it.pubkeyHex))
@@ -75,7 +75,7 @@ object NostrUserProfileDataSource : NostrDataSource("UserProfileFeed") {
fun createAcceptedAwardsFilter() = user?.let {
TypedFilter(
types = FeedType.values().toSet(),
types = COMMON_FEED_TYPES,
filter = JsonFilter(
kinds = listOf(BadgeProfilesEvent.kind),
authors = listOf(it.pubkeyHex),
@@ -86,7 +86,7 @@ object NostrUserProfileDataSource : NostrDataSource("UserProfileFeed") {
fun createBookmarksFilter() = user?.let {
TypedFilter(
types = FeedType.values().toSet(),
types = COMMON_FEED_TYPES,
filter = JsonFilter(
kinds = listOf(BookmarkListEvent.kind),
authors = listOf(it.pubkeyHex),
@@ -97,7 +97,7 @@ object NostrUserProfileDataSource : NostrDataSource("UserProfileFeed") {
fun createReceivedAwardsFilter() = user?.let {
TypedFilter(
types = FeedType.values().toSet(),
types = COMMON_FEED_TYPES,
filter = JsonFilter(
kinds = listOf(BadgeAwardEvent.kind),
tags = mapOf("p" to listOf(it.pubkeyHex)),
@@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.service.model
import android.util.Log
import com.google.gson.annotations.SerializedName
import com.vitorpamplona.amethyst.model.HexKey
import nostr.postr.Utils
class LnZapPaymentResponseEvent(
id: HexKey,
@@ -16,9 +17,26 @@ class LnZapPaymentResponseEvent(
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 {
fun decrypt(privKey: ByteArray, pubKey: ByteArray): String? {
return try {
val sharedSecret = Utils.getSharedSecret(privKey, pubKey)
val retVal = Utils.decrypt(content, sharedSecret)
if (retVal.startsWith(PrivateDmEvent.nip18Advertisement)) {
retVal.substring(16)
} else {
retVal
}
} catch (e: Exception) {
Log.w("PrivateDM", "Error decrypting the message ${e.message}")
null
}
}
fun response(privKey: ByteArray, pubKey: ByteArray): Response? = try {
if (content.isNotEmpty()) {
gson.fromJson(content, Response::class.java)
gson.fromJson(decrypt(privKey, pubKey), Response::class.java)
} else {
null
}
@@ -95,6 +95,10 @@ object Client : RelayPool.Listener {
RelayPool.addRelay(relay)
relay.requestAndWatch {
allSubscriptions().forEach {
relay.sendFilter(requestId = it)
}
onConnected(relay)
GlobalScope.launch(Dispatchers.IO) {
@@ -5,7 +5,6 @@ import com.google.gson.JsonElement
import com.vitorpamplona.amethyst.BuildConfig
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
@@ -17,6 +16,8 @@ enum class FeedType {
FOLLOWS, PUBLIC_CHATS, PRIVATE_DMS, GLOBAL, SEARCH, WALLET_CONNECT
}
val COMMON_FEED_TYPES = setOf(FeedType.FOLLOWS, FeedType.PUBLIC_CHATS, FeedType.PRIVATE_DMS, FeedType.GLOBAL)
class Relay(
var url: String,
var read: Boolean = true,
@@ -94,14 +95,11 @@ class Relay(
val type = msg[0].asString
val channel = msg[1].asString
Log.w("Relay", "New Message $type, $url, $channel, ${msg[2]}")
// 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 {
@@ -117,15 +115,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 on OK $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,
@@ -205,7 +203,7 @@ class Relay(
if (filters.isNotEmpty()) {
val request =
"""["REQ","$requestId",${filters.take(40).joinToString(",") { it.filter.toJson(url) }}]"""
// println("FILTERSSENT $url $request")
println("FILTERSSENT $url $request")
socket?.send(request)
eventUploadCounterInBytes += request.bytesUsedInMemory()
afterEOSE = false
@@ -221,26 +219,6 @@ 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.
@@ -83,8 +83,7 @@ class AccountViewModel(private val account: Account) : ViewModel() {
if (account.hasWalletConnectSetup()) {
account.sendZapPaymentRequestFor(
bolt11 = it,
onResponse = {
val response = it.response()
onResponse = { response ->
if (response is PayInvoiceErrorResponse) {
onProgress(0.0f)
onError(
@@ -93,7 +92,7 @@ class AccountViewModel(private val account: Account) : ViewModel() {
?: "Error parsing error message"
)
} else {
// awaits for confirmation from Receiver or timeout.
onProgress(0.99f)
}
}
)
@@ -565,8 +565,7 @@ private fun DrawAdditionalInfo(baseUser: User, account: Account, accountViewMode
onSuccess = {
// pay directly
if (account.hasWalletConnectSetup()) {
account.sendZapPaymentRequestFor(it) {
val response = it.response()
account.sendZapPaymentRequestFor(it) { response ->
if (response is PayInvoiceSuccessResponse) {
scope.launch {
Toast.makeText(