From 9613a1e6fc12cb9d75bc1939dcb5220ce182fffd Mon Sep 17 00:00:00 2001 From: Kayos Date: Sat, 28 Mar 2026 13:18:08 -0700 Subject: [PATCH] Fix Koios API integration for unfunded addresses - Add trailing slash to Koios base URLs (required by Retrofit) - Handle empty response bodies for unfunded addresses (returns [] from API) - getBalance now returns 0 for unfunded addresses instead of failing - getUtxos now returns empty list for unfunded addresses - Add debug logging for Koios responses --- .../impl/cardano/CardanoNetworkConfig.kt | 4 +- .../wallet/impl/cardano/KoiosCardanoClient.kt | 71 +++++++++++-------- 2 files changed, 45 insertions(+), 30 deletions(-) diff --git a/features/wallet/impl/src/main/kotlin/io/element/android/features/wallet/impl/cardano/CardanoNetworkConfig.kt b/features/wallet/impl/src/main/kotlin/io/element/android/features/wallet/impl/cardano/CardanoNetworkConfig.kt index e9569c5d15..12f92d62c1 100644 --- a/features/wallet/impl/src/main/kotlin/io/element/android/features/wallet/impl/cardano/CardanoNetworkConfig.kt +++ b/features/wallet/impl/src/main/kotlin/io/element/android/features/wallet/impl/cardano/CardanoNetworkConfig.kt @@ -48,8 +48,8 @@ object CardanoNetworkConfig { * Rate limits: 100 req/10s for anonymous users. */ val KOIOS_BASE_URL: String = when (NETWORK) { - CardanoNetwork.TESTNET -> "https://preprod.koios.rest/api/v1" - CardanoNetwork.MAINNET -> "https://api.koios.rest/api/v1" + CardanoNetwork.TESTNET -> "https://preprod.koios.rest/api/v1/" + CardanoNetwork.MAINNET -> "https://api.koios.rest/api/v1/" } /** diff --git a/features/wallet/impl/src/main/kotlin/io/element/android/features/wallet/impl/cardano/KoiosCardanoClient.kt b/features/wallet/impl/src/main/kotlin/io/element/android/features/wallet/impl/cardano/KoiosCardanoClient.kt index 80110e7637..36cb0a847a 100644 --- a/features/wallet/impl/src/main/kotlin/io/element/android/features/wallet/impl/cardano/KoiosCardanoClient.kt +++ b/features/wallet/impl/src/main/kotlin/io/element/android/features/wallet/impl/cardano/KoiosCardanoClient.kt @@ -52,48 +52,62 @@ class KoiosCardanoClient @Inject constructor() : CardanoClient { throttleRequest() val result = backendService.addressService.getAddressInfo(address) - if (result.isSuccessful) { - val info = result.value - val lovelace = info.amount - ?.find { it.unit == "lovelace" } - ?.quantity - ?.toLong() - ?: 0L - Result.success(lovelace) - } else { - Result.failure(parseError(result.response)) + Timber.tag(TAG).d("getBalance result: isSuccessful=${result.isSuccessful}, response=${result.response?.take(200)}") + when { + result.isSuccessful -> { + val info = result.value + val lovelace = info.amount + ?.find { it.unit == "lovelace" } + ?.quantity + ?.toLong() + ?: 0L + Result.success(lovelace) + } + result.response?.contains("Empty") == true -> { + // Empty response means unfunded address - return 0 balance + Timber.tag(TAG).d("Address has no history, returning 0 balance") + Result.success(0L) + } + else -> { + Result.failure(parseError(result.response)) + } } } } - override suspend fun getUtxos(address: String): Result> = withRetry("getUtxos($address)") { withContext(Dispatchers.IO) { throttleRequest() val result = backendService.utxoService.getUtxos(address, 100, 1) - if (result.isSuccessful) { - val utxos = result.value.map { utxo -> - val lovelace = utxo.amount - ?.find { it.unit == "lovelace" } - ?.quantity - ?.toLong() - ?: 0L + when { + result.isSuccessful -> { + val utxos = result.value.map { utxo -> + val lovelace = utxo.amount + ?.find { it.unit == "lovelace" } + ?.quantity + ?.toLong() + ?: 0L - Utxo( - txHash = utxo.txHash, - outputIndex = utxo.outputIndex, - amount = lovelace, - address = address, - ) + Utxo( + txHash = utxo.txHash, + outputIndex = utxo.outputIndex, + amount = lovelace, + address = address, + ) + } + Result.success(utxos) + } + result.response?.contains("Empty") == true -> { + // Empty response means no UTXOs - return empty list + Result.success(emptyList()) + } + else -> { + Result.failure(parseError(result.response)) } - Result.success(utxos) - } else { - Result.failure(parseError(result.response)) } } } - override suspend fun submitTx(signedTxCbor: String): Result = withRetry("submitTx") { withContext(Dispatchers.IO) { @@ -176,6 +190,7 @@ class KoiosCardanoClient @Inject constructor() : CardanoClient { throttleRequest() val result = backendService.addressService.getAddressInfo(address) + Timber.tag(TAG).d("getBalance result: isSuccessful=${result.isSuccessful}, response=${result.response?.take(200)}") if (result.isSuccessful) { val info = result.value val assets = info.amount