diff --git a/Cargo.lock b/Cargo.lock index 121d9bc..b75a70e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -491,7 +491,7 @@ dependencies = [ [[package]] name = "bitkitcore" -version = "0.1.42" +version = "0.1.46" dependencies = [ "android_logger", "async-trait", @@ -510,7 +510,8 @@ dependencies = [ "log", "once_cell", "openssl", - "pubky", + "pubky 0.6.0", + "pubky-app-specs", "r2d2", "r2d2_sqlite", "rand 0.8.5", @@ -2417,6 +2418,12 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -3038,6 +3045,31 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "pubky" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f7fe978ba733fdb614320e9b1f3910cfecf0509530511c8ba12cc8d32f8bc8" +dependencies = [ + "anyhow", + "base64 0.22.1", + "bytes", + "cookie", + "cookie_store", + "flume", + "futures-lite", + "futures-util", + "log", + "pkarr", + "pubky-common 0.5.4", + "reqwest", + "thiserror 2.0.17", + "tokio", + "tracing", + "url", + "wasm-bindgen-futures", +] + [[package]] name = "pubky" version = "0.6.0" @@ -3052,7 +3084,7 @@ dependencies = [ "httpdate", "log", "pkarr", - "pubky-common", + "pubky-common 0.6.0", "reqwest", "thiserror 2.0.17", "tokio", @@ -3062,6 +3094,46 @@ dependencies = [ "web-time", ] +[[package]] +name = "pubky-app-specs" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "768489143c50693e617f2efd6b2498be0c8b0ac6280b4ba59067a3afc8b01477" +dependencies = [ + "base32", + "blake3", + "js-sys", + "mime", + "pubky 0.5.4", + "serde", + "serde-wasm-bindgen", + "serde_json", + "url", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "pubky-common" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f078584beadafe3bc8669bfd6b245f10f42b8bea24e7ccbff0e09886e310ba9b" +dependencies = [ + "argon2", + "base32", + "blake3", + "crypto_secretbox", + "ed25519-dalek", + "js-sys", + "once_cell", + "pkarr", + "postcard", + "pubky-timestamp", + "rand 0.9.2", + "serde", + "thiserror 2.0.17", +] + [[package]] name = "pubky-common" version = "0.6.0" @@ -3830,6 +3902,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde-xml-rs" version = "0.8.2" diff --git a/Cargo.toml b/Cargo.toml index 9b9cbe3..3bf1a07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bitkitcore" -version = "0.1.42" +version = "0.1.46" edition = "2021" [lib] @@ -38,6 +38,7 @@ bdk = { version = "0.30.2", features = ["all-keys"] } base64 = "0.22" log = "0.4" pubky = "0.6.0" +pubky-app-specs = "0.4" # Bluetooth (for Android JNI initialization) # btleplug is also pulled in transitively by trezor-connect-rs diff --git a/Package.swift b/Package.swift index 417a194..854a88c 100644 --- a/Package.swift +++ b/Package.swift @@ -3,8 +3,8 @@ import PackageDescription -let tag = "v0.1.42" -let checksum = "2c7c01511b5c08efe97ce3fa5b417f5d0c8afc1ae2a40b4a099ae1e834b189a0" +let tag = "v0.1.46" +let checksum = "2d0518b22404db4e4e2c78bf7d676f29893454b5a0c311dfb470d48127faf754" let url = "https://github.com/synonymdev/bitkit-core/releases/download/\(tag)/BitkitCore.xcframework.zip" let package = Package( diff --git a/bindings/android/gradle.properties b/bindings/android/gradle.properties index 8582c45..0c81396 100644 --- a/bindings/android/gradle.properties +++ b/bindings/android/gradle.properties @@ -3,4 +3,4 @@ android.useAndroidX=true android.enableJetifier=true kotlin.code.style=official group=com.synonym -version=0.1.42 +version=0.1.46 diff --git a/bindings/android/lib/src/main/jniLibs/arm64-v8a/libbitkitcore.so b/bindings/android/lib/src/main/jniLibs/arm64-v8a/libbitkitcore.so index f9a2231..5f3f500 100755 Binary files a/bindings/android/lib/src/main/jniLibs/arm64-v8a/libbitkitcore.so and b/bindings/android/lib/src/main/jniLibs/arm64-v8a/libbitkitcore.so differ diff --git a/bindings/android/lib/src/main/jniLibs/arm64-v8a/libpubky_app_specs-c7af973a4ebe5bd3.so b/bindings/android/lib/src/main/jniLibs/arm64-v8a/libpubky_app_specs-c7af973a4ebe5bd3.so new file mode 100755 index 0000000..5071975 Binary files /dev/null and b/bindings/android/lib/src/main/jniLibs/arm64-v8a/libpubky_app_specs-c7af973a4ebe5bd3.so differ diff --git a/bindings/android/lib/src/main/jniLibs/armeabi-v7a/libbitkitcore.so b/bindings/android/lib/src/main/jniLibs/armeabi-v7a/libbitkitcore.so index 14bf42b..722c122 100755 Binary files a/bindings/android/lib/src/main/jniLibs/armeabi-v7a/libbitkitcore.so and b/bindings/android/lib/src/main/jniLibs/armeabi-v7a/libbitkitcore.so differ diff --git a/bindings/android/lib/src/main/jniLibs/armeabi-v7a/libpubky_app_specs-21d6fd696265b368.so b/bindings/android/lib/src/main/jniLibs/armeabi-v7a/libpubky_app_specs-21d6fd696265b368.so new file mode 100755 index 0000000..8daad22 Binary files /dev/null and b/bindings/android/lib/src/main/jniLibs/armeabi-v7a/libpubky_app_specs-21d6fd696265b368.so differ diff --git a/bindings/android/lib/src/main/jniLibs/x86/libbitkitcore.so b/bindings/android/lib/src/main/jniLibs/x86/libbitkitcore.so index 42c2258..b7ad282 100755 Binary files a/bindings/android/lib/src/main/jniLibs/x86/libbitkitcore.so and b/bindings/android/lib/src/main/jniLibs/x86/libbitkitcore.so differ diff --git a/bindings/android/lib/src/main/jniLibs/x86/libpubky_app_specs-a233000ce3838d6c.so b/bindings/android/lib/src/main/jniLibs/x86/libpubky_app_specs-a233000ce3838d6c.so new file mode 100755 index 0000000..e3b6340 Binary files /dev/null and b/bindings/android/lib/src/main/jniLibs/x86/libpubky_app_specs-a233000ce3838d6c.so differ diff --git a/bindings/android/lib/src/main/jniLibs/x86_64/libbitkitcore.so b/bindings/android/lib/src/main/jniLibs/x86_64/libbitkitcore.so index be63c0a..4a7a2a1 100755 Binary files a/bindings/android/lib/src/main/jniLibs/x86_64/libbitkitcore.so and b/bindings/android/lib/src/main/jniLibs/x86_64/libbitkitcore.so differ diff --git a/bindings/android/lib/src/main/jniLibs/x86_64/libpubky_app_specs-994ca791cc83d53a.so b/bindings/android/lib/src/main/jniLibs/x86_64/libpubky_app_specs-994ca791cc83d53a.so new file mode 100755 index 0000000..eeaa46a Binary files /dev/null and b/bindings/android/lib/src/main/jniLibs/x86_64/libpubky_app_specs-994ca791cc83d53a.so differ diff --git a/bindings/android/lib/src/main/kotlin/com/synonym/bitkitcore/bitkitcore.android.kt b/bindings/android/lib/src/main/kotlin/com/synonym/bitkitcore/bitkitcore.android.kt index ce2bb24..5defca6 100644 --- a/bindings/android/lib/src/main/kotlin/com/synonym/bitkitcore/bitkitcore.android.kt +++ b/bindings/android/lib/src/main/kotlin/com/synonym/bitkitcore/bitkitcore.android.kt @@ -1384,6 +1384,10 @@ internal typealias UniffiVTableCallbackInterfaceTrezorUiCallbackUniffiByValue = + + + + @@ -1506,9 +1510,15 @@ internal object IntegrityCheckingUniffiLib : Library { if (uniffi_bitkitcore_checksum_func_estimate_order_fee_full() != 13361.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (uniffi_bitkitcore_checksum_func_fetch_pubky_contacts() != 18744.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (uniffi_bitkitcore_checksum_func_fetch_pubky_file() != 24890.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (uniffi_bitkitcore_checksum_func_fetch_pubky_profile() != 19709.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (uniffi_bitkitcore_checksum_func_generate_mnemonic() != 19292.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -1900,9 +1910,15 @@ internal object IntegrityCheckingUniffiLib : Library { external fun uniffi_bitkitcore_checksum_func_estimate_order_fee_full( ): Short @JvmStatic + external fun uniffi_bitkitcore_checksum_func_fetch_pubky_contacts( + ): Short + @JvmStatic external fun uniffi_bitkitcore_checksum_func_fetch_pubky_file( ): Short @JvmStatic + external fun uniffi_bitkitcore_checksum_func_fetch_pubky_profile( + ): Short + @JvmStatic external fun uniffi_bitkitcore_checksum_func_generate_mnemonic( ): Short @JvmStatic @@ -2491,10 +2507,18 @@ internal object UniffiLib : Library { `options`: RustBufferByValue, ): Long @JvmStatic + external fun uniffi_bitkitcore_fn_func_fetch_pubky_contacts( + `publicKey`: RustBufferByValue, + ): Long + @JvmStatic external fun uniffi_bitkitcore_fn_func_fetch_pubky_file( `uri`: RustBufferByValue, ): Long @JvmStatic + external fun uniffi_bitkitcore_fn_func_fetch_pubky_profile( + `publicKey`: RustBufferByValue, + ): Long + @JvmStatic external fun uniffi_bitkitcore_fn_func_generate_mnemonic( `wordCount`: RustBufferByValue, uniffiCallStatus: UniffiRustCallStatus, @@ -6197,6 +6221,59 @@ public object FfiConverterTypePubkyAuth: FfiConverterRustBuffer { +public object FfiConverterTypePubkyProfile: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): PubkyProfile { + return PubkyProfile( + FfiConverterString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalString.read(buf), + FfiConverterOptionalSequenceTypePubkyProfileLink.read(buf), + FfiConverterOptionalString.read(buf), + ) + } + + override fun allocationSize(value: PubkyProfile): ULong = ( + FfiConverterString.allocationSize(value.`name`) + + FfiConverterOptionalString.allocationSize(value.`bio`) + + FfiConverterOptionalString.allocationSize(value.`image`) + + FfiConverterOptionalSequenceTypePubkyProfileLink.allocationSize(value.`links`) + + FfiConverterOptionalString.allocationSize(value.`status`) + ) + + override fun write(value: PubkyProfile, buf: ByteBuffer) { + FfiConverterString.write(value.`name`, buf) + FfiConverterOptionalString.write(value.`bio`, buf) + FfiConverterOptionalString.write(value.`image`, buf) + FfiConverterOptionalSequenceTypePubkyProfileLink.write(value.`links`, buf) + FfiConverterOptionalString.write(value.`status`, buf) + } +} + + + + +public object FfiConverterTypePubkyProfileLink: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): PubkyProfileLink { + return PubkyProfileLink( + FfiConverterString.read(buf), + FfiConverterString.read(buf), + ) + } + + override fun allocationSize(value: PubkyProfileLink): ULong = ( + FfiConverterString.allocationSize(value.`title`) + + FfiConverterString.allocationSize(value.`url`) + ) + + override fun write(value: PubkyProfileLink, buf: ByteBuffer) { + FfiConverterString.write(value.`title`, buf) + FfiConverterString.write(value.`url`, buf) + } +} + + + + public object FfiConverterTypeSweepResult: FfiConverterRustBuffer { override fun read(buf: ByteBuffer): SweepResult { return SweepResult( @@ -8057,6 +8134,13 @@ public object FfiConverterTypePubkyError : FfiConverterRustBuffer PubkyException.ResolutionFailed( FfiConverterString.read(buf), ) + 5 -> PubkyException.FetchFailed( + FfiConverterString.read(buf), + ) + 6 -> PubkyException.ProfileNotFound() + 7 -> PubkyException.ProfileParseFailed( + FfiConverterString.read(buf), + ) else -> throw RuntimeException("invalid error enum value, something is very wrong!!") } } @@ -8082,6 +8166,20 @@ public object FfiConverterTypePubkyError : FfiConverterRustBuffer ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.`reason`) + ) + is PubkyException.ProfileNotFound -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) + is PubkyException.ProfileParseFailed -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterString.allocationSize(value.`reason`) + ) } } @@ -8106,6 +8204,20 @@ public object FfiConverterTypePubkyError : FfiConverterRustBuffer { + buf.putInt(5) + FfiConverterString.write(value.`reason`, buf) + Unit + } + is PubkyException.ProfileNotFound -> { + buf.putInt(6) + Unit + } + is PubkyException.ProfileParseFailed -> { + buf.putInt(7) + FfiConverterString.write(value.`reason`, buf) + Unit + } }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } } } @@ -9881,6 +9993,35 @@ public object FfiConverterOptionalSequenceTypeIManualRefund: FfiConverterRustBuf +public object FfiConverterOptionalSequenceTypePubkyProfileLink: FfiConverterRustBuffer?> { + override fun read(buf: ByteBuffer): List? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterSequenceTypePubkyProfileLink.read(buf) + } + + override fun allocationSize(value: List?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterSequenceTypePubkyProfileLink.allocationSize(value) + } + } + + override fun write(value: List?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterSequenceTypePubkyProfileLink.write(value, buf) + } + } +} + + + + public object FfiConverterOptionalMapStringString: FfiConverterRustBuffer?> { override fun read(buf: ByteBuffer): Map? { if (buf.get().toInt() == 0) { @@ -10260,6 +10401,31 @@ public object FfiConverterSequenceTypePreActivityMetadata: FfiConverterRustBuffe +public object FfiConverterSequenceTypePubkyProfileLink: FfiConverterRustBuffer> { + override fun read(buf: ByteBuffer): List { + val len = buf.getInt() + return List(len) { + FfiConverterTypePubkyProfileLink.read(buf) + } + } + + override fun allocationSize(value: List): ULong { + val sizeForLength = 4UL + val sizeForItems = value.sumOf { FfiConverterTypePubkyProfileLink.allocationSize(it) } + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: ByteBuffer) { + buf.putInt(value.size) + value.iterator().forEach { + FfiConverterTypePubkyProfileLink.write(it, buf) + } + } +} + + + + public object FfiConverterSequenceTypeTransactionDetails: FfiConverterRustBuffer> { override fun read(buf: ByteBuffer): List { val len = buf.getInt() @@ -10932,6 +11098,23 @@ public suspend fun `estimateOrderFeeFull`(`lspBalanceSat`: kotlin.ULong, `channe ) } +@Throws(PubkyException::class, kotlin.coroutines.cancellation.CancellationException::class) +public suspend fun `fetchPubkyContacts`(`publicKey`: kotlin.String): List { + return uniffiRustCallAsync( + UniffiLib.uniffi_bitkitcore_fn_func_fetch_pubky_contacts( + FfiConverterString.lower(`publicKey`), + ), + { future, callback, continuation -> UniffiLib.ffi_bitkitcore_rust_future_poll_rust_buffer(future, callback, continuation) }, + { future, continuation -> UniffiLib.ffi_bitkitcore_rust_future_complete_rust_buffer(future, continuation) }, + { future -> UniffiLib.ffi_bitkitcore_rust_future_free_rust_buffer(future) }, + { future -> UniffiLib.ffi_bitkitcore_rust_future_cancel_rust_buffer(future) }, + // lift function + { FfiConverterSequenceString.lift(it) }, + // Error FFI converter + PubkyExceptionErrorHandler, + ) +} + @Throws(PubkyException::class, kotlin.coroutines.cancellation.CancellationException::class) public suspend fun `fetchPubkyFile`(`uri`: kotlin.String): kotlin.ByteArray { return uniffiRustCallAsync( @@ -10949,6 +11132,23 @@ public suspend fun `fetchPubkyFile`(`uri`: kotlin.String): kotlin.ByteArray { ) } +@Throws(PubkyException::class, kotlin.coroutines.cancellation.CancellationException::class) +public suspend fun `fetchPubkyProfile`(`publicKey`: kotlin.String): PubkyProfile { + return uniffiRustCallAsync( + UniffiLib.uniffi_bitkitcore_fn_func_fetch_pubky_profile( + FfiConverterString.lower(`publicKey`), + ), + { future, callback, continuation -> UniffiLib.ffi_bitkitcore_rust_future_poll_rust_buffer(future, callback, continuation) }, + { future, continuation -> UniffiLib.ffi_bitkitcore_rust_future_complete_rust_buffer(future, continuation) }, + { future -> UniffiLib.ffi_bitkitcore_rust_future_free_rust_buffer(future) }, + { future -> UniffiLib.ffi_bitkitcore_rust_future_cancel_rust_buffer(future) }, + // lift function + { FfiConverterTypePubkyProfile.lift(it) }, + // Error FFI converter + PubkyExceptionErrorHandler, + ) +} + @Throws(AddressException::class) public fun `generateMnemonic`(`wordCount`: WordCount?): kotlin.String { return FfiConverterString.lift(uniffiRustCallWithError(AddressExceptionErrorHandler) { uniffiRustCallStatus -> diff --git a/bindings/android/lib/src/main/kotlin/com/synonym/bitkitcore/bitkitcore.common.kt b/bindings/android/lib/src/main/kotlin/com/synonym/bitkitcore/bitkitcore.common.kt index 64d03f8..4b1e29c 100644 --- a/bindings/android/lib/src/main/kotlin/com/synonym/bitkitcore/bitkitcore.common.kt +++ b/bindings/android/lib/src/main/kotlin/com/synonym/bitkitcore/bitkitcore.common.kt @@ -1150,6 +1150,29 @@ public data class PubkyAuth ( +@kotlinx.serialization.Serializable +public data class PubkyProfile ( + val `name`: kotlin.String, + val `bio`: kotlin.String?, + val `image`: kotlin.String?, + val `links`: List?, + val `status`: kotlin.String? +) { + public companion object +} + + + +@kotlinx.serialization.Serializable +public data class PubkyProfileLink ( + val `title`: kotlin.String, + val `url`: kotlin.String +) { + public companion object +} + + + @kotlinx.serialization.Serializable public data class SweepResult ( /** @@ -2649,6 +2672,26 @@ public sealed class PubkyException: kotlin.Exception() { get() = "reason=${ `reason` }" } + public class FetchFailed( + public val `reason`: kotlin.String, + ) : PubkyException() { + override val message: String + get() = "reason=${ `reason` }" + } + + public class ProfileNotFound( + ) : PubkyException() { + override val message: String + get() = "" + } + + public class ProfileParseFailed( + public val `reason`: kotlin.String, + ) : PubkyException() { + override val message: String + get() = "reason=${ `reason` }" + } + } @@ -3200,6 +3243,10 @@ public enum class WordCount { + + + + diff --git a/bindings/ios/BitkitCore.xcframework.zip b/bindings/ios/BitkitCore.xcframework.zip index 2f8e45b..076e229 100644 Binary files a/bindings/ios/BitkitCore.xcframework.zip and b/bindings/ios/BitkitCore.xcframework.zip differ diff --git a/bindings/ios/BitkitCore.xcframework/Info.plist b/bindings/ios/BitkitCore.xcframework/Info.plist index 478a88f..b7357e0 100644 --- a/bindings/ios/BitkitCore.xcframework/Info.plist +++ b/bindings/ios/BitkitCore.xcframework/Info.plist @@ -10,7 +10,7 @@ HeadersPath Headers LibraryIdentifier - ios-arm64 + ios-arm64-simulator LibraryPath libbitkitcore.a SupportedArchitectures @@ -19,6 +19,8 @@ SupportedPlatform ios + SupportedPlatformVariant + simulator BinaryPath @@ -26,7 +28,7 @@ HeadersPath Headers LibraryIdentifier - ios-arm64-simulator + ios-arm64 LibraryPath libbitkitcore.a SupportedArchitectures @@ -35,8 +37,6 @@ SupportedPlatform ios - SupportedPlatformVariant - simulator CFBundlePackageType diff --git a/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/Headers/bitkitcoreFFI.h b/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/Headers/bitkitcoreFFI.h index e2134e2..de38032 100644 --- a/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/Headers/bitkitcoreFFI.h +++ b/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/Headers/bitkitcoreFFI.h @@ -600,11 +600,21 @@ uint64_t uniffi_bitkitcore_fn_func_estimate_order_fee(uint64_t lsp_balance_sat, uint64_t uniffi_bitkitcore_fn_func_estimate_order_fee_full(uint64_t lsp_balance_sat, uint32_t channel_expiry_weeks, RustBuffer options ); #endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_FETCH_PUBKY_CONTACTS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_FETCH_PUBKY_CONTACTS +uint64_t uniffi_bitkitcore_fn_func_fetch_pubky_contacts(RustBuffer public_key +); +#endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_FETCH_PUBKY_FILE #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_FETCH_PUBKY_FILE uint64_t uniffi_bitkitcore_fn_func_fetch_pubky_file(RustBuffer uri ); #endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_FETCH_PUBKY_PROFILE +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_FETCH_PUBKY_PROFILE +uint64_t uniffi_bitkitcore_fn_func_fetch_pubky_profile(RustBuffer public_key +); +#endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_GENERATE_MNEMONIC #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_GENERATE_MNEMONIC RustBuffer uniffi_bitkitcore_fn_func_generate_mnemonic(RustBuffer word_count, RustCallStatus *_Nonnull out_status @@ -1508,12 +1518,24 @@ uint16_t uniffi_bitkitcore_checksum_func_estimate_order_fee(void #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_ESTIMATE_ORDER_FEE_FULL uint16_t uniffi_bitkitcore_checksum_func_estimate_order_fee_full(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_FETCH_PUBKY_CONTACTS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_FETCH_PUBKY_CONTACTS +uint16_t uniffi_bitkitcore_checksum_func_fetch_pubky_contacts(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_FETCH_PUBKY_FILE #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_FETCH_PUBKY_FILE uint16_t uniffi_bitkitcore_checksum_func_fetch_pubky_file(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_FETCH_PUBKY_PROFILE +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_FETCH_PUBKY_PROFILE +uint16_t uniffi_bitkitcore_checksum_func_fetch_pubky_profile(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_GENERATE_MNEMONIC diff --git a/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/libbitkitcore.a b/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/libbitkitcore.a index 180ba37..809be5c 100644 Binary files a/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/libbitkitcore.a and b/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/libbitkitcore.a differ diff --git a/bindings/ios/BitkitCore.xcframework/ios-arm64/Headers/bitkitcoreFFI.h b/bindings/ios/BitkitCore.xcframework/ios-arm64/Headers/bitkitcoreFFI.h index e2134e2..de38032 100644 --- a/bindings/ios/BitkitCore.xcframework/ios-arm64/Headers/bitkitcoreFFI.h +++ b/bindings/ios/BitkitCore.xcframework/ios-arm64/Headers/bitkitcoreFFI.h @@ -600,11 +600,21 @@ uint64_t uniffi_bitkitcore_fn_func_estimate_order_fee(uint64_t lsp_balance_sat, uint64_t uniffi_bitkitcore_fn_func_estimate_order_fee_full(uint64_t lsp_balance_sat, uint32_t channel_expiry_weeks, RustBuffer options ); #endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_FETCH_PUBKY_CONTACTS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_FETCH_PUBKY_CONTACTS +uint64_t uniffi_bitkitcore_fn_func_fetch_pubky_contacts(RustBuffer public_key +); +#endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_FETCH_PUBKY_FILE #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_FETCH_PUBKY_FILE uint64_t uniffi_bitkitcore_fn_func_fetch_pubky_file(RustBuffer uri ); #endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_FETCH_PUBKY_PROFILE +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_FETCH_PUBKY_PROFILE +uint64_t uniffi_bitkitcore_fn_func_fetch_pubky_profile(RustBuffer public_key +); +#endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_GENERATE_MNEMONIC #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_GENERATE_MNEMONIC RustBuffer uniffi_bitkitcore_fn_func_generate_mnemonic(RustBuffer word_count, RustCallStatus *_Nonnull out_status @@ -1508,12 +1518,24 @@ uint16_t uniffi_bitkitcore_checksum_func_estimate_order_fee(void #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_ESTIMATE_ORDER_FEE_FULL uint16_t uniffi_bitkitcore_checksum_func_estimate_order_fee_full(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_FETCH_PUBKY_CONTACTS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_FETCH_PUBKY_CONTACTS +uint16_t uniffi_bitkitcore_checksum_func_fetch_pubky_contacts(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_FETCH_PUBKY_FILE #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_FETCH_PUBKY_FILE uint16_t uniffi_bitkitcore_checksum_func_fetch_pubky_file(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_FETCH_PUBKY_PROFILE +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_FETCH_PUBKY_PROFILE +uint16_t uniffi_bitkitcore_checksum_func_fetch_pubky_profile(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_GENERATE_MNEMONIC diff --git a/bindings/ios/BitkitCore.xcframework/ios-arm64/libbitkitcore.a b/bindings/ios/BitkitCore.xcframework/ios-arm64/libbitkitcore.a index 7e1c4c2..d3df61a 100644 Binary files a/bindings/ios/BitkitCore.xcframework/ios-arm64/libbitkitcore.a and b/bindings/ios/BitkitCore.xcframework/ios-arm64/libbitkitcore.a differ diff --git a/bindings/ios/bitkitcore.swift b/bindings/ios/bitkitcore.swift index ce6871b..e11c432 100644 --- a/bindings/ios/bitkitcore.swift +++ b/bindings/ios/bitkitcore.swift @@ -7271,6 +7271,174 @@ public func FfiConverterTypePubkyAuth_lower(_ value: PubkyAuth) -> RustBuffer { } +public struct PubkyProfile { + public var name: String + public var bio: String? + public var image: String? + public var links: [PubkyProfileLink]? + public var status: String? + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(name: String, bio: String?, image: String?, links: [PubkyProfileLink]?, status: String?) { + self.name = name + self.bio = bio + self.image = image + self.links = links + self.status = status + } +} + +#if compiler(>=6) +extension PubkyProfile: Sendable {} +#endif + + +extension PubkyProfile: Equatable, Hashable { + public static func ==(lhs: PubkyProfile, rhs: PubkyProfile) -> Bool { + if lhs.name != rhs.name { + return false + } + if lhs.bio != rhs.bio { + return false + } + if lhs.image != rhs.image { + return false + } + if lhs.links != rhs.links { + return false + } + if lhs.status != rhs.status { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(name) + hasher.combine(bio) + hasher.combine(image) + hasher.combine(links) + hasher.combine(status) + } +} + +extension PubkyProfile: Codable {} + + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypePubkyProfile: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> PubkyProfile { + return + try PubkyProfile( + name: FfiConverterString.read(from: &buf), + bio: FfiConverterOptionString.read(from: &buf), + image: FfiConverterOptionString.read(from: &buf), + links: FfiConverterOptionSequenceTypePubkyProfileLink.read(from: &buf), + status: FfiConverterOptionString.read(from: &buf) + ) + } + + public static func write(_ value: PubkyProfile, into buf: inout [UInt8]) { + FfiConverterString.write(value.name, into: &buf) + FfiConverterOptionString.write(value.bio, into: &buf) + FfiConverterOptionString.write(value.image, into: &buf) + FfiConverterOptionSequenceTypePubkyProfileLink.write(value.links, into: &buf) + FfiConverterOptionString.write(value.status, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypePubkyProfile_lift(_ buf: RustBuffer) throws -> PubkyProfile { + return try FfiConverterTypePubkyProfile.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypePubkyProfile_lower(_ value: PubkyProfile) -> RustBuffer { + return FfiConverterTypePubkyProfile.lower(value) +} + + +public struct PubkyProfileLink { + public var title: String + public var url: String + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(title: String, url: String) { + self.title = title + self.url = url + } +} + +#if compiler(>=6) +extension PubkyProfileLink: Sendable {} +#endif + + +extension PubkyProfileLink: Equatable, Hashable { + public static func ==(lhs: PubkyProfileLink, rhs: PubkyProfileLink) -> Bool { + if lhs.title != rhs.title { + return false + } + if lhs.url != rhs.url { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(title) + hasher.combine(url) + } +} + +extension PubkyProfileLink: Codable {} + + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypePubkyProfileLink: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> PubkyProfileLink { + return + try PubkyProfileLink( + title: FfiConverterString.read(from: &buf), + url: FfiConverterString.read(from: &buf) + ) + } + + public static func write(_ value: PubkyProfileLink, into buf: inout [UInt8]) { + FfiConverterString.write(value.title, into: &buf) + FfiConverterString.write(value.url, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypePubkyProfileLink_lift(_ buf: RustBuffer) throws -> PubkyProfileLink { + return try FfiConverterTypePubkyProfileLink.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypePubkyProfileLink_lower(_ value: PubkyProfileLink) -> RustBuffer { + return FfiConverterTypePubkyProfileLink.lower(value) +} + + public struct SweepResult { /** * The transaction ID of the sweep transaction @@ -12866,6 +13034,11 @@ public enum PubkyError: Swift.Error { case NoActiveFlow case ResolutionFailed(reason: String ) + case FetchFailed(reason: String + ) + case ProfileNotFound + case ProfileParseFailed(reason: String + ) } @@ -12892,6 +13065,13 @@ public struct FfiConverterTypePubkyError: FfiConverterRustBuffer { case 4: return .ResolutionFailed( reason: try FfiConverterString.read(from: &buf) ) + case 5: return .FetchFailed( + reason: try FfiConverterString.read(from: &buf) + ) + case 6: return .ProfileNotFound + case 7: return .ProfileParseFailed( + reason: try FfiConverterString.read(from: &buf) + ) default: throw UniffiInternalError.unexpectedEnumCase } @@ -12922,6 +13102,20 @@ public struct FfiConverterTypePubkyError: FfiConverterRustBuffer { writeInt(&buf, Int32(4)) FfiConverterString.write(reason, into: &buf) + + case let .FetchFailed(reason): + writeInt(&buf, Int32(5)) + FfiConverterString.write(reason, into: &buf) + + + case .ProfileNotFound: + writeInt(&buf, Int32(6)) + + + case let .ProfileParseFailed(reason): + writeInt(&buf, Int32(7)) + FfiConverterString.write(reason, into: &buf) + } } } @@ -14973,6 +15167,30 @@ fileprivate struct FfiConverterOptionSequenceTypeIManualRefund: FfiConverterRust } } +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterOptionSequenceTypePubkyProfileLink: FfiConverterRustBuffer { + typealias SwiftType = [PubkyProfileLink]? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterSequenceTypePubkyProfileLink.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterSequenceTypePubkyProfileLink.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + #if swift(>=5.8) @_documentation(visibility: private) #endif @@ -15347,6 +15565,31 @@ fileprivate struct FfiConverterSequenceTypePreActivityMetadata: FfiConverterRust } } +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterSequenceTypePubkyProfileLink: FfiConverterRustBuffer { + typealias SwiftType = [PubkyProfileLink] + + public static func write(_ value: [PubkyProfileLink], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypePubkyProfileLink.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [PubkyProfileLink] { + let len: Int32 = try readInt(&buf) + var seq = [PubkyProfileLink]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypePubkyProfileLink.read(from: &buf)) + } + return seq + } +} + #if swift(>=5.8) @_documentation(visibility: private) #endif @@ -15948,6 +16191,20 @@ public func estimateOrderFeeFull(lspBalanceSat: UInt64, channelExpiryWeeks: UInt errorHandler: FfiConverterTypeBlocktankError_lift ) } +public func fetchPubkyContacts(publicKey: String)async throws -> [String] { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_func_fetch_pubky_contacts(FfiConverterString.lower(publicKey) + ) + }, + pollFunc: ffi_bitkitcore_rust_future_poll_rust_buffer, + completeFunc: ffi_bitkitcore_rust_future_complete_rust_buffer, + freeFunc: ffi_bitkitcore_rust_future_free_rust_buffer, + liftFunc: FfiConverterSequenceString.lift, + errorHandler: FfiConverterTypePubkyError_lift + ) +} public func fetchPubkyFile(uri: String)async throws -> Data { return try await uniffiRustCallAsync( @@ -15962,6 +16219,20 @@ public func fetchPubkyFile(uri: String)async throws -> Data { errorHandler: FfiConverterTypePubkyError_lift ) } +public func fetchPubkyProfile(publicKey: String)async throws -> PubkyProfile { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_func_fetch_pubky_profile(FfiConverterString.lower(publicKey) + ) + }, + pollFunc: ffi_bitkitcore_rust_future_poll_rust_buffer, + completeFunc: ffi_bitkitcore_rust_future_complete_rust_buffer, + freeFunc: ffi_bitkitcore_rust_future_free_rust_buffer, + liftFunc: FfiConverterTypePubkyProfile_lift, + errorHandler: FfiConverterTypePubkyError_lift + ) +} public func generateMnemonic(wordCount: WordCount?)throws -> String { return try FfiConverterString.lift(try rustCallWithError(FfiConverterTypeAddressError_lift) { uniffi_bitkitcore_fn_func_generate_mnemonic( @@ -17089,9 +17360,15 @@ private let initializationResult: InitializationResult = { if (uniffi_bitkitcore_checksum_func_estimate_order_fee_full() != 13361) { return InitializationResult.apiChecksumMismatch } + if (uniffi_bitkitcore_checksum_func_fetch_pubky_contacts() != 18744) { + return InitializationResult.apiChecksumMismatch + } if (uniffi_bitkitcore_checksum_func_fetch_pubky_file() != 24890) { return InitializationResult.apiChecksumMismatch } + if (uniffi_bitkitcore_checksum_func_fetch_pubky_profile() != 19709) { + return InitializationResult.apiChecksumMismatch + } if (uniffi_bitkitcore_checksum_func_generate_mnemonic() != 19292) { return InitializationResult.apiChecksumMismatch } diff --git a/bindings/ios/bitkitcoreFFI.h b/bindings/ios/bitkitcoreFFI.h index e2134e2..de38032 100644 --- a/bindings/ios/bitkitcoreFFI.h +++ b/bindings/ios/bitkitcoreFFI.h @@ -600,11 +600,21 @@ uint64_t uniffi_bitkitcore_fn_func_estimate_order_fee(uint64_t lsp_balance_sat, uint64_t uniffi_bitkitcore_fn_func_estimate_order_fee_full(uint64_t lsp_balance_sat, uint32_t channel_expiry_weeks, RustBuffer options ); #endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_FETCH_PUBKY_CONTACTS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_FETCH_PUBKY_CONTACTS +uint64_t uniffi_bitkitcore_fn_func_fetch_pubky_contacts(RustBuffer public_key +); +#endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_FETCH_PUBKY_FILE #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_FETCH_PUBKY_FILE uint64_t uniffi_bitkitcore_fn_func_fetch_pubky_file(RustBuffer uri ); #endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_FETCH_PUBKY_PROFILE +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_FETCH_PUBKY_PROFILE +uint64_t uniffi_bitkitcore_fn_func_fetch_pubky_profile(RustBuffer public_key +); +#endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_GENERATE_MNEMONIC #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_GENERATE_MNEMONIC RustBuffer uniffi_bitkitcore_fn_func_generate_mnemonic(RustBuffer word_count, RustCallStatus *_Nonnull out_status @@ -1508,12 +1518,24 @@ uint16_t uniffi_bitkitcore_checksum_func_estimate_order_fee(void #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_ESTIMATE_ORDER_FEE_FULL uint16_t uniffi_bitkitcore_checksum_func_estimate_order_fee_full(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_FETCH_PUBKY_CONTACTS +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_FETCH_PUBKY_CONTACTS +uint16_t uniffi_bitkitcore_checksum_func_fetch_pubky_contacts(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_FETCH_PUBKY_FILE #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_FETCH_PUBKY_FILE uint16_t uniffi_bitkitcore_checksum_func_fetch_pubky_file(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_FETCH_PUBKY_PROFILE +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_FETCH_PUBKY_PROFILE +uint16_t uniffi_bitkitcore_checksum_func_fetch_pubky_profile(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_GENERATE_MNEMONIC diff --git a/bindings/python/bitkitcore/bitkitcore.py b/bindings/python/bitkitcore/bitkitcore.py index cbebf11..6fe9ce7 100644 --- a/bindings/python/bitkitcore/bitkitcore.py +++ b/bindings/python/bitkitcore/bitkitcore.py @@ -513,8 +513,12 @@ def _uniffi_check_api_checksums(lib): raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_bitkitcore_checksum_func_estimate_order_fee_full() != 13361: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_bitkitcore_checksum_func_fetch_pubky_contacts() != 18744: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_bitkitcore_checksum_func_fetch_pubky_file() != 24890: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_bitkitcore_checksum_func_fetch_pubky_profile() != 19709: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_bitkitcore_checksum_func_generate_mnemonic() != 19292: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_bitkitcore_checksum_func_get_activities() != 21347: @@ -1145,10 +1149,18 @@ class _UniffiVTableCallbackInterfaceTrezorUiCallback(ctypes.Structure): _UniffiRustBuffer, ) _UniffiLib.uniffi_bitkitcore_fn_func_estimate_order_fee_full.restype = ctypes.c_uint64 +_UniffiLib.uniffi_bitkitcore_fn_func_fetch_pubky_contacts.argtypes = ( + _UniffiRustBuffer, +) +_UniffiLib.uniffi_bitkitcore_fn_func_fetch_pubky_contacts.restype = ctypes.c_uint64 _UniffiLib.uniffi_bitkitcore_fn_func_fetch_pubky_file.argtypes = ( _UniffiRustBuffer, ) _UniffiLib.uniffi_bitkitcore_fn_func_fetch_pubky_file.restype = ctypes.c_uint64 +_UniffiLib.uniffi_bitkitcore_fn_func_fetch_pubky_profile.argtypes = ( + _UniffiRustBuffer, +) +_UniffiLib.uniffi_bitkitcore_fn_func_fetch_pubky_profile.restype = ctypes.c_uint64 _UniffiLib.uniffi_bitkitcore_fn_func_generate_mnemonic.argtypes = ( _UniffiRustBuffer, ctypes.POINTER(_UniffiRustCallStatus), @@ -1930,9 +1942,15 @@ class _UniffiVTableCallbackInterfaceTrezorUiCallback(ctypes.Structure): _UniffiLib.uniffi_bitkitcore_checksum_func_estimate_order_fee_full.argtypes = ( ) _UniffiLib.uniffi_bitkitcore_checksum_func_estimate_order_fee_full.restype = ctypes.c_uint16 +_UniffiLib.uniffi_bitkitcore_checksum_func_fetch_pubky_contacts.argtypes = ( +) +_UniffiLib.uniffi_bitkitcore_checksum_func_fetch_pubky_contacts.restype = ctypes.c_uint16 _UniffiLib.uniffi_bitkitcore_checksum_func_fetch_pubky_file.argtypes = ( ) _UniffiLib.uniffi_bitkitcore_checksum_func_fetch_pubky_file.restype = ctypes.c_uint16 +_UniffiLib.uniffi_bitkitcore_checksum_func_fetch_pubky_profile.argtypes = ( +) +_UniffiLib.uniffi_bitkitcore_checksum_func_fetch_pubky_profile.restype = ctypes.c_uint16 _UniffiLib.uniffi_bitkitcore_checksum_func_generate_mnemonic.argtypes = ( ) _UniffiLib.uniffi_bitkitcore_checksum_func_generate_mnemonic.restype = ctypes.c_uint16 @@ -6038,6 +6056,99 @@ def write(value, buf): _UniffiConverterString.write(value.data, buf) +class PubkyProfile: + name: "str" + bio: "typing.Optional[str]" + image: "typing.Optional[str]" + links: "typing.Optional[typing.List[PubkyProfileLink]]" + status: "typing.Optional[str]" + def __init__(self, *, name: "str", bio: "typing.Optional[str]", image: "typing.Optional[str]", links: "typing.Optional[typing.List[PubkyProfileLink]]", status: "typing.Optional[str]"): + self.name = name + self.bio = bio + self.image = image + self.links = links + self.status = status + + def __str__(self): + return "PubkyProfile(name={}, bio={}, image={}, links={}, status={})".format(self.name, self.bio, self.image, self.links, self.status) + + def __eq__(self, other): + if self.name != other.name: + return False + if self.bio != other.bio: + return False + if self.image != other.image: + return False + if self.links != other.links: + return False + if self.status != other.status: + return False + return True + +class _UniffiConverterTypePubkyProfile(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return PubkyProfile( + name=_UniffiConverterString.read(buf), + bio=_UniffiConverterOptionalString.read(buf), + image=_UniffiConverterOptionalString.read(buf), + links=_UniffiConverterOptionalSequenceTypePubkyProfileLink.read(buf), + status=_UniffiConverterOptionalString.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterString.check_lower(value.name) + _UniffiConverterOptionalString.check_lower(value.bio) + _UniffiConverterOptionalString.check_lower(value.image) + _UniffiConverterOptionalSequenceTypePubkyProfileLink.check_lower(value.links) + _UniffiConverterOptionalString.check_lower(value.status) + + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value.name, buf) + _UniffiConverterOptionalString.write(value.bio, buf) + _UniffiConverterOptionalString.write(value.image, buf) + _UniffiConverterOptionalSequenceTypePubkyProfileLink.write(value.links, buf) + _UniffiConverterOptionalString.write(value.status, buf) + + +class PubkyProfileLink: + title: "str" + url: "str" + def __init__(self, *, title: "str", url: "str"): + self.title = title + self.url = url + + def __str__(self): + return "PubkyProfileLink(title={}, url={})".format(self.title, self.url) + + def __eq__(self, other): + if self.title != other.title: + return False + if self.url != other.url: + return False + return True + +class _UniffiConverterTypePubkyProfileLink(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return PubkyProfileLink( + title=_UniffiConverterString.read(buf), + url=_UniffiConverterString.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterString.check_lower(value.title) + _UniffiConverterString.check_lower(value.url) + + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value.title, buf) + _UniffiConverterString.write(value.url, buf) + + class SweepResult: txid: "str" """ @@ -10150,6 +10261,33 @@ def __init__(self, reason): def __repr__(self): return "PubkyError.ResolutionFailed({})".format(str(self)) _UniffiTempPubkyError.ResolutionFailed = ResolutionFailed # type: ignore + class FetchFailed(_UniffiTempPubkyError): + def __init__(self, reason): + super().__init__(", ".join([ + "reason={!r}".format(reason), + ])) + self.reason = reason + + def __repr__(self): + return "PubkyError.FetchFailed({})".format(str(self)) + _UniffiTempPubkyError.FetchFailed = FetchFailed # type: ignore + class ProfileNotFound(_UniffiTempPubkyError): + def __init__(self): + pass + + def __repr__(self): + return "PubkyError.ProfileNotFound({})".format(str(self)) + _UniffiTempPubkyError.ProfileNotFound = ProfileNotFound # type: ignore + class ProfileParseFailed(_UniffiTempPubkyError): + def __init__(self, reason): + super().__init__(", ".join([ + "reason={!r}".format(reason), + ])) + self.reason = reason + + def __repr__(self): + return "PubkyError.ProfileParseFailed({})".format(str(self)) + _UniffiTempPubkyError.ProfileParseFailed = ProfileParseFailed # type: ignore PubkyError = _UniffiTempPubkyError # type: ignore del _UniffiTempPubkyError @@ -10174,6 +10312,17 @@ def read(buf): return PubkyError.ResolutionFailed( _UniffiConverterString.read(buf), ) + if variant == 5: + return PubkyError.FetchFailed( + _UniffiConverterString.read(buf), + ) + if variant == 6: + return PubkyError.ProfileNotFound( + ) + if variant == 7: + return PubkyError.ProfileParseFailed( + _UniffiConverterString.read(buf), + ) raise InternalError("Raw enum value doesn't match any cases") @staticmethod @@ -10189,6 +10338,14 @@ def check_lower(value): if isinstance(value, PubkyError.ResolutionFailed): _UniffiConverterString.check_lower(value.reason) return + if isinstance(value, PubkyError.FetchFailed): + _UniffiConverterString.check_lower(value.reason) + return + if isinstance(value, PubkyError.ProfileNotFound): + return + if isinstance(value, PubkyError.ProfileParseFailed): + _UniffiConverterString.check_lower(value.reason) + return @staticmethod def write(value, buf): @@ -10203,6 +10360,14 @@ def write(value, buf): if isinstance(value, PubkyError.ResolutionFailed): buf.write_i32(4) _UniffiConverterString.write(value.reason, buf) + if isinstance(value, PubkyError.FetchFailed): + buf.write_i32(5) + _UniffiConverterString.write(value.reason, buf) + if isinstance(value, PubkyError.ProfileNotFound): + buf.write_i32(6) + if isinstance(value, PubkyError.ProfileParseFailed): + buf.write_i32(7) + _UniffiConverterString.write(value.reason, buf) @@ -12554,6 +12719,33 @@ def read(cls, buf): +class _UniffiConverterOptionalSequenceTypePubkyProfileLink(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + if value is not None: + _UniffiConverterSequenceTypePubkyProfileLink.check_lower(value) + + @classmethod + def write(cls, value, buf): + if value is None: + buf.write_u8(0) + return + + buf.write_u8(1) + _UniffiConverterSequenceTypePubkyProfileLink.write(value, buf) + + @classmethod + def read(cls, buf): + flag = buf.read_u8() + if flag == 0: + return None + elif flag == 1: + return _UniffiConverterSequenceTypePubkyProfileLink.read(buf) + else: + raise InternalError("Unexpected flag byte for optional type") + + + class _UniffiConverterOptionalMapStringString(_UniffiConverterRustBuffer): @classmethod def check_lower(cls, value): @@ -12931,6 +13123,31 @@ def read(cls, buf): +class _UniffiConverterSequenceTypePubkyProfileLink(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + for item in value: + _UniffiConverterTypePubkyProfileLink.check_lower(item) + + @classmethod + def write(cls, value, buf): + items = len(value) + buf.write_i32(items) + for item in value: + _UniffiConverterTypePubkyProfileLink.write(item, buf) + + @classmethod + def read(cls, buf): + count = buf.read_i32() + if count < 0: + raise InternalError("Unexpected negative sequence length") + + return [ + _UniffiConverterTypePubkyProfileLink.read(buf) for i in range(count) + ] + + + class _UniffiConverterSequenceTypeTransactionDetails(_UniffiConverterRustBuffer): @classmethod def check_lower(cls, value): @@ -14769,6 +14986,23 @@ async def estimate_order_fee_full(lsp_balance_sat: "int",channel_expiry_weeks: " # Error FFI converter _UniffiConverterTypeBlocktankError, + ) +async def fetch_pubky_contacts(public_key: "str") -> "typing.List[str]": + + _UniffiConverterString.check_lower(public_key) + + return await _uniffi_rust_call_async( + _UniffiLib.uniffi_bitkitcore_fn_func_fetch_pubky_contacts( + _UniffiConverterString.lower(public_key)), + _UniffiLib.ffi_bitkitcore_rust_future_poll_rust_buffer, + _UniffiLib.ffi_bitkitcore_rust_future_complete_rust_buffer, + _UniffiLib.ffi_bitkitcore_rust_future_free_rust_buffer, + # lift function + _UniffiConverterSequenceString.lift, + + # Error FFI converter +_UniffiConverterTypePubkyError, + ) async def fetch_pubky_file(uri: "str") -> "bytes": @@ -14787,6 +15021,23 @@ async def fetch_pubky_file(uri: "str") -> "bytes": _UniffiConverterTypePubkyError, ) +async def fetch_pubky_profile(public_key: "str") -> "PubkyProfile": + + _UniffiConverterString.check_lower(public_key) + + return await _uniffi_rust_call_async( + _UniffiLib.uniffi_bitkitcore_fn_func_fetch_pubky_profile( + _UniffiConverterString.lower(public_key)), + _UniffiLib.ffi_bitkitcore_rust_future_poll_rust_buffer, + _UniffiLib.ffi_bitkitcore_rust_future_complete_rust_buffer, + _UniffiLib.ffi_bitkitcore_rust_future_free_rust_buffer, + # lift function + _UniffiConverterTypePubkyProfile.lift, + + # Error FFI converter +_UniffiConverterTypePubkyError, + + ) def generate_mnemonic(word_count: "typing.Optional[WordCount]") -> "str": _UniffiConverterOptionalTypeWordCount.check_lower(word_count) @@ -16168,6 +16419,8 @@ def wipe_all_transaction_details() -> None: "OnchainActivity", "PreActivityMetadata", "PubkyAuth", + "PubkyProfile", + "PubkyProfileLink", "SweepResult", "SweepTransactionPreview", "SweepableBalances", @@ -16220,7 +16473,9 @@ def wipe_all_transaction_details() -> None: "entropy_to_mnemonic", "estimate_order_fee", "estimate_order_fee_full", + "fetch_pubky_contacts", "fetch_pubky_file", + "fetch_pubky_profile", "generate_mnemonic", "get_activities", "get_activities_by_tag", diff --git a/bindings/python/bitkitcore/libbitkitcore.dylib b/bindings/python/bitkitcore/libbitkitcore.dylib index 23cb8c5..b68b77c 100755 Binary files a/bindings/python/bitkitcore/libbitkitcore.dylib and b/bindings/python/bitkitcore/libbitkitcore.dylib differ diff --git a/src/lib.rs b/src/lib.rs index 48a4e1c..d29e5de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,7 +35,7 @@ pub use modules::scanner::{ pub use modules::lnurl; pub use modules::onchain; pub use modules::activity; -use crate::modules::pubky::PubkyError; +use crate::modules::pubky::{PubkyError, PubkyProfile}; use crate::activity::{ActivityError, ActivityDB, OnchainActivity, LightningActivity, Activity, ActivityFilter, SortDirection, PaymentType, DbError, ClosedChannelDetails, ActivityTags, PreActivityMetadata, TransactionDetails, TxInput, TxOutput}; use crate::modules::blocktank::{BlocktankDB, BlocktankError, IBtInfo, IBtOrder, CreateOrderOptions, BtOrderState2, IBt0ConfMinTxFeeWindow, IBtEstimateFeeResponse, IBtEstimateFeeResponse2, CreateCjitOptions, ICJitEntry, CJitStateEnum, IBtBolt11Invoice, IGift, ChannelLiquidityOptions, ChannelLiquidityParams, DefaultLspBalanceParams}; use crate::onchain::{AddressError, ValidationResult, GetAddressResponse, Network, GetAddressesResponse, SweepError, SweepResult, SweepTransactionPreview, SweepableBalances}; @@ -1500,6 +1500,26 @@ pub async fn complete_pubky_auth() -> Result { })) } +#[uniffi::export] +pub async fn fetch_pubky_profile(public_key: String) -> Result { + let rt = ensure_runtime(); + rt.spawn(async move { + crate::modules::pubky::fetch_pubky_profile(public_key).await + }).await.unwrap_or_else(|e| Err(PubkyError::FetchFailed { + reason: format!("Runtime error: {}", e) + })) +} + +#[uniffi::export] +pub async fn fetch_pubky_contacts(public_key: String) -> Result, PubkyError> { + let rt = ensure_runtime(); + rt.spawn(async move { + crate::modules::pubky::fetch_pubky_contacts(public_key).await + }).await.unwrap_or_else(|e| Err(PubkyError::FetchFailed { + reason: format!("Runtime error: {}", e) + })) +} + // ============================================================================ // Trezor Hardware Wallet Functions // ============================================================================ diff --git a/src/modules/pubky/errors.rs b/src/modules/pubky/errors.rs index 04676f9..9b4e6ed 100644 --- a/src/modules/pubky/errors.rs +++ b/src/modules/pubky/errors.rs @@ -11,4 +11,13 @@ pub enum PubkyError { #[error("Resolution failed: {reason}")] ResolutionFailed { reason: String }, + + #[error("Fetch failed: {reason}")] + FetchFailed { reason: String }, + + #[error("Profile not found")] + ProfileNotFound, + + #[error("Profile parse failed: {reason}")] + ProfileParseFailed { reason: String }, } diff --git a/src/modules/pubky/mod.rs b/src/modules/pubky/mod.rs index a51ca0f..c036f0f 100644 --- a/src/modules/pubky/mod.rs +++ b/src/modules/pubky/mod.rs @@ -1,9 +1,11 @@ mod auth; mod errors; +mod profile; mod resolve; #[cfg(test)] mod tests; pub use auth::*; pub use errors::*; +pub use profile::*; pub use resolve::*; diff --git a/src/modules/pubky/profile.rs b/src/modules/pubky/profile.rs new file mode 100644 index 0000000..23f23c9 --- /dev/null +++ b/src/modules/pubky/profile.rs @@ -0,0 +1,108 @@ +use pubky_app_specs::{PubkyAppUser, PubkyAppUserLink}; + +use super::errors::PubkyError; +use super::resolve::get_pubky; + +const PROFILE_PATH: &str = "/pub/pubky.app/profile.json"; +const FOLLOWS_PATH: &str = "/pub/pubky.app/follows/"; + +#[derive(uniffi::Record, Debug, Clone)] +pub struct PubkyProfileLink { + pub title: String, + pub url: String, +} + +#[derive(uniffi::Record, Debug, Clone)] +pub struct PubkyProfile { + pub name: String, + pub bio: Option, + pub image: Option, + pub links: Option>, + pub status: Option, +} + +impl From for PubkyProfileLink { + fn from(link: PubkyAppUserLink) -> Self { + PubkyProfileLink { + title: link.title, + url: link.url, + } + } +} + +impl From for PubkyProfile { + fn from(user: PubkyAppUser) -> Self { + PubkyProfile { + name: user.name, + bio: user.bio, + image: user.image, + links: user.links.map(|links| { + links.into_iter().map(PubkyProfileLink::from).collect() + }), + status: user.status, + } + } +} + +fn is_not_found(err: &impl std::fmt::Display) -> bool { + let msg = err.to_string(); + msg.contains("404") || msg.to_lowercase().contains("not found") +} + +/// Fetch and parse a user's `profile.json` by their public key (z32-encoded). +pub async fn fetch_pubky_profile(public_key: String) -> Result { + let pubky = get_pubky()?; + let addr = format!("{public_key}{PROFILE_PATH}"); + + let response = pubky + .public_storage() + .get(&addr) + .await + .map_err(|e| { + if is_not_found(&e) { + PubkyError::ProfileNotFound + } else { + PubkyError::FetchFailed { reason: e.to_string() } + } + })?; + + let bytes = response + .bytes() + .await + .map_err(|e| PubkyError::FetchFailed { reason: e.to_string() })?; + + let user: PubkyAppUser = serde_json::from_slice(&bytes) + .map_err(|e| PubkyError::ProfileParseFailed { reason: e.to_string() })?; + + Ok(PubkyProfile::from(user)) +} + +/// Fetch the followed public keys for a user. Returns an empty list if the user has no follows. +pub async fn fetch_pubky_contacts(public_key: String) -> Result, PubkyError> { + let pubky = get_pubky()?; + let addr = format!("{public_key}{FOLLOWS_PATH}"); + let storage = pubky.public_storage(); + + let list_builder = storage + .list(&addr) + .map_err(|e| PubkyError::FetchFailed { reason: e.to_string() })?; + + let entries = match list_builder.send().await { + Ok(entries) => entries, + Err(e) if is_not_found(&e) => return Ok(Vec::new()), + Err(e) => return Err(PubkyError::FetchFailed { reason: e.to_string() }), + }; + + let mut contacts = Vec::new(); + for entry in entries { + let url_str = entry.to_pubky_url(); + if url_str.ends_with('/') { + continue; + } + if let Some(pk_str) = url_str.rsplit('/').next().filter(|s| !s.is_empty()) { + contacts.push(pk_str.to_string()); + } + } + + Ok(contacts) +} diff --git a/src/modules/pubky/resolve.rs b/src/modules/pubky/resolve.rs index c97d114..7153723 100644 --- a/src/modules/pubky/resolve.rs +++ b/src/modules/pubky/resolve.rs @@ -5,7 +5,7 @@ use super::errors::PubkyError; static PUBKY_SDK: OnceCell = OnceCell::new(); -fn get_pubky() -> Result<&'static Pubky, PubkyError> { +pub(super) fn get_pubky() -> Result<&'static Pubky, PubkyError> { PUBKY_SDK.get_or_try_init(|| { Pubky::new().map_err(|e| PubkyError::ResolutionFailed { reason: e.to_string(), diff --git a/src/modules/pubky/tests.rs b/src/modules/pubky/tests.rs index 3e5904f..919cba6 100644 --- a/src/modules/pubky/tests.rs +++ b/src/modules/pubky/tests.rs @@ -1,5 +1,9 @@ use super::*; +// ============================================================================ +// Resolution tests +// ============================================================================ + #[test] fn resolve_pubky_uri() { let pk = "operrr8wsbpr3ue9d4qj41ge1kcc6r7fdiy6o3ugjrrhi4y77rdo"; @@ -32,6 +36,10 @@ fn resolve_invalid_uri() { } } +// ============================================================================ +// Auth flow tests +// ============================================================================ + #[tokio::test] async fn complete_without_active_flow_returns_no_active_flow() { let result = complete_pubky_auth().await; @@ -52,6 +60,10 @@ async fn cancel_without_active_flow_returns_no_active_flow() { } } +// ============================================================================ +// File fetch tests +// ============================================================================ + #[tokio::test] async fn fetch_malformed_uri_returns_error() { let result = fetch_pubky_file("totally-not-a-pubky-uri".into()).await; @@ -62,3 +74,171 @@ async fn fetch_malformed_uri_returns_error() { } } +// ============================================================================ +// Profile conversion tests +// ============================================================================ + +#[test] +fn profile_from_full_app_user() { + use pubky_app_specs::{PubkyAppUser, PubkyAppUserLink}; + + let user = PubkyAppUser::new( + "Alice".to_string(), + Some("Hello world".to_string()), + Some("https://example.com/avatar.png".to_string()), + Some(vec![PubkyAppUserLink::new( + "Website".to_string(), + "https://alice.example.com".to_string(), + )]), + Some("Online".to_string()), + ); + + let profile = PubkyProfile::from(user); + assert_eq!(profile.name, "Alice"); + assert_eq!(profile.bio.as_deref(), Some("Hello world")); + assert_eq!(profile.image.as_deref(), Some("https://example.com/avatar.png")); + assert_eq!(profile.status.as_deref(), Some("Online")); + + let links = profile.links.unwrap(); + assert_eq!(links.len(), 1); + assert_eq!(links[0].title, "Website"); + assert!(links[0].url.starts_with("https://alice.example.com")); +} + +#[test] +fn profile_from_minimal_app_user() { + use pubky_app_specs::PubkyAppUser; + + let user = PubkyAppUser::new("Bob".to_string(), None, None, None, None); + + let profile = PubkyProfile::from(user); + assert_eq!(profile.name, "Bob"); + assert!(profile.bio.is_none()); + assert!(profile.image.is_none()); + assert!(profile.links.is_none()); + assert!(profile.status.is_none()); +} + +#[test] +fn profile_from_user_with_empty_links() { + use pubky_app_specs::PubkyAppUser; + + let user = PubkyAppUser::new( + "Carol".to_string(), + Some("Bio".to_string()), + None, + Some(vec![]), + None, + ); + + let profile = PubkyProfile::from(user); + assert_eq!(profile.name, "Carol"); + let links = profile.links.unwrap(); + assert!(links.is_empty()); +} + +// ============================================================================ +// Profile JSON deserialization tests +// ============================================================================ + +#[test] +fn profile_deserialized_from_full_json() { + let json = r#"{ + "name": "Dave", + "bio": "Hello", + "image": "https://example.com/img.png", + "links": [ + {"title": "Blog", "url": "https://blog.example.com"}, + {"title": "GitHub", "url": "https://github.com/dave"} + ], + "status": "Away" + }"#; + + let user: pubky_app_specs::PubkyAppUser = serde_json::from_str(json).unwrap(); + let profile = PubkyProfile::from(user); + + assert_eq!(profile.name, "Dave"); + assert_eq!(profile.bio.as_deref(), Some("Hello")); + assert_eq!(profile.image.as_deref(), Some("https://example.com/img.png")); + assert_eq!(profile.status.as_deref(), Some("Away")); + let links = profile.links.unwrap(); + assert_eq!(links.len(), 2); + assert_eq!(links[0].title, "Blog"); + assert_eq!(links[1].title, "GitHub"); +} + +#[test] +fn profile_deserialized_from_name_only_json() { + let json = r#"{"name": "Eve"}"#; + let user: pubky_app_specs::PubkyAppUser = serde_json::from_str(json).unwrap(); + let profile = PubkyProfile::from(user); + + assert_eq!(profile.name, "Eve"); + assert!(profile.bio.is_none()); + assert!(profile.image.is_none()); + assert!(profile.links.is_none()); + assert!(profile.status.is_none()); +} + +#[test] +fn profile_missing_name_json_fails() { + let json = r#"{"bio": "no name field"}"#; + let result: Result = serde_json::from_str(json); + assert!(result.is_err()); +} + +#[test] +fn profile_invalid_json_fails() { + let json = r#"not valid json"#; + let result: Result = serde_json::from_str(json); + assert!(result.is_err()); +} + +// ============================================================================ +// Profile fetch error tests +// ============================================================================ + +#[tokio::test] +async fn fetch_profile_invalid_key_returns_fetch_failed() { + let result = fetch_pubky_profile("not-a-valid-public-key".into()).await; + assert!(result.is_err()); + match result.unwrap_err() { + PubkyError::FetchFailed { .. } | PubkyError::ProfileNotFound => {} + other => panic!("expected FetchFailed or ProfileNotFound, got: {other:?}"), + } +} + +#[tokio::test] +async fn fetch_profile_empty_key_returns_fetch_failed() { + let result = fetch_pubky_profile(String::new()).await; + assert!(result.is_err()); + match result.unwrap_err() { + PubkyError::FetchFailed { .. } | PubkyError::ProfileNotFound => {} + other => panic!("expected FetchFailed or ProfileNotFound, got: {other:?}"), + } +} + +// ============================================================================ +// Contacts fetch error tests +// ============================================================================ + +#[tokio::test] +async fn fetch_contacts_invalid_key_returns_fetch_failed() { + let result = fetch_pubky_contacts("not-a-valid-public-key".into()).await; + assert!(result.is_err()); + match result.unwrap_err() { + PubkyError::FetchFailed { .. } => {} + other => panic!("expected FetchFailed, got: {other:?}"), + } +} + +#[tokio::test] +async fn fetch_contacts_empty_key_returns_fetch_failed() { + let result = fetch_pubky_contacts(String::new()).await; + assert!(result.is_err()); + match result.unwrap_err() { + PubkyError::FetchFailed { .. } => {} + other => panic!("expected FetchFailed, got: {other:?}"), + } +} +