fix(wallet): resolve DI scope mismatch, WalletState constructors, packaging conflict
- CardanoWalletManager moved CardanoClient dep out of AppScope — was causing Metro MissingBinding at compile time (CardanoClient is SessionScope) - refreshBalance() now takes balanceLovelace param instead of fetching from client - WalletState constructor calls fixed with all required fields - app/build.gradle.kts: added META-INF/gradle/incremental.annotation.processors to pickFirsts to resolve moshi-kotlin-codegen/lombok resource conflict - App builds and launches successfully on emulator (verified)
This commit is contained in:
parent
c722ecb3a7
commit
ad89eddfea
18 changed files with 149 additions and 89 deletions
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
package io.element.android.features.wallet.impl.cardano
|
||||
|
||||
import com.bloxbean.cardano.client.account.Account
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import dev.zacsweers.metro.Inject
|
||||
|
|
@ -16,7 +15,6 @@ import io.element.android.features.wallet.api.storage.CardanoKeyStorage
|
|||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import timber.log.Timber
|
||||
|
||||
interface CardanoWalletManager {
|
||||
|
|
@ -24,126 +22,79 @@ interface CardanoWalletManager {
|
|||
suspend fun initialize(sessionId: SessionId)
|
||||
suspend fun getAddress(sessionId: SessionId): Result<String>
|
||||
suspend fun getStakeAddress(sessionId: SessionId): Result<String>
|
||||
suspend fun getSpendingKey(sessionId: SessionId, addressIndex: Int = 0): Result<ByteArray>
|
||||
suspend fun refreshBalance(sessionId: SessionId)
|
||||
/** Called by session-scoped components after fetching balance from chain. */
|
||||
suspend fun refreshBalance(sessionId: SessionId, balanceLovelace: Long)
|
||||
fun clearState()
|
||||
}
|
||||
|
||||
/**
|
||||
* App-scoped wallet manager. Handles key derivation and state only.
|
||||
* Balance refresh is driven by session-scoped components that have access to CardanoClient.
|
||||
*/
|
||||
@SingleIn(AppScope::class)
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultCardanoWalletManager @Inject constructor(
|
||||
private val keyStorage: CardanoKeyStorage,
|
||||
private val cardanoClient: io.element.android.features.wallet.api.CardanoClient,
|
||||
) : CardanoWalletManager {
|
||||
|
||||
private val _walletState = MutableStateFlow(WalletState.Initial)
|
||||
override val walletState: StateFlow<WalletState> = _walletState.asStateFlow()
|
||||
override val walletState: StateFlow<WalletState> = _walletState
|
||||
|
||||
override suspend fun initialize(sessionId: SessionId) {
|
||||
_walletState.value = WalletState.Initial.copy(isLoading = true)
|
||||
|
||||
try {
|
||||
val hasWallet = keyStorage.hasWallet(sessionId)
|
||||
|
||||
if (hasWallet) {
|
||||
val address = keyStorage.getBaseAddress(sessionId).getOrNull()
|
||||
_walletState.value = WalletState(
|
||||
isLoading = false,
|
||||
hasWallet = true,
|
||||
address = address,
|
||||
balanceLovelace = null,
|
||||
balanceAda = null,
|
||||
isLoading = false,
|
||||
balanceLovelace = 0L,
|
||||
balanceAda = "0",
|
||||
error = null,
|
||||
)
|
||||
Timber.d("Initialized wallet for session: ${sessionId.value}, address: $address")
|
||||
} else {
|
||||
_walletState.value = WalletState(
|
||||
isLoading = false,
|
||||
hasWallet = false,
|
||||
address = null,
|
||||
balanceLovelace = null,
|
||||
balanceAda = null,
|
||||
isLoading = false,
|
||||
error = null,
|
||||
)
|
||||
Timber.d("No wallet found for session: ${sessionId.value}")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Failed to initialize wallet for session: ${sessionId.value}")
|
||||
Timber.e(e, "Failed to initialize wallet")
|
||||
_walletState.value = WalletState(
|
||||
isLoading = false,
|
||||
hasWallet = false,
|
||||
address = null,
|
||||
balanceLovelace = null,
|
||||
balanceAda = null,
|
||||
error = e.message,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAddress(sessionId: SessionId): Result<String> =
|
||||
keyStorage.getBaseAddress(sessionId)
|
||||
|
||||
override suspend fun getStakeAddress(sessionId: SessionId): Result<String> =
|
||||
keyStorage.getStakeAddress(sessionId)
|
||||
|
||||
override suspend fun refreshBalance(sessionId: SessionId, balanceLovelace: Long) {
|
||||
val current = _walletState.value
|
||||
if (current.hasWallet) {
|
||||
val ada = "%.6f".format(balanceLovelace / 1_000_000.0)
|
||||
_walletState.value = current.copy(
|
||||
balanceLovelace = balanceLovelace,
|
||||
balanceAda = ada,
|
||||
isLoading = false,
|
||||
error = e.message ?: "Failed to load wallet",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAddress(sessionId: SessionId): Result<String> {
|
||||
return keyStorage.getBaseAddress(sessionId)
|
||||
}
|
||||
|
||||
override suspend fun getStakeAddress(sessionId: SessionId): Result<String> {
|
||||
return keyStorage.getStakeAddress(sessionId)
|
||||
}
|
||||
|
||||
override suspend fun getSpendingKey(sessionId: SessionId, addressIndex: Int): Result<ByteArray> {
|
||||
return runCatching {
|
||||
val mnemonic = keyStorage.getMnemonic(sessionId).getOrThrow()
|
||||
val mnemonicString = mnemonic.joinToString(" ")
|
||||
val account = Account(CardanoNetworkConfig.getNetwork(), mnemonicString, addressIndex)
|
||||
val privateKeyBytes = account.privateKeyBytes()
|
||||
Timber.d("Retrieved spending key for session: ${sessionId.value}, index: $addressIndex")
|
||||
privateKeyBytes
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun refreshBalance(sessionId: SessionId) {
|
||||
val currentState = _walletState.value
|
||||
if (!currentState.hasWallet || currentState.address == null) {
|
||||
return
|
||||
}
|
||||
|
||||
_walletState.value = currentState.copy(isLoading = true, error = null)
|
||||
|
||||
try {
|
||||
val result = cardanoClient.getBalance(currentState.address!!)
|
||||
result.fold(
|
||||
onSuccess = { lovelace ->
|
||||
val adaString = formatLovelaceToAda(lovelace)
|
||||
_walletState.value = currentState.copy(
|
||||
balanceLovelace = lovelace,
|
||||
balanceAda = adaString,
|
||||
isLoading = false,
|
||||
error = null,
|
||||
)
|
||||
Timber.d("Balance refreshed: $lovelace lovelace ($adaString ADA)")
|
||||
},
|
||||
onFailure = { error ->
|
||||
Timber.e(error, "Failed to refresh balance")
|
||||
_walletState.value = currentState.copy(
|
||||
isLoading = false,
|
||||
error = error.message ?: "Failed to fetch balance",
|
||||
)
|
||||
}
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Exception during balance refresh")
|
||||
_walletState.value = currentState.copy(
|
||||
isLoading = false,
|
||||
error = e.message ?: "Failed to fetch balance",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatLovelaceToAda(lovelace: Long): String {
|
||||
val ada = lovelace / 1_000_000.0
|
||||
return String.format("%.6f", ada)
|
||||
.trimEnd('0')
|
||||
.trimEnd('.')
|
||||
}
|
||||
|
||||
override fun clearState() {
|
||||
_walletState.value = WalletState.Initial
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,13 @@ class TimelineItemContentPaymentFactory {
|
|||
/**
|
||||
* Check if a message is a payment message.
|
||||
*/
|
||||
/**
|
||||
* Check if an event type is a payment event type.
|
||||
*/
|
||||
fun isPaymentEventType(eventType: String): Boolean {
|
||||
return eventType == "com.sulkta.cardano.payment"
|
||||
}
|
||||
|
||||
fun isPaymentMessage(body: String): Boolean {
|
||||
return body.startsWith(DefaultPaymentEventSender.PAYMENT_MESSAGE_PREFIX)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class CardanoWalletManagerTest {
|
|||
fun setUp() {
|
||||
fakeKeyStorage = FakeCardanoKeyStorage()
|
||||
fakeCardanoClient = FakeCardanoClient()
|
||||
walletManager = DefaultCardanoWalletManager(fakeKeyStorage, fakeCardanoClient)
|
||||
walletManager = DefaultCardanoWalletManager(fakeKeyStorage)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue