Updated on 2026-08-14
This commit is contained in:
parent
0a8e78877d
commit
06263e1193
11 changed files with 360 additions and 436 deletions
|
|
@ -2,10 +2,10 @@ package com.tangem.domain.models.account
|
|||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.raise.ensure
|
||||
import com.tangem.domain.models.TokensGroupType
|
||||
import com.tangem.domain.models.TokensSortType
|
||||
import com.tangem.domain.models.account.Account.CryptoPortfolio.Error.AccountNameError
|
||||
import com.tangem.domain.models.account.Account.CryptoPortfolio.Error.DerivationIndexError
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -43,14 +43,14 @@ sealed interface Account {
|
|||
override val accountId: AccountId,
|
||||
override val name: AccountName,
|
||||
val icon: CryptoPortfolioIcon,
|
||||
val derivationIndex: Int,
|
||||
val derivationIndex: DerivationIndex,
|
||||
val isArchived: Boolean,
|
||||
val cryptoCurrencyList: CryptoCurrencyList,
|
||||
) : Account {
|
||||
|
||||
/** Indicates if the account is the main account */
|
||||
val isMainAccount: Boolean
|
||||
get() = derivationIndex == 0
|
||||
get() = derivationIndex.isMain
|
||||
|
||||
/** Number of tokens in the account */
|
||||
val tokensCount: Int
|
||||
|
|
@ -97,15 +97,11 @@ sealed interface Account {
|
|||
|
||||
/** Error indicating that the account name is blank */
|
||||
@Serializable
|
||||
data class AccountNameError(val cause: AccountName.Error) : Error {
|
||||
override fun toString(): String = cause.toString()
|
||||
}
|
||||
data class AccountNameError(val cause: AccountName.Error) : Error
|
||||
|
||||
/** Error indicating that the derivation index is negative */
|
||||
@Serializable
|
||||
data object NegativeDerivationIndex : Error {
|
||||
override fun toString(): String = "${this::class.simpleName}: Derivation index must be non-negative"
|
||||
}
|
||||
data class DerivationIndexError(val cause: DerivationIndex.Error) : Error
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
@ -130,7 +126,8 @@ sealed interface Account {
|
|||
cryptoCurrencyList: CryptoCurrencyList,
|
||||
): Either<Error, CryptoPortfolio> {
|
||||
return either {
|
||||
val accountName = AccountName(name).mapLeft(::AccountNameError).bind()
|
||||
val accountName = AccountName(value = name).mapLeft(::AccountNameError).bind()
|
||||
val derivationIndex = DerivationIndex(derivationIndex).mapLeft(::DerivationIndexError).bind()
|
||||
|
||||
invoke(
|
||||
accountId = accountId,
|
||||
|
|
@ -140,7 +137,6 @@ sealed interface Account {
|
|||
isArchived = isArchived,
|
||||
cryptoCurrencyList = cryptoCurrencyList,
|
||||
)
|
||||
.bind()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -159,22 +155,18 @@ sealed interface Account {
|
|||
accountId: AccountId,
|
||||
accountName: AccountName,
|
||||
accountIcon: CryptoPortfolioIcon,
|
||||
derivationIndex: Int,
|
||||
derivationIndex: DerivationIndex,
|
||||
isArchived: Boolean,
|
||||
cryptoCurrencyList: CryptoCurrencyList,
|
||||
): Either<Error, CryptoPortfolio> {
|
||||
return either {
|
||||
ensure(derivationIndex >= 0) { Error.NegativeDerivationIndex }
|
||||
|
||||
CryptoPortfolio(
|
||||
accountId = accountId,
|
||||
name = accountName,
|
||||
icon = accountIcon,
|
||||
derivationIndex = derivationIndex,
|
||||
isArchived = isArchived,
|
||||
cryptoCurrencyList = cryptoCurrencyList,
|
||||
)
|
||||
}
|
||||
): CryptoPortfolio {
|
||||
return CryptoPortfolio(
|
||||
accountId = accountId,
|
||||
name = accountName,
|
||||
icon = accountIcon,
|
||||
derivationIndex = derivationIndex,
|
||||
isArchived = isArchived,
|
||||
cryptoCurrencyList = cryptoCurrencyList,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -183,12 +175,16 @@ sealed interface Account {
|
|||
* @param userWalletId the ID of the user wallet
|
||||
*/
|
||||
fun createMainAccount(userWalletId: UserWalletId): CryptoPortfolio {
|
||||
// TODO: [REDACTED_JIRA]
|
||||
val derivationIndex = DerivationIndex.Main
|
||||
|
||||
return CryptoPortfolio(
|
||||
accountId = AccountId(userWalletId = userWalletId, value = "main_account"),
|
||||
accountId = AccountId.forCryptoPortfolio(
|
||||
userWalletId = userWalletId,
|
||||
derivationIndex = derivationIndex,
|
||||
),
|
||||
name = AccountName.Main,
|
||||
icon = CryptoPortfolioIcon.ofMainAccount(userWalletId),
|
||||
derivationIndex = 0,
|
||||
derivationIndex = derivationIndex,
|
||||
isArchived = false,
|
||||
cryptoCurrencyList = CryptoCurrencyList(
|
||||
currencies = emptySet(),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
package com.tangem.domain.models.account
|
||||
|
||||
import com.tangem.common.extensions.toByteArray
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.utils.extensions.toHexString
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.security.MessageDigest
|
||||
|
||||
/**
|
||||
* Represents a unique identifier for an account
|
||||
|
|
@ -10,7 +13,26 @@ import kotlinx.serialization.Serializable
|
|||
* @property userWalletId the identifier of the user wallet associated with the account
|
||||
*/
|
||||
@Serializable
|
||||
data class AccountId(
|
||||
data class AccountId private constructor(
|
||||
val value: String,
|
||||
val userWalletId: UserWalletId,
|
||||
)
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
||||
private val sha256Digest: MessageDigest by lazy { MessageDigest.getInstance("SHA-256") }
|
||||
|
||||
/**
|
||||
* Creates a unique account identifier for a crypto portfolio
|
||||
*
|
||||
* @param userWalletId the identifier of the user wallet
|
||||
* @param derivationIndex the derivation index used to generate the identifier
|
||||
*/
|
||||
fun forCryptoPortfolio(userWalletId: UserWalletId, derivationIndex: DerivationIndex): AccountId {
|
||||
val input = userWalletId.value + derivationIndex.value.toByteArray()
|
||||
val value = sha256Digest.digest(input).toHexString()
|
||||
|
||||
return AccountId(value = value, userWalletId = userWalletId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package com.tangem.domain.models.account
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.raise.ensure
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Represents a derivation index for accounts, ensuring validity and providing utility methods
|
||||
*
|
||||
* @property value the integer value of the derivation index
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Serializable
|
||||
data class DerivationIndex private constructor(
|
||||
val value: Int,
|
||||
) {
|
||||
|
||||
/** Checks if the derivation index corresponds to the main account */
|
||||
val isMain: Boolean
|
||||
get() = value == MAIN_ACCOUNT_DERIVATION_INDEX
|
||||
|
||||
/**
|
||||
* Represents possible errors that can occur when creating a [DerivationIndex]
|
||||
*/
|
||||
@Serializable
|
||||
sealed interface Error {
|
||||
|
||||
/** Error indicating that the provided derivation index [derivationIndex] is invalid */
|
||||
@Serializable
|
||||
data class NegativeDerivationIndex(val derivationIndex: Int) : Error {
|
||||
override fun toString(): String {
|
||||
return "${this::class.simpleName}: Derivation index cannot be negative: $derivationIndex"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val MAIN_ACCOUNT_DERIVATION_INDEX = 0
|
||||
|
||||
/** Predefined instance of [DerivationIndex] for the main account */
|
||||
val Main: DerivationIndex = DerivationIndex(value = MAIN_ACCOUNT_DERIVATION_INDEX)
|
||||
|
||||
/**
|
||||
* Factory method to create a [DerivationIndex] instance
|
||||
*
|
||||
* @param value the integer value of the derivation index
|
||||
*
|
||||
* @return Either an error if the value is invalid, or a valid [DerivationIndex] instance
|
||||
*/
|
||||
operator fun invoke(value: Int): Either<Error, DerivationIndex> = either {
|
||||
ensure(value >= 0) { Error.NegativeDerivationIndex(derivationIndex = value) }
|
||||
|
||||
DerivationIndex(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.tangem.domain.models.account
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class AccountIdTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideTestModels")
|
||||
fun forCryptoPortfolio(model: ForCryptoPortfolioModel) {
|
||||
// Arrange
|
||||
val userWalletId = UserWalletId("27163F47405CE73110837F24DF82607FF11C7AF9D78C93F409E4FEAFF3400C8F")
|
||||
|
||||
// Act
|
||||
val actual = AccountId.forCryptoPortfolio(userWalletId = userWalletId, derivationIndex = model.derivationIndex)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual.value).isEqualTo(model.expected)
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
ForCryptoPortfolioModel(
|
||||
derivationIndex = DerivationIndex.Main,
|
||||
expected = "4E39B13EA11E3B35339664A10BEF48F4AF752A1CC2200F79D23CB0FB3396C63F",
|
||||
),
|
||||
ForCryptoPortfolioModel(
|
||||
derivationIndex = DerivationIndex(1).getOrNull()!!,
|
||||
expected = "7F22E71F8106783F0F2DAFCDE525E2F2A2281E864DDBE2FE668FA09329D563A2",
|
||||
),
|
||||
ForCryptoPortfolioModel(
|
||||
derivationIndex = DerivationIndex(42).getOrNull()!!,
|
||||
expected = "555C1E17A302659446C97393453B7C2B3246AF4DA082C56C28FB6EDD1A6606A4",
|
||||
),
|
||||
)
|
||||
|
||||
data class ForCryptoPortfolioModel(
|
||||
val derivationIndex: DerivationIndex,
|
||||
val expected: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -115,38 +115,18 @@ class AccountTest {
|
|||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke returns NegativeDerivationIndex`() {
|
||||
// Arrange
|
||||
val derivationIndex = -1
|
||||
|
||||
// Act
|
||||
val actual = CryptoPortfolio(
|
||||
accountId = mockk(),
|
||||
name = "Test Account",
|
||||
accountIcon = mockk(),
|
||||
derivationIndex = derivationIndex,
|
||||
isArchived = false,
|
||||
cryptoCurrencyList = mockk(),
|
||||
)
|
||||
.leftOrNull()!!
|
||||
|
||||
// Assert
|
||||
val expected = CryptoPortfolio.Error.NegativeDerivationIndex
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke returns CryptoPortfolio`() {
|
||||
// Act
|
||||
val derivationIndex = DerivationIndex.Main
|
||||
val actual = CryptoPortfolio(
|
||||
accountId = AccountId(
|
||||
value = "value",
|
||||
accountId = AccountId.forCryptoPortfolio(
|
||||
userWalletId = UserWalletId("011"),
|
||||
derivationIndex = derivationIndex,
|
||||
),
|
||||
name = "Test Account",
|
||||
accountIcon = CryptoPortfolioIcon.ofMainAccount(userWalletId = UserWalletId("011")),
|
||||
derivationIndex = 0,
|
||||
derivationIndex = derivationIndex.value,
|
||||
isArchived = false,
|
||||
cryptoCurrencyList = CryptoCurrencyList(
|
||||
currencies = emptySet(),
|
||||
|
|
@ -165,24 +145,27 @@ class AccountTest {
|
|||
fun createMainAccount() {
|
||||
// Arrange
|
||||
val userWalletId = UserWalletId("011")
|
||||
val derivationIndex = DerivationIndex.Main
|
||||
|
||||
// Act
|
||||
val actual = CryptoPortfolio.createMainAccount(userWalletId = userWalletId)
|
||||
|
||||
// Assert
|
||||
// TODO: [REDACTED_JIRA]
|
||||
val expected = CryptoPortfolio(
|
||||
accountId = AccountId(userWalletId = userWalletId, value = "main_account"),
|
||||
accountId = AccountId.forCryptoPortfolio(
|
||||
userWalletId = userWalletId,
|
||||
derivationIndex = derivationIndex,
|
||||
),
|
||||
accountName = AccountName.Main,
|
||||
accountIcon = CryptoPortfolioIcon.ofMainAccount(userWalletId),
|
||||
derivationIndex = 0,
|
||||
derivationIndex = derivationIndex,
|
||||
isArchived = false,
|
||||
cryptoCurrencyList = CryptoCurrencyList(
|
||||
currencies = emptySet(),
|
||||
sortType = TokensSortType.NONE,
|
||||
groupType = TokensGroupType.NONE,
|
||||
),
|
||||
).getOrNull()
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
|
@ -194,11 +177,10 @@ class AccountTest {
|
|||
derivationIndex: Int = 0,
|
||||
currencies: Set<CryptoCurrency> = emptySet(),
|
||||
): CryptoPortfolio {
|
||||
val accountIndex = DerivationIndex(value = derivationIndex).getOrNull()!!
|
||||
|
||||
return CryptoPortfolio.invoke(
|
||||
accountId = AccountId(
|
||||
value = "value",
|
||||
userWalletId = userWalletId,
|
||||
),
|
||||
accountId = AccountId.forCryptoPortfolio(userWalletId = userWalletId, derivationIndex = accountIndex),
|
||||
name = name,
|
||||
accountIcon = CryptoPortfolioIcon.ofMainAccount(userWalletId),
|
||||
derivationIndex = derivationIndex,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
package com.tangem.domain.models.account
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.google.common.truth.Truth
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class DerivationIndexTest {
|
||||
|
||||
@Test
|
||||
fun `isMain returns true only for main derivation index`() {
|
||||
// Arrange
|
||||
val main = DerivationIndex.Main
|
||||
val notMain = DerivationIndex(1).getOrNull()!!
|
||||
|
||||
// Act & Assert
|
||||
Truth.assertThat(main.isMain).isTrue()
|
||||
Truth.assertThat(notMain.isMain).isFalse()
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideTestModels")
|
||||
fun invoke(model: InvokeTestModel) {
|
||||
// Act
|
||||
val actual = DerivationIndex(model.index)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isEqualTo(model.expected)
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
InvokeTestModel(index = 0, expected = DerivationIndex.Main.right()),
|
||||
InvokeTestModel(index = 5, expected = DerivationIndex(5).getOrNull()!!.right()),
|
||||
InvokeTestModel(index = -1, expected = DerivationIndex.Error.NegativeDerivationIndex(-1).left()),
|
||||
)
|
||||
|
||||
data class InvokeTestModel(
|
||||
val index: Int,
|
||||
val expected: Either<DerivationIndex.Error, DerivationIndex>,
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue