1. Moves AddressableNote index from NAddr to aTag format due to the presence of a preferred relay inside NAddr, which is an optional field and should not be part of the idex.

2. Parses relay information for Addressable Notes
This commit is contained in:
Vitor Pamplona
2023-03-07 13:48:05 -05:00
parent 5add9669f2
commit 1919dc5c81
19 changed files with 150 additions and 44 deletions

View File

@@ -77,7 +77,7 @@ object LocalCache {
} }
fun checkGetOrCreateNote(key: String): Note? { fun checkGetOrCreateNote(key: String): Note? {
if (key.startsWith("naddr1")) { if (key.startsWith("naddr1") || key.contains(":")) {
return checkGetOrCreateAddressableNote(key) return checkGetOrCreateAddressableNote(key)
} }
return try { return try {
@@ -120,7 +120,7 @@ object LocalCache {
fun checkGetOrCreateAddressableNote(key: String): AddressableNote? { fun checkGetOrCreateAddressableNote(key: String): AddressableNote? {
return try { return try {
val addr = ATag.parse(key) val addr = ATag.parse(key, null) // relay doesn't matter for the index.
if (addr != null) if (addr != null)
getOrCreateAddressableNote(addr) getOrCreateAddressableNote(addr)
else else
@@ -133,10 +133,12 @@ object LocalCache {
@Synchronized @Synchronized
fun getOrCreateAddressableNote(key: ATag): AddressableNote { fun getOrCreateAddressableNote(key: ATag): AddressableNote {
return addressables[key.toNAddr()] ?: run { // we can't use naddr here because naddr might include relay info and
// the preferred relay should not be part of the index.
return addressables[key.toTag()] ?: run {
val answer = AddressableNote(key) val answer = AddressableNote(key)
answer.author = checkGetOrCreateUser(key.pubKeyHex) answer.author = checkGetOrCreateUser(key.pubKeyHex)
addressables.put(key.toNAddr(), answer) addressables.put(key.toTag(), answer)
answer answer
} }
} }

View File

@@ -24,7 +24,7 @@ import kotlinx.coroutines.withContext
val tagSearch = Pattern.compile("(?:\\s|\\A)\\#\\[([0-9]+)\\]") val tagSearch = Pattern.compile("(?:\\s|\\A)\\#\\[([0-9]+)\\]")
class AddressableNote(val address: ATag): Note(address.toNAddr()) { class AddressableNote(val address: ATag): Note(address.toTag()) {
override fun idNote() = address.toNAddr() override fun idNote() = address.toNAddr()
override fun idDisplayNote() = idNote().toShortenHex() override fun idDisplayNote() = idNote().toShortenHex()
override fun address() = address override fun address() = address

View File

@@ -35,8 +35,8 @@ class ThreadAssembler {
@OptIn(ExperimentalTime::class) @OptIn(ExperimentalTime::class)
fun findThreadFor(noteId: String): Set<Note> { fun findThreadFor(noteId: String): Set<Note> {
val (result, elapsed) = measureTimedValue { val (result, elapsed) = measureTimedValue {
val note = if (noteId.startsWith("naddr")) { val note = if (noteId.contains(":")) {
val aTag = ATag.parse(noteId) val aTag = ATag.parse(noteId, null)
if (aTag != null) if (aTag != null)
LocalCache.getOrCreateAddressableNote(aTag) LocalCache.getOrCreateAddressableNote(aTag)
else else

View File

@@ -11,7 +11,7 @@ class Nip19 {
USER, NOTE, RELAY, ADDRESS USER, NOTE, RELAY, ADDRESS
} }
data class Return(val type: Type, val hex: String) data class Return(val type: Type, val hex: String, val relay: String?)
fun uriToRoute(uri: String?): Return? { fun uriToRoute(uri: String?): Return? {
try { try {
@@ -39,29 +39,39 @@ class Nip19 {
} }
private fun npub(bytes: ByteArray): Return { private fun npub(bytes: ByteArray): Return {
return Return(Type.USER, bytes.toHexKey()) return Return(Type.USER, bytes.toHexKey(), null)
} }
private fun note(bytes: ByteArray): Return { private fun note(bytes: ByteArray): Return {
return Return(Type.NOTE, bytes.toHexKey()); return Return(Type.NOTE, bytes.toHexKey(), null);
} }
private fun nprofile(bytes: ByteArray): Return? { private fun nprofile(bytes: ByteArray): Return? {
val hex = parseTLV(bytes) val tlv = parseTLV(bytes)
.get(NIP19TLVTypes.SPECIAL.id)
val hex = tlv.get(NIP19TLVTypes.SPECIAL.id)
?.get(0) ?.get(0)
?.toHexKey() ?: return null ?.toHexKey() ?: return null
return Return(Type.USER, hex) val relay = tlv.get(NIP19TLVTypes.RELAY.id)
?.get(0)
?.toString(Charsets.UTF_8)
return Return(Type.USER, hex, relay)
} }
private fun nevent(bytes: ByteArray): Return? { private fun nevent(bytes: ByteArray): Return? {
val hex = parseTLV(bytes) val tlv = parseTLV(bytes)
.get(NIP19TLVTypes.SPECIAL.id)
val hex = tlv.get(NIP19TLVTypes.SPECIAL.id)
?.get(0) ?.get(0)
?.toHexKey() ?: return null ?.toHexKey() ?: return null
return Return(Type.USER, hex) val relay = tlv.get(NIP19TLVTypes.RELAY.id)
?.get(0)
?.toString(Charsets.UTF_8)
return Return(Type.USER, hex, relay)
} }
private fun nrelay(bytes: ByteArray): Return? { private fun nrelay(bytes: ByteArray): Return? {
@@ -70,7 +80,7 @@ class Nip19 {
?.get(0) ?.get(0)
?.toString(Charsets.UTF_8) ?: return null ?.toString(Charsets.UTF_8) ?: return null
return Return(Type.RELAY, relayUrl) return Return(Type.RELAY, relayUrl, null)
} }
private fun naddr(bytes: ByteArray): Return? { private fun naddr(bytes: ByteArray): Return? {
@@ -92,7 +102,7 @@ class Nip19 {
?.get(0) ?.get(0)
?.let { toInt32(it) } ?.let { toInt32(it) }
return Return(Type.ADDRESS, "$kind:$author:$d") return Return(Type.ADDRESS, "$kind:$author:$d", relay)
} }
} }

View File

@@ -84,7 +84,7 @@ object NostrUserProfileDataSource: NostrDataSource("UserProfileFeed") {
types = FeedType.values().toSet(), types = FeedType.values().toSet(),
filter = JsonFilter( filter = JsonFilter(
kinds = listOf(BadgeProfilesEvent.kind), kinds = listOf(BadgeProfilesEvent.kind),
tags = mapOf("p" to listOf(it.pubkeyHex)), authors = listOf(it.pubkeyHex),
limit = 1 limit = 1
) )
) )

View File

@@ -11,35 +11,41 @@ import nostr.postr.Bech32
import nostr.postr.bechToBytes import nostr.postr.bechToBytes
import nostr.postr.toByteArray import nostr.postr.toByteArray
data class ATag(val kind: Int, val pubKeyHex: String, val dTag: String) { data class ATag(val kind: Int, val pubKeyHex: String, val dTag: String, val relay: String?) {
fun toTag() = "$kind:$pubKeyHex:$dTag" fun toTag() = "$kind:$pubKeyHex:$dTag"
fun toNAddr(): String { fun toNAddr(): String {
val kind = kind.toByteArray() val kind = kind.toByteArray()
val addr = pubKeyHex.toByteArray() val author = pubKeyHex.toByteArray()
val dTag = dTag.toByteArray(Charsets.UTF_8) val dTag = dTag.toByteArray(Charsets.UTF_8)
val relay = relay?.toByteArray(Charsets.UTF_8)
val fullArray = var fullArray =
byteArrayOf(NIP19TLVTypes.SPECIAL.id, dTag.size.toByte()) + dTag + byteArrayOf(NIP19TLVTypes.SPECIAL.id, dTag.size.toByte()) + dTag
byteArrayOf(NIP19TLVTypes.AUTHOR.id, addr.size.toByte()) + addr +
if (relay != null)
fullArray = fullArray + byteArrayOf(NIP19TLVTypes.RELAY.id, relay.size.toByte()) + relay
fullArray = fullArray +
byteArrayOf(NIP19TLVTypes.AUTHOR.id, author.size.toByte()) + author +
byteArrayOf(NIP19TLVTypes.KIND.id, kind.size.toByte()) + kind byteArrayOf(NIP19TLVTypes.KIND.id, kind.size.toByte()) + kind
return Bech32.encodeBytes(hrp = "naddr", fullArray, Bech32.Encoding.Bech32) return Bech32.encodeBytes(hrp = "naddr", fullArray, Bech32.Encoding.Bech32)
} }
companion object { companion object {
fun parse(address: String): ATag? { fun parse(address: String, relay: String?): ATag? {
return if (address.startsWith("naddr") || address.startsWith("nostr:naddr")) return if (address.startsWith("naddr") || address.startsWith("nostr:naddr"))
parseNAddr(address) parseNAddr(address)
else else
parseAtag(address) parseAtag(address, relay)
} }
fun parseAtag(atag: String): ATag? { fun parseAtag(atag: String, relay: String?): ATag? {
return try { return try {
val parts = atag.split(":") val parts = atag.split(":")
Hex.decode(parts[1]) Hex.decode(parts[1])
ATag(parts[0].toInt(), parts[1], parts[2]) ATag(parts[0].toInt(), parts[1], parts[2], relay)
} catch (t: Throwable) { } catch (t: Throwable) {
Log.w("ATag", "Error parsing A Tag: ${atag}: ${t.message}") Log.w("ATag", "Error parsing A Tag: ${atag}: ${t.message}")
null null
@@ -58,7 +64,7 @@ data class ATag(val kind: Int, val pubKeyHex: String, val dTag: String) {
val kind = tlv.get(NIP19TLVTypes.KIND.id)?.get(0)?.let { toInt32(it) } val kind = tlv.get(NIP19TLVTypes.KIND.id)?.get(0)?.let { toInt32(it) }
if (kind != null && author != null) if (kind != null && author != null)
return ATag(kind, author, d) return ATag(kind, author, d, relay)
} }
} catch (e: Throwable) { } catch (e: Throwable) {

View File

@@ -14,7 +14,12 @@ class BadgeAwardEvent(
sig: HexKey sig: HexKey
): Event(id, pubKey, createdAt, kind, tags, content, sig) { ): Event(id, pubKey, createdAt, kind, tags, content, sig) {
fun awardees() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) } fun awardees() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
fun awardDefinition() = tags.filter { it.firstOrNull() == "a" }.mapNotNull { it.getOrNull(1) }.mapNotNull { ATag.parse(it) } fun awardDefinition() = tags.filter { it.firstOrNull() == "a" }.mapNotNull {
val aTagValue = it.getOrNull(1)
val relay = it.getOrNull(2)
if (aTagValue != null) ATag.parse(aTagValue, relay) else null
}
companion object { companion object {
const val kind = 8 const val kind = 8

View File

@@ -14,7 +14,7 @@ class BadgeDefinitionEvent(
sig: HexKey sig: HexKey
): Event(id, pubKey, createdAt, kind, tags, content, sig) { ): Event(id, pubKey, createdAt, kind, tags, content, sig) {
fun dTag() = tags.filter { it.firstOrNull() == "d" }.mapNotNull { it.getOrNull(1) }.firstOrNull() ?: "" fun dTag() = tags.filter { it.firstOrNull() == "d" }.mapNotNull { it.getOrNull(1) }.firstOrNull() ?: ""
fun address() = ATag(kind, pubKey, dTag()) fun address() = ATag(kind, pubKey, dTag(), null)
fun name() = tags.filter { it.firstOrNull() == "name" }.mapNotNull { it.getOrNull(1) }.firstOrNull() fun name() = tags.filter { it.firstOrNull() == "name" }.mapNotNull { it.getOrNull(1) }.firstOrNull()
fun thumb() = tags.filter { it.firstOrNull() == "thumb" }.mapNotNull { it.getOrNull(1) }.firstOrNull() fun thumb() = tags.filter { it.firstOrNull() == "thumb" }.mapNotNull { it.getOrNull(1) }.firstOrNull()

View File

@@ -11,10 +11,15 @@ class BadgeProfilesEvent(
sig: HexKey sig: HexKey
): Event(id, pubKey, createdAt, kind, tags, content, sig) { ): Event(id, pubKey, createdAt, kind, tags, content, sig) {
fun badgeAwardEvents() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) } fun badgeAwardEvents() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
fun badgeAwardDefinitions() = tags.filter { it.firstOrNull() == "a" }.mapNotNull { it.getOrNull(1) }.mapNotNull { ATag.parse(it) } fun badgeAwardDefinitions() = tags.filter { it.firstOrNull() == "a" }.mapNotNull {
val aTagValue = it.getOrNull(1)
val relay = it.getOrNull(2)
if (aTagValue != null) ATag.parse(aTagValue, relay) else null
}
fun dTag() = tags.filter { it.firstOrNull() == "d" }.mapNotNull { it.getOrNull(1) }.firstOrNull() ?: "" fun dTag() = tags.filter { it.firstOrNull() == "d" }.mapNotNull { it.getOrNull(1) }.firstOrNull() ?: ""
fun address() = ATag(kind, pubKey, dTag()) fun address() = ATag(kind, pubKey, dTag(), null)
companion object { companion object {
const val kind = 30008 const val kind = 30008

View File

@@ -24,8 +24,12 @@ class LnZapEvent(
override fun taggedAddresses(): List<ATag> = tags override fun taggedAddresses(): List<ATag> = tags
.filter { it.firstOrNull() == "a" } .filter { it.firstOrNull() == "a" }
.mapNotNull { it.getOrNull(1) } .mapNotNull {
.mapNotNull { ATag.parse(it) } val aTagValue = it.getOrNull(1)
val relay = it.getOrNull(2)
if (aTagValue != null) ATag.parse(aTagValue, relay) else null
}
override fun amount(): BigDecimal? { override fun amount(): BigDecimal? {
return lnInvoice()?.let { LnInvoiceUtil.getAmountInSats(it) } return lnInvoice()?.let { LnInvoiceUtil.getAmountInSats(it) }

View File

@@ -16,7 +16,12 @@ class LnZapRequestEvent (
): Event(id, pubKey, createdAt, kind, tags, content, sig) { ): Event(id, pubKey, createdAt, kind, tags, content, sig) {
fun zappedPost() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) } fun zappedPost() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
fun zappedAuthor() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) } fun zappedAuthor() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
fun taggedAddresses() = tags.filter { it.firstOrNull() == "a" }.mapNotNull { it.getOrNull(1) }.mapNotNull { ATag.parse(it) } fun taggedAddresses() = tags.filter { it.firstOrNull() == "a" }.mapNotNull {
val aTagValue = it.getOrNull(1)
val relay = it.getOrNull(2)
if (aTagValue != null) ATag.parse(aTagValue, relay) else null
}
companion object { companion object {
const val kind = 9734 const val kind = 9734

View File

@@ -17,7 +17,7 @@ class LongTextNoteEvent(
fun mentions() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) } fun mentions() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
fun dTag() = tags.filter { it.firstOrNull() == "d" }.mapNotNull { it.getOrNull(1) }.firstOrNull() ?: "" fun dTag() = tags.filter { it.firstOrNull() == "d" }.mapNotNull { it.getOrNull(1) }.firstOrNull() ?: ""
fun address() = ATag(kind, pubKey, dTag()) fun address() = ATag(kind, pubKey, dTag(), null)
fun topics() = tags.filter { it.firstOrNull() == "t" }.mapNotNull { it.getOrNull(1) } fun topics() = tags.filter { it.firstOrNull() == "t" }.mapNotNull { it.getOrNull(1) }
fun title() = tags.filter { it.firstOrNull() == "title" }.mapNotNull { it.getOrNull(1) }.firstOrNull() fun title() = tags.filter { it.firstOrNull() == "title" }.mapNotNull { it.getOrNull(1) }.firstOrNull()

View File

@@ -17,7 +17,12 @@ class ReactionEvent (
fun originalPost() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) } fun originalPost() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
fun originalAuthor() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) } fun originalAuthor() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
fun taggedAddresses() = tags.filter { it.firstOrNull() == "a" }.mapNotNull { it.getOrNull(1) }.mapNotNull { ATag.parse(it) } fun taggedAddresses() = tags.filter { it.firstOrNull() == "a" }.mapNotNull {
val aTagValue = it.getOrNull(1)
val relay = it.getOrNull(2)
if (aTagValue != null) ATag.parse(aTagValue, relay) else null
}
companion object { companion object {
const val kind = 7 const val kind = 7

View File

@@ -48,7 +48,12 @@ class ReportEvent (
) )
} }
fun taggedAddresses() = tags.filter { it.firstOrNull() == "a" }.mapNotNull { it.getOrNull(1) }.mapNotNull { ATag.parse(it) } fun taggedAddresses() = tags.filter { it.firstOrNull() == "a" }.mapNotNull {
val aTagValue = it.getOrNull(1)
val relay = it.getOrNull(2)
if (aTagValue != null) ATag.parse(aTagValue, relay) else null
}
companion object { companion object {
const val kind = 1984 const val kind = 1984

View File

@@ -18,7 +18,12 @@ class RepostEvent (
fun boostedPost() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) } fun boostedPost() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
fun originalAuthor() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) } fun originalAuthor() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
fun taggedAddresses() = tags.filter { it.firstOrNull() == "a" }.mapNotNull { it.getOrNull(1) }.mapNotNull { ATag.parse(it) } fun taggedAddresses() = tags.filter { it.firstOrNull() == "a" }.mapNotNull {
val aTagValue = it.getOrNull(1)
val relay = it.getOrNull(2)
if (aTagValue != null) ATag.parse(aTagValue, relay) else null
}
fun containedPost() = try { fun containedPost() = try {
fromJson(content, Client.lenient) fromJson(content, Client.lenient)

View File

@@ -14,7 +14,13 @@ class TextNoteEvent(
sig: HexKey sig: HexKey
): Event(id, pubKey, createdAt, kind, tags, content, sig) { ): Event(id, pubKey, createdAt, kind, tags, content, sig) {
fun mentions() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) } fun mentions() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
fun taggedAddresses() = tags.filter { it.firstOrNull() == "a" }.mapNotNull { it.getOrNull(1) }.mapNotNull { ATag.parse(it) } fun taggedAddresses() = tags.filter { it.firstOrNull() == "a" }.mapNotNull {
val aTagValue = it.getOrNull(1)
val relay = it.getOrNull(2)
if (aTagValue != null) ATag.parse(aTagValue, relay) else null
}
fun replyTos() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) } fun replyTos() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
companion object { companion object {

View File

@@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.ui.components
import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.text.ClickableText
import androidx.compose.material.LocalTextStyle import androidx.compose.material.LocalTextStyle
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
@@ -31,7 +32,24 @@ fun ClickableRoute(
onClick = { navController.navigate(route) }, onClick = { navController.navigate(route) },
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary) style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
) )
} else if (nip19.type == Nip19.Type.ADDRESS) {
val noteBase = LocalCache.checkGetOrCreateAddressableNote(nip19.hex)
if (noteBase == null) {
Text(
"@${nip19.hex} "
)
} else { } else {
val noteState by noteBase.live().metadata.observeAsState()
val note = noteState?.note ?: return
ClickableText(
text = AnnotatedString("@${note.idDisplayNote()} "),
onClick = { navController.navigate("Note/${nip19.hex}") },
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
)
}
} else if (nip19.type == Nip19.Type.NOTE) {
val noteBase = LocalCache.getOrCreateNote(nip19.hex) val noteBase = LocalCache.getOrCreateNote(nip19.hex)
val noteState by noteBase.live().metadata.observeAsState() val noteState by noteBase.live().metadata.observeAsState()
val note = noteState?.note ?: return val note = noteState?.note ?: return
@@ -55,5 +73,9 @@ fun ClickableRoute(
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary) style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
) )
} }
} else {
Text(
"@${nip19.hex} "
)
} }
} }

View File

@@ -200,11 +200,13 @@ private fun isArabic(text: String): Boolean {
fun isBechLink(word: String): Boolean { fun isBechLink(word: String): Boolean {
return word.startsWith("nostr:", true) return word.startsWith("nostr:", true)
|| word.startsWith("npub1", true) || word.startsWith("npub1", true)
|| word.startsWith("naddr1", true)
|| word.startsWith("note1", true) || word.startsWith("note1", true)
|| word.startsWith("nprofile1", true) || word.startsWith("nprofile1", true)
|| word.startsWith("nevent1", true) || word.startsWith("nevent1", true)
|| word.startsWith("@npub1", true) || word.startsWith("@npub1", true)
|| word.startsWith("@note1", true) || word.startsWith("@note1", true)
|| word.startsWith("@addr1", true)
|| word.startsWith("@nprofile1", true) || word.startsWith("@nprofile1", true)
|| word.startsWith("@nevent1", true) || word.startsWith("@nevent1", true)
} }

View File

@@ -18,15 +18,39 @@ class NIP19ParserTest {
assertEquals("30023:d0debf9fb12def81f43d7c69429bb784812ac1e4d2d53a202db6aac7ea4b466c:guide-wireguard", result?.hex) assertEquals("30023:d0debf9fb12def81f43d7c69429bb784812ac1e4d2d53a202db6aac7ea4b466c:guide-wireguard", result?.hex)
} }
@Test
fun nAddrParse3() {
val result = Nip19().uriToRoute("naddr1qqyrswtyv5mnjv3sqy28wumn8ghj7un9d3shjtnyv9kh2uewd9hsygx3uczxts4hwue9ayfn7ggq62anzstde2qs749pm9tx2csuthhpjvpsgqqqw4rs8pmj38")
assertEquals(Nip19.Type.ADDRESS, result?.type)
assertEquals("30023:d1e60465c2b777325e9133f2100d2bb31416dca810f54a1d95665621c5dee193:89de7920", result?.hex)
assertEquals("wss://relay.damus.io", result?.relay)
}
@Test
fun nAddrATagParse3() {
val address = ATag.parse("30023:d1e60465c2b777325e9133f2100d2bb31416dca810f54a1d95665621c5dee193:89de7920", "wss://relay.damus.io")
assertEquals(30023, address?.kind)
assertEquals("d1e60465c2b777325e9133f2100d2bb31416dca810f54a1d95665621c5dee193", address?.pubKeyHex)
assertEquals("89de7920", address?.dTag)
assertEquals("wss://relay.damus.io" , address?.relay)
assertEquals("naddr1qqyrswtyv5mnjv3sqy28wumn8ghj7un9d3shjtnyv9kh2uewd9hsygx3uczxts4hwue9ayfn7ggq62anzstde2qs749pm9tx2csuthhpjvpsgqqqw4rs8pmj38", address?.toNAddr())
}
@Test @Test
fun nAddrFormatter() { fun nAddrFormatter() {
val address = ATag(30023, "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c", "" ) val address = ATag(30023, "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c", "", null)
assertEquals("naddr1qqqqygzxpsj7dqha57pjk5k37gkn6g4nzakewtmqmnwryyhd3jfwlpgxtspsgqqqw4rs3xyxus", address.toNAddr()) assertEquals("naddr1qqqqygzxpsj7dqha57pjk5k37gkn6g4nzakewtmqmnwryyhd3jfwlpgxtspsgqqqw4rs3xyxus", address.toNAddr())
} }
@Test @Test
fun nAddrFormatter2() { fun nAddrFormatter2() {
val address = ATag(30023, "d0debf9fb12def81f43d7c69429bb784812ac1e4d2d53a202db6aac7ea4b466c", "guide-wireguard" ) val address = ATag(30023, "d0debf9fb12def81f43d7c69429bb784812ac1e4d2d53a202db6aac7ea4b466c", "guide-wireguard", null)
assertEquals("naddr1qq8kwatfv3jj6amfwfjkwatpwfjqygxsm6lelvfda7qlg0tud9pfhduysy4vrexj65azqtdk4tr75j6xdspsgqqqw4rsg32ag8", address.toNAddr()) assertEquals("naddr1qq8kwatfv3jj6amfwfjkwatpwfjqygxsm6lelvfda7qlg0tud9pfhduysy4vrexj65azqtdk4tr75j6xdspsgqqqw4rsg32ag8", address.toNAddr())
} }
@Test
fun nAddrFormatter3() {
val address = ATag(30023, "d1e60465c2b777325e9133f2100d2bb31416dca810f54a1d95665621c5dee193", "89de7920", "wss://relay.damus.io")
assertEquals("naddr1qqyrswtyv5mnjv3sqy28wumn8ghj7un9d3shjtnyv9kh2uewd9hsygx3uczxts4hwue9ayfn7ggq62anzstde2qs749pm9tx2csuthhpjvpsgqqqw4rs8pmj38", address.toNAddr())
}
} }