chore: update nostr sdk

This commit is contained in:
2026-06-08 16:40:56 +07:00
parent 50b7f7a3f3
commit 6a69d3a5b2
7 changed files with 44 additions and 52 deletions

View File

@@ -24,7 +24,7 @@ kotlin {
implementation(libs.jetbrains.navigation3.ui) implementation(libs.jetbrains.navigation3.ui)
implementation(libs.jetbrains.lifecycle.viewmodelNavigation3) implementation(libs.jetbrains.lifecycle.viewmodelNavigation3)
implementation(libs.androidx.core.splashscreen) implementation(libs.androidx.core.splashscreen)
implementation("su.reya:nostr-sdk-kmp:0.2.3") implementation("su.reya:nostr-sdk-kmp:0.2.6")
implementation("io.coil-kt.coil3:coil-compose:3.4.0") implementation("io.coil-kt.coil3:coil-compose:3.4.0")
implementation("io.coil-kt.coil3:coil-network-okhttp:3.4.0") implementation("io.coil-kt.coil3:coil-network-okhttp:3.4.0")
implementation("io.github.kalinjul.easyqrscan:scanner:0.7.0") implementation("io.github.kalinjul.easyqrscan:scanner:0.7.0")

View File

@@ -58,7 +58,11 @@ class NostrForegroundService : Service() {
dbDir.mkdirs() dbDir.mkdirs()
// Initialize Nostr client // Initialize Nostr client
try {
nostr.init(dbDir.absolutePath) nostr.init(dbDir.absolutePath)
} catch (e: Exception) {
throw IllegalStateException("Failed to initialize Nostr Client", e)
}
// Connect to bootstrap relays // Connect to bootstrap relays
nostr.connectBootstrapRelays() nostr.connectBootstrapRelays()
// Handle notifications // Handle notifications

View File

@@ -33,7 +33,7 @@ kotlin {
implementation(libs.androidx.lifecycle.runtimeCompose) implementation(libs.androidx.lifecycle.runtimeCompose)
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.8.0") implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.8.0")
implementation("su.reya:nostr-sdk-kmp:0.2.3") implementation("su.reya:nostr-sdk-kmp:0.2.6")
implementation("com.squareup.okio:okio:3.16.2") implementation("com.squareup.okio:okio:3.16.2")
} }
androidMain.dependencies { androidMain.dependencies {

View File

@@ -43,18 +43,18 @@ import rust.nostr.sdk.RelayUrl
import rust.nostr.sdk.ReqExitPolicy import rust.nostr.sdk.ReqExitPolicy
import rust.nostr.sdk.ReqTarget import rust.nostr.sdk.ReqTarget
import rust.nostr.sdk.SendEventTarget import rust.nostr.sdk.SendEventTarget
import rust.nostr.sdk.SignerAuthenticator
import rust.nostr.sdk.SingleLetterTag import rust.nostr.sdk.SingleLetterTag
import rust.nostr.sdk.SleepWhenIdle import rust.nostr.sdk.SleepWhenIdle
import rust.nostr.sdk.SubscribeAutoCloseOptions import rust.nostr.sdk.SubscribeAutoCloseOptions
import rust.nostr.sdk.Tag import rust.nostr.sdk.Tag
import rust.nostr.sdk.TagKind
import rust.nostr.sdk.Timestamp import rust.nostr.sdk.Timestamp
import rust.nostr.sdk.UnsignedEvent import rust.nostr.sdk.UnsignedEvent
import rust.nostr.sdk.UnwrappedGift import rust.nostr.sdk.UnwrappedGift
import rust.nostr.sdk.extractRelayList import rust.nostr.sdk.extractRelayList
import rust.nostr.sdk.giftWrapAsync
import rust.nostr.sdk.initLogger import rust.nostr.sdk.initLogger
import rust.nostr.sdk.nip17ExtractRelayList import rust.nostr.sdk.nip17ExtractRelayList
import rust.nostr.sdk.nip59MakeGiftWrapAsync
import kotlin.time.Duration import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
@@ -120,27 +120,23 @@ class Nostr {
// Initialize the logger for nostr client // Initialize the logger for nostr client
initLogger(logLevel) initLogger(logLevel)
// Initialize the database and gossip instance // Initialize configurations for nostr client
val lmdb = NostrDatabase.lmdb(dbPath) val lmdb = NostrDatabase.lmdb(dbPath)
val gossip = NostrGossip.inMemory() val gossip = NostrGossip.inMemory()
val authenticator = SignerAuthenticator(signer)
// Set the idle timeout for relays
val idleTimeout = Duration.parse("5m") val idleTimeout = Duration.parse("5m")
client = client =
ClientBuilder() ClientBuilder()
.signer(signer) .authenticator(authenticator)
.database(lmdb) .database(lmdb)
.gossip(gossip) .gossip(gossip)
.gossipConfig( .gossipConfig(
GossipConfig() GossipConfig()
.noBackgroundRefresh() .noBackgroundRefresh()
.fetchTimeout(Duration.parse("2s")) .fetchTimeout(Duration.parse("2s"))
.syncIdleTimeout(Duration.parse("100ms"))
.syncInitialTimeout(Duration.parse("100ms"))
) )
.verifySubscriptions(false) .verifySubscriptions(false)
.automaticAuthentication(true)
.sleepWhenIdle(SleepWhenIdle.Enabled(idleTimeout)) .sleepWhenIdle(SleepWhenIdle.Enabled(idleTimeout))
.build() .build()
@@ -391,16 +387,15 @@ class Nostr {
val currentUser = val currentUser =
signer.currentUser ?: throw IllegalStateException("User not signed in") signer.currentUser ?: throw IllegalStateException("User not signed in")
// Ensure the rumor ID is set // Construct the room id
val rumor = rumor.ensureId()
val roomId = rumor.roomId() val roomId = rumor.roomId()
// Construct reference tags // Construct reference tags
val tags = listOf( val tags = listOf(
Tag.identifier(giftId.toHex()), Tag.identifier(giftId.toHex()),
Tag.event(rumor.id()!!), Tag.event(rumor.id()!!),
Tag.reference(roomId.toString()), Tag.custom("a", listOf(roomId.toString())),
Tag.custom(TagKind.Unknown("k"), listOf("14")) Tag.custom("k", listOf("14"))
) )
// Set event kind // Set event kind
@@ -408,8 +403,8 @@ class Nostr {
val event = EventBuilder(kind, rumor.asJson()) val event = EventBuilder(kind, rumor.asJson())
.tags(tags) .tags(tags)
.build(currentUser) .finalizeUnsigned(currentUser)
.signWithKeys(Keys.generate()) .signAsync(Keys.generate())
client?.database()?.saveEvent(event) client?.database()?.saveEvent(event)
} catch (e: Exception) { } catch (e: Exception) {
@@ -487,7 +482,7 @@ class Nostr {
suspend fun createIdentity(keys: Keys, name: String, bio: String?, picture: String?) { suspend fun createIdentity(keys: Keys, name: String, bio: String?, picture: String?) {
// Send relay list event // Send relay list event
val relayList = getDefaultRelayList() val relayList = getDefaultRelayList()
val relayListEvent = EventBuilder.relayList(relayList).signWithKeys(keys); val relayListEvent = EventBuilder.relayList(relayList).finalizeAsync(keys);
client?.sendEvent( client?.sendEvent(
event = relayListEvent, event = relayListEvent,
@@ -498,7 +493,7 @@ class Nostr {
// Send messaging relay list event // Send messaging relay list event
val msgRelayList = getDefaultMsgRelayList() val msgRelayList = getDefaultMsgRelayList()
val msgRelayListEvent = EventBuilder.nip17RelayList(msgRelayList).signWithKeys(keys) val msgRelayListEvent = EventBuilder.nip17RelayList(msgRelayList).finalizeAsync(keys)
client?.sendEvent( client?.sendEvent(
event = msgRelayListEvent, event = msgRelayListEvent,
@@ -509,7 +504,7 @@ class Nostr {
// Send metadata event // Send metadata event
val metadata = val metadata =
Metadata.fromRecord(MetadataRecord(displayName = name, about = bio, picture = picture)) Metadata.fromRecord(MetadataRecord(displayName = name, about = bio, picture = picture))
val metadataEvent = EventBuilder.metadata(metadata).signWithKeys(keys) val metadataEvent = EventBuilder.metadata(metadata).finalizeAsync(keys)
client?.sendEvent( client?.sendEvent(
event = metadataEvent, event = metadataEvent,
@@ -519,8 +514,8 @@ class Nostr {
// Send contact list event // Send contact list event
val defaultContact = val defaultContact =
listOf(Contact(publicKey = PublicKey.parse("npub1j3rz3ndl902lya6ywxvy5c983lxs8mpukqnx4pa4lt5wrykwl5ys7wpw3x"))) Contact(PublicKey.parse("npub1j3rz3ndl902lya6ywxvy5c983lxs8mpukqnx4pa4lt5wrykwl5ys7wpw3x"))
val contactListEvent = EventBuilder.contactList(defaultContact).signWithKeys(keys) val contactListEvent = EventBuilder.contactList(listOf(defaultContact)).finalizeAsync(keys)
client?.sendEvent( client?.sendEvent(
event = contactListEvent, event = contactListEvent,
@@ -546,7 +541,7 @@ class Nostr {
picture = picture ?: record.picture picture = picture ?: record.picture
) )
val newMetadata = Metadata.fromRecord(newRecord) val newMetadata = Metadata.fromRecord(newRecord)
val event = EventBuilder.metadata(newMetadata).signAsync(signer) val event = EventBuilder.metadata(newMetadata).finalizeAsync(signer)
client?.sendEvent( client?.sendEvent(
event = event, event = event,
@@ -623,7 +618,7 @@ class Nostr {
suspend fun setMsgRelays(urls: List<RelayUrl>) { suspend fun setMsgRelays(urls: List<RelayUrl>) {
try { try {
val event = EventBuilder.nip17RelayList(urls).signAsync(signer) val event = EventBuilder.nip17RelayList(urls).finalizeAsync(signer)
client?.sendEvent( client?.sendEvent(
event = event, event = event,
@@ -787,7 +782,7 @@ class Nostr {
// Add a subject tag if provided // Add a subject tag if provided
if (subject != null) { if (subject != null) {
tags.add(Tag.custom(TagKind.Subject, listOf(subject))) tags.add(Tag.custom("subject", listOf(subject)))
} }
// Add event tags for replies // Add event tags for replies
@@ -805,13 +800,9 @@ class Nostr {
for (receiver in setOf(currentUser) + to) { for (receiver in setOf(currentUser) + to) {
// Construct the rumor event // Construct the rumor event
// NEVER SIGN this event with the current user signer // NEVER SIGN this event with the current user signer
val rumor = EventBuilder val rumor = EventBuilder(Kind.fromStd(KindStandard.PRIVATE_DIRECT_MESSAGE), content)
.privateMsgRumor(receiver = receiver, message = content)
.tags(tags) .tags(tags)
.allowSelfTagging() .finalizeUnsigned(currentUser)
.build(currentUser)
// Ensure the event ID is set
.ensureId()
// Emit the rumor to the chat screen // Emit the rumor to the chat screen
if (receiver == currentUser) { if (receiver == currentUser) {
@@ -819,12 +810,12 @@ class Nostr {
} }
// Construct the gift wrap event // Construct the gift wrap event
val gift = giftWrapAsync( val gift = nip59MakeGiftWrapAsync(
signer = signer, signer = signer,
receiverPubkey = receiver, receiverPubkey = receiver,
rumor = rumor, rumor = rumor,
extraTags = listOf( extraTags = listOf(
Tag.custom(TagKind.Unknown("k"), listOf("14")) Tag.custom("k", listOf("14"))
) )
) )

View File

@@ -26,6 +26,8 @@ import rust.nostr.sdk.AsyncNostrSigner
import rust.nostr.sdk.EventBuilder import rust.nostr.sdk.EventBuilder
import rust.nostr.sdk.EventId import rust.nostr.sdk.EventId
import rust.nostr.sdk.Keys import rust.nostr.sdk.Keys
import rust.nostr.sdk.Kind
import rust.nostr.sdk.KindStandard
import rust.nostr.sdk.Metadata import rust.nostr.sdk.Metadata
import rust.nostr.sdk.NostrConnect import rust.nostr.sdk.NostrConnect
import rust.nostr.sdk.NostrConnectUri import rust.nostr.sdk.NostrConnectUri
@@ -438,23 +440,20 @@ class NostrViewModel(
contentType: String? = null contentType: String? = null
) { ) {
_isLoggedIn.value = true _isLoggedIn.value = true
try {
val keys = Keys.generate() val keys = Keys.generate()
val secret = keys.secretKey().toBech32() val secret = keys.secretKey().toBech32()
val avatarUrl = picture?.let { blossomUpload(it, contentType ?: "image/jpeg") }
try {
val avatarUrl = picture?.let { blossomUpload(it, contentType ?: "image/jpeg") }
// Create identity // Create identity
nostr.createIdentity(keys = keys, name = name, bio, picture = avatarUrl) nostr.createIdentity(keys = keys, name = name, bio, picture = avatarUrl)
// Save secret to the secret storage
secretStore.set("user_signer", secret)
// Set an empty secret state
_signerRequired.value = false
} catch (e: Exception) { } catch (e: Exception) {
showError("Error: ${e.message}") showError("Error: ${e.message}")
} finally { } finally {
secretStore.set("user_signer", secret)
_isLoggedIn.value = false _isLoggedIn.value = false
_signerRequired.value = false
} }
} }
@@ -476,10 +475,10 @@ class NostrViewModel(
try { try {
val signer = createSigner(secret) val signer = createSigner(secret)
nostr.setSigner(signer) nostr.setSigner(signer)
secretStore.set("user_signer", secret)
} catch (e: Exception) { } catch (e: Exception) {
showError("Error: ${e.message}") showError("Error: ${e.message}")
} finally { } finally {
secretStore.set("user_signer", secret)
_signerRequired.value = false _signerRequired.value = false
_isLoggedIn.value = false _isLoggedIn.value = false
} }
@@ -520,10 +519,9 @@ class NostrViewModel(
val currentUser = nostr.signer.currentUser!! val currentUser = nostr.signer.currentUser!!
// Construct the rumor event // Construct the rumor event
val rumor = EventBuilder val rumor = EventBuilder(Kind.fromStd(KindStandard.PRIVATE_DIRECT_MESSAGE), "")
.privateMsgRumor(to.first(), "")
.tags(to.map { Tag.publicKey(it) }) .tags(to.map { Tag.publicKey(it) })
.build(currentUser) .finalizeUnsigned(currentUser)
// Check if the room already exists // Check if the room already exists
val id = rumor.roomId() val id = rumor.roomId()

View File

@@ -6,7 +6,6 @@ import kotlinx.datetime.minus
import kotlinx.datetime.number import kotlinx.datetime.number
import kotlinx.datetime.toLocalDateTime import kotlinx.datetime.toLocalDateTime
import rust.nostr.sdk.PublicKey import rust.nostr.sdk.PublicKey
import rust.nostr.sdk.TagKind
import rust.nostr.sdk.Timestamp import rust.nostr.sdk.Timestamp
import rust.nostr.sdk.UnsignedEvent import rust.nostr.sdk.UnsignedEvent
import kotlin.time.Clock import kotlin.time.Clock
@@ -37,7 +36,7 @@ data class Room(
fun new(rumor: UnsignedEvent, userPubkey: PublicKey): Room { fun new(rumor: UnsignedEvent, userPubkey: PublicKey): Room {
val id = rumor.roomId() val id = rumor.roomId()
val createdAt = rumor.createdAt() val createdAt = rumor.createdAt()
val subject = rumor.tags().find(TagKind.Subject)?.content() val subject = rumor.tags().toVec().find { it.kind() == "subject" }?.content()
// Collect the author's public key and all public keys from tags // Collect the author's public key and all public keys from tags
val pubkeys: MutableSet<PublicKey> = mutableSetOf() val pubkeys: MutableSet<PublicKey> = mutableSetOf()

View File

@@ -75,7 +75,7 @@ class BlossomClient(
signer: AsyncNostrSigner, signer: AsyncNostrSigner,
authz: BlossomAuthorization authz: BlossomAuthorization
): HeaderValue { ): HeaderValue {
val authEvent = EventBuilder.blossomAuth(authz).signAsync(signer) val authEvent = EventBuilder.blossomAuth(authz).finalizeAsync(signer)
val encodedAuth = Base64.encode(authEvent.asJson().toByteArray()) val encodedAuth = Base64.encode(authEvent.asJson().toByteArray())
val value = "Nostr $encodedAuth" val value = "Nostr $encodedAuth"
return HeaderValue(value) return HeaderValue(value)