From 6a69d3a5b217610078c37eec061c083832ca9ecb Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Mon, 8 Jun 2026 16:40:56 +0700 Subject: [PATCH] chore: update nostr sdk --- composeApp/build.gradle.kts | 2 +- .../su/reya/coop/NostrForegroundService.kt | 8 ++- shared/build.gradle.kts | 2 +- .../commonMain/kotlin/su/reya/coop/Nostr.kt | 53 ++++++++----------- .../kotlin/su/reya/coop/NostrViewModel.kt | 26 +++++---- .../commonMain/kotlin/su/reya/coop/Room.kt | 3 +- .../su/reya/coop/blossom/BlossomClient.kt | 2 +- 7 files changed, 44 insertions(+), 52 deletions(-) diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index a7fe5ee..1416469 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -24,7 +24,7 @@ kotlin { implementation(libs.jetbrains.navigation3.ui) implementation(libs.jetbrains.lifecycle.viewmodelNavigation3) 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-network-okhttp:3.4.0") implementation("io.github.kalinjul.easyqrscan:scanner:0.7.0") diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/NostrForegroundService.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/NostrForegroundService.kt index 9f5936f..0efbd5b 100644 --- a/composeApp/src/androidMain/kotlin/su/reya/coop/NostrForegroundService.kt +++ b/composeApp/src/androidMain/kotlin/su/reya/coop/NostrForegroundService.kt @@ -52,13 +52,17 @@ class NostrForegroundService : Service() { notificationJob = serviceScope.launch { try { Log.d("Coop", "Starting Nostr in background") - + // Create a database directory val dbDir = File(filesDir, "nostr") dbDir.mkdirs() // Initialize Nostr client - nostr.init(dbDir.absolutePath) + try { + nostr.init(dbDir.absolutePath) + } catch (e: Exception) { + throw IllegalStateException("Failed to initialize Nostr Client", e) + } // Connect to bootstrap relays nostr.connectBootstrapRelays() // Handle notifications diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index 9e2d8c8..8a5b486 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -33,7 +33,7 @@ kotlin { implementation(libs.androidx.lifecycle.runtimeCompose) implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2") 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") } androidMain.dependencies { diff --git a/shared/src/commonMain/kotlin/su/reya/coop/Nostr.kt b/shared/src/commonMain/kotlin/su/reya/coop/Nostr.kt index 33ecfe5..aad656c 100644 --- a/shared/src/commonMain/kotlin/su/reya/coop/Nostr.kt +++ b/shared/src/commonMain/kotlin/su/reya/coop/Nostr.kt @@ -43,18 +43,18 @@ import rust.nostr.sdk.RelayUrl import rust.nostr.sdk.ReqExitPolicy import rust.nostr.sdk.ReqTarget import rust.nostr.sdk.SendEventTarget +import rust.nostr.sdk.SignerAuthenticator import rust.nostr.sdk.SingleLetterTag import rust.nostr.sdk.SleepWhenIdle import rust.nostr.sdk.SubscribeAutoCloseOptions import rust.nostr.sdk.Tag -import rust.nostr.sdk.TagKind import rust.nostr.sdk.Timestamp import rust.nostr.sdk.UnsignedEvent import rust.nostr.sdk.UnwrappedGift import rust.nostr.sdk.extractRelayList -import rust.nostr.sdk.giftWrapAsync import rust.nostr.sdk.initLogger import rust.nostr.sdk.nip17ExtractRelayList +import rust.nostr.sdk.nip59MakeGiftWrapAsync import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds @@ -120,27 +120,23 @@ class Nostr { // Initialize the logger for nostr client initLogger(logLevel) - // Initialize the database and gossip instance + // Initialize configurations for nostr client val lmdb = NostrDatabase.lmdb(dbPath) val gossip = NostrGossip.inMemory() - - // Set the idle timeout for relays + val authenticator = SignerAuthenticator(signer) val idleTimeout = Duration.parse("5m") client = ClientBuilder() - .signer(signer) + .authenticator(authenticator) .database(lmdb) .gossip(gossip) .gossipConfig( GossipConfig() .noBackgroundRefresh() .fetchTimeout(Duration.parse("2s")) - .syncIdleTimeout(Duration.parse("100ms")) - .syncInitialTimeout(Duration.parse("100ms")) ) .verifySubscriptions(false) - .automaticAuthentication(true) .sleepWhenIdle(SleepWhenIdle.Enabled(idleTimeout)) .build() @@ -391,16 +387,15 @@ class Nostr { val currentUser = signer.currentUser ?: throw IllegalStateException("User not signed in") - // Ensure the rumor ID is set - val rumor = rumor.ensureId() + // Construct the room id val roomId = rumor.roomId() // Construct reference tags val tags = listOf( Tag.identifier(giftId.toHex()), Tag.event(rumor.id()!!), - Tag.reference(roomId.toString()), - Tag.custom(TagKind.Unknown("k"), listOf("14")) + Tag.custom("a", listOf(roomId.toString())), + Tag.custom("k", listOf("14")) ) // Set event kind @@ -408,8 +403,8 @@ class Nostr { val event = EventBuilder(kind, rumor.asJson()) .tags(tags) - .build(currentUser) - .signWithKeys(Keys.generate()) + .finalizeUnsigned(currentUser) + .signAsync(Keys.generate()) client?.database()?.saveEvent(event) } catch (e: Exception) { @@ -487,7 +482,7 @@ class Nostr { suspend fun createIdentity(keys: Keys, name: String, bio: String?, picture: String?) { // Send relay list event val relayList = getDefaultRelayList() - val relayListEvent = EventBuilder.relayList(relayList).signWithKeys(keys); + val relayListEvent = EventBuilder.relayList(relayList).finalizeAsync(keys); client?.sendEvent( event = relayListEvent, @@ -498,7 +493,7 @@ class Nostr { // Send messaging relay list event val msgRelayList = getDefaultMsgRelayList() - val msgRelayListEvent = EventBuilder.nip17RelayList(msgRelayList).signWithKeys(keys) + val msgRelayListEvent = EventBuilder.nip17RelayList(msgRelayList).finalizeAsync(keys) client?.sendEvent( event = msgRelayListEvent, @@ -509,7 +504,7 @@ class Nostr { // Send metadata event val metadata = 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( event = metadataEvent, @@ -519,8 +514,8 @@ class Nostr { // Send contact list event val defaultContact = - listOf(Contact(publicKey = PublicKey.parse("npub1j3rz3ndl902lya6ywxvy5c983lxs8mpukqnx4pa4lt5wrykwl5ys7wpw3x"))) - val contactListEvent = EventBuilder.contactList(defaultContact).signWithKeys(keys) + Contact(PublicKey.parse("npub1j3rz3ndl902lya6ywxvy5c983lxs8mpukqnx4pa4lt5wrykwl5ys7wpw3x")) + val contactListEvent = EventBuilder.contactList(listOf(defaultContact)).finalizeAsync(keys) client?.sendEvent( event = contactListEvent, @@ -546,7 +541,7 @@ class Nostr { picture = picture ?: record.picture ) val newMetadata = Metadata.fromRecord(newRecord) - val event = EventBuilder.metadata(newMetadata).signAsync(signer) + val event = EventBuilder.metadata(newMetadata).finalizeAsync(signer) client?.sendEvent( event = event, @@ -623,7 +618,7 @@ class Nostr { suspend fun setMsgRelays(urls: List) { try { - val event = EventBuilder.nip17RelayList(urls).signAsync(signer) + val event = EventBuilder.nip17RelayList(urls).finalizeAsync(signer) client?.sendEvent( event = event, @@ -787,7 +782,7 @@ class Nostr { // Add a subject tag if provided if (subject != null) { - tags.add(Tag.custom(TagKind.Subject, listOf(subject))) + tags.add(Tag.custom("subject", listOf(subject))) } // Add event tags for replies @@ -805,13 +800,9 @@ class Nostr { for (receiver in setOf(currentUser) + to) { // Construct the rumor event // NEVER SIGN this event with the current user signer - val rumor = EventBuilder - .privateMsgRumor(receiver = receiver, message = content) + val rumor = EventBuilder(Kind.fromStd(KindStandard.PRIVATE_DIRECT_MESSAGE), content) .tags(tags) - .allowSelfTagging() - .build(currentUser) - // Ensure the event ID is set - .ensureId() + .finalizeUnsigned(currentUser) // Emit the rumor to the chat screen if (receiver == currentUser) { @@ -819,12 +810,12 @@ class Nostr { } // Construct the gift wrap event - val gift = giftWrapAsync( + val gift = nip59MakeGiftWrapAsync( signer = signer, receiverPubkey = receiver, rumor = rumor, extraTags = listOf( - Tag.custom(TagKind.Unknown("k"), listOf("14")) + Tag.custom("k", listOf("14")) ) ) diff --git a/shared/src/commonMain/kotlin/su/reya/coop/NostrViewModel.kt b/shared/src/commonMain/kotlin/su/reya/coop/NostrViewModel.kt index edee2a8..2de4218 100644 --- a/shared/src/commonMain/kotlin/su/reya/coop/NostrViewModel.kt +++ b/shared/src/commonMain/kotlin/su/reya/coop/NostrViewModel.kt @@ -26,6 +26,8 @@ import rust.nostr.sdk.AsyncNostrSigner import rust.nostr.sdk.EventBuilder import rust.nostr.sdk.EventId import rust.nostr.sdk.Keys +import rust.nostr.sdk.Kind +import rust.nostr.sdk.KindStandard import rust.nostr.sdk.Metadata import rust.nostr.sdk.NostrConnect import rust.nostr.sdk.NostrConnectUri @@ -438,23 +440,20 @@ class NostrViewModel( contentType: String? = null ) { _isLoggedIn.value = true - try { - val keys = Keys.generate() - val secret = keys.secretKey().toBech32() - val avatarUrl = picture?.let { blossomUpload(it, contentType ?: "image/jpeg") } + val keys = Keys.generate() + val secret = keys.secretKey().toBech32() + + try { + val avatarUrl = picture?.let { blossomUpload(it, contentType ?: "image/jpeg") } // Create identity 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) { showError("Error: ${e.message}") } finally { + secretStore.set("user_signer", secret) _isLoggedIn.value = false + _signerRequired.value = false } } @@ -476,10 +475,10 @@ class NostrViewModel( try { val signer = createSigner(secret) nostr.setSigner(signer) - secretStore.set("user_signer", secret) } catch (e: Exception) { showError("Error: ${e.message}") } finally { + secretStore.set("user_signer", secret) _signerRequired.value = false _isLoggedIn.value = false } @@ -520,10 +519,9 @@ class NostrViewModel( val currentUser = nostr.signer.currentUser!! // Construct the rumor event - val rumor = EventBuilder - .privateMsgRumor(to.first(), "") + val rumor = EventBuilder(Kind.fromStd(KindStandard.PRIVATE_DIRECT_MESSAGE), "") .tags(to.map { Tag.publicKey(it) }) - .build(currentUser) + .finalizeUnsigned(currentUser) // Check if the room already exists val id = rumor.roomId() diff --git a/shared/src/commonMain/kotlin/su/reya/coop/Room.kt b/shared/src/commonMain/kotlin/su/reya/coop/Room.kt index b8672b8..816e0a6 100644 --- a/shared/src/commonMain/kotlin/su/reya/coop/Room.kt +++ b/shared/src/commonMain/kotlin/su/reya/coop/Room.kt @@ -6,7 +6,6 @@ import kotlinx.datetime.minus import kotlinx.datetime.number import kotlinx.datetime.toLocalDateTime import rust.nostr.sdk.PublicKey -import rust.nostr.sdk.TagKind import rust.nostr.sdk.Timestamp import rust.nostr.sdk.UnsignedEvent import kotlin.time.Clock @@ -37,7 +36,7 @@ data class Room( fun new(rumor: UnsignedEvent, userPubkey: PublicKey): Room { val id = rumor.roomId() 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 val pubkeys: MutableSet = mutableSetOf() diff --git a/shared/src/commonMain/kotlin/su/reya/coop/blossom/BlossomClient.kt b/shared/src/commonMain/kotlin/su/reya/coop/blossom/BlossomClient.kt index 6ba145b..b8d3018 100644 --- a/shared/src/commonMain/kotlin/su/reya/coop/blossom/BlossomClient.kt +++ b/shared/src/commonMain/kotlin/su/reya/coop/blossom/BlossomClient.kt @@ -75,7 +75,7 @@ class BlossomClient( signer: AsyncNostrSigner, authz: BlossomAuthorization ): HeaderValue { - val authEvent = EventBuilder.blossomAuth(authz).signAsync(signer) + val authEvent = EventBuilder.blossomAuth(authz).finalizeAsync(signer) val encodedAuth = Base64.encode(authEvent.asJson().toByteArray()) val value = "Nostr $encodedAuth" return HeaderValue(value)