diff --git a/.claude/rules/codestyle/drawable-naming.md b/.claude/rules/codestyle/drawable-naming.md new file mode 100644 index 0000000000..e18b07bd2e --- /dev/null +++ b/.claude/rules/codestyle/drawable-naming.md @@ -0,0 +1,19 @@ +# Image Resources + +## Naming + +There are 3 types of icons: + +1. Black or single color icon (naming: `ic_name_24`, where number is size) +2. Icon with constant color, and tint could be applied (naming: `img_name_24`) +3. Large image with different colors and shapes (naming: `ill_name`) + +Examples: + +1. `ic_chevron_24` +2. `img_walletconnect_24` +3. `ill_bussiness` + +## Attention + +For complex vector images (named with `ill_name`), you should use `.png` resources, because when the project is compiled, all complex vectors are converted to large, heavy PNGs for different dimensions. \ No newline at end of file diff --git a/.claude/rules/domain/core-components.md b/.claude/rules/domain/core-components.md new file mode 100644 index 0000000000..9b9584fd4e --- /dev/null +++ b/.claude/rules/domain/core-components.md @@ -0,0 +1,107 @@ +# Domain Components + +Key domain mechanisms that orchestrate data flow: suppliers, fetchers, and use cases. + +## Retrieving Core Models + +### UserWallet + +#### UserWalletsListRepository + +**Location:** `domain/common` — `com.tangem.domain.common.wallets.UserWalletsListRepository` + +Repository for managing user wallets list. Provides `StateFlow?>` for the wallets list and `StateFlow` for the selected wallet. Supports loading, selecting, saving, locking/unlocking (biometric, access code), deleting, and reordering wallets. + +### Account / AccountList + +#### SingleAccountSupplier + +**Location:** `domain/account` — `com.tangem.domain.account.supplier.SingleAccountSupplier` + +Supplier that provides a single `Account` by `AccountId`. Has convenience methods `filterPaymentAccount` and `filterCryptoPortfolioAccount` to filter by account subtype. + +#### SingleAccountListSupplier + +**Location:** `domain/account` — `com.tangem.domain.account.supplier.SingleAccountListSupplier` + +Supplier that provides an `AccountList` for a specific user wallet by `UserWalletId`. + +#### MultiAccountListSupplier + +**Location:** `domain/account` — `com.tangem.domain.account.supplier.MultiAccountListSupplier` + +Supplier that provides a list of `AccountList`s for all user wallets. Extends `FlowCachingSupplier`. + +#### SingleAccountListFetcher + +**Location:** `domain/account` — `com.tangem.domain.account.fetcher.SingleAccountListFetcher` + +Fetcher that fetches a list of accounts for a single wallet by `UserWalletId`. Extends `FlowFetcher`. + +### AccountStatus / AccountStatusList + +#### SingleAccountStatusSupplier + +**Location:** `domain/account/status` — `com.tangem.domain.account.status.supplier.SingleAccountStatusSupplier` + +Supplier that provides a single `AccountStatus` by account identifier. Extends `FlowCachingSupplier`. + +#### SingleAccountStatusListSupplier + +**Location:** `domain/account/status` — `com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier` + +Same as `SingleAccountListSupplier` but provides `AccountStatusList` (accounts with balances) for a specific user wallet. + +#### MultiAccountStatusListSupplier + +**Location:** `domain/account/status` — `com.tangem.domain.account.status.supplier.MultiAccountStatusListSupplier` + +Same as `MultiAccountListSupplier` but provides a list of `AccountStatusList`s for all user wallets. + +### Network / NetworkStatus + +#### SingleNetworkStatusSupplier + +**Location:** `domain/networks` — `com.tangem.domain.networks.single.SingleNetworkStatusSupplier` + +Supplier of `NetworkStatus` for a specific network and wallet. Extends `FlowCachingSupplier`. + +#### MultiNetworkStatusSupplier + +**Location:** `domain/networks` — `com.tangem.domain.networks.multi.MultiNetworkStatusSupplier` + +Supplier of all `NetworkStatus`es (as `Set`) for a selected wallet. Extends `FlowCachingSupplier`. + +#### SingleNetworkStatusFetcher + +**Location:** `domain/networks` — `com.tangem.domain.networks.single.SingleNetworkStatusFetcher` + +Fetcher of network status for a single `Network` by `UserWalletId`. Extends `FlowFetcher`. + +#### MultiNetworkStatusFetcher + +**Location:** `domain/networks` — `com.tangem.domain.networks.multi.MultiNetworkStatusFetcher` + +Fetcher of network statuses for a set of `Network`s for a multi-currency wallet by `UserWalletId`. Extends `FlowFetcher`. + +## Updating Balances + +### WalletBalanceFetcher + +**Location:** `domain/tokens` — `com.tangem.domain.tokens.wallet.WalletBalanceFetcher` + +Fetcher of wallet balances by `UserWalletId`. Selects the appropriate fetching strategy based on wallet type (multi-wallet, single wallet with tokens, single wallet). Delegates to `BalanceFetchingOperations` for shared fetching logic. + +### CryptoCurrencyBalanceFetcher + +**Location:** `domain/account/status` — `com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher` + +Fetches and refreshes balances for specific crypto currencies. Uses per-wallet mutexes to allow concurrent refreshes for different wallets while preventing concurrent refreshes for the same wallet. Delegates to `BalanceFetchingOperations`. + +## Managing Portfolio (User Tokens) + +### ManageCryptoCurrenciesUseCase + +**Location:** `domain/account/status` — `com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase` + +Use case for adding and removing crypto currencies in an account. \ No newline at end of file diff --git a/.claude/rules/domain/core-models.md b/.claude/rules/domain/core-models.md new file mode 100644 index 0000000000..bd7bf66aba --- /dev/null +++ b/.claude/rules/domain/core-models.md @@ -0,0 +1,142 @@ +# Domain Models + +Core business models used across the application. Models are defined in `domain/models/` and `domain/account/`. + +## StatusSource + +**Location:** `domain/models` — `com.tangem.domain.models.StatusSource` + +Enum representing data loading/refresh status. Used across all status models (NetworkStatus, QuoteStatus, YieldBalance, CryptoCurrencyStatus.Sources): +- `CACHE` — initial status, data loaded from cache +- `ACTUAL` — terminal status, data successfully fetched from server +- `ONLY_CACHE` — terminal status, data could not be refreshed (only cached data available) + +## CryptoCurrency + +**Location:** `domain/models` — `com.tangem.domain.models.currency.CryptoCurrency` + +Sealed class representing a cryptocurrency — either a native coin (`Coin`) or a token (`Token`). Used throughout the application: portfolio, token search, swaps, buy/sell, staking, etc. + +## CryptoCurrencyStatus + +**Location:** `domain/models` — `com.tangem.domain.models.currency.CryptoCurrencyStatus` + +Model representing a currency with its balance state. Primarily used to display user's coin balance in the portfolio. Wraps `CryptoCurrency` with a `Value` sealed interface: + +| Value subtype | Description | +|---|---| +| `Loading` | First-time fetch; once data is loaded, subsequent updates use cache via StatusSource, bypassing Loading | +| `Loaded` | Full data available | +| `Custom` | Custom token in portfolio; some data may be missing (e.g., no balance if backend has no quotes for it) | +| `NoQuote` | Balance known, no price data | +| `NoAccount` | Account not created (e.g., Solana reserve) | +| `Unreachable` | Network error | +| `NoAmount` | Coin is added to portfolio but no blockchain data available for it | +| `MissedDerivation` | Coin has no derivations — failed to obtain a blockchain network address | + +All Value subtypes carry `sources: Sources` tracking data freshness per dimension: `networkSource`, `quoteSource`, `stakingBalanceSource`, and aggregated `total`. + +## Network + +**Location:** `domain/models` — `com.tangem.domain.models.network.Network` + +Represents a blockchain network (e.g., Ethereum, Bitcoin). Contains network metadata: ID, name, currency symbol, derivation path, standard type (ERC20, TRC20, BEP20, etc.), and capabilities (token support, transaction extras, name resolving). + +## NetworkStatus + +**Location:** `domain/models` — `com.tangem.domain.models.network.NetworkStatus` + +Blockchain balances for all tokens of a network at a specific address. Only `Verified` and `NoAccount` are cached. + +| Value subtype | Description | +|---|---| +| `Verified` | Successful response from blockchain | +| `Unreachable` | Failed response from blockchain | +| `NoAccount` | Blockchain-specific status for chains that require a deposit to an address before it can be used | +| `MissedDerivation` | Derivation failed — no blockchain network address | + +## QuoteStatus + +**Location:** `domain/models` — `com.tangem.domain.models.quote.QuoteStatus` + +Exchange rate between the app's selected fiat currency and a coin's currency. + +## YieldBalance + +**Location:** `domain/models` — `com.tangem.domain.models.staking.YieldBalance` + +Staking yield balance for a specific `StakingID` (integrationId + address). + +## TotalFiatBalance + +**Location:** `domain/models` — `com.tangem.domain.models.TotalFiatBalance` + +Aggregate fiat balance across all tokens. Sealed interface with three states: `Loading`, `Failed`, `Loaded(amount, source)`. + +## TokenList + +**Location:** `domain/models` — `com.tangem.domain.models.tokenlist.TokenList` + +List of cryptocurrency tokens for display in portfolio. Sealed interface with subtypes: +- `GroupedByNetwork` — tokens grouped by `Network`, each group contains a list of `CryptoCurrencyStatus` +- `Ungrouped` — flat list of `CryptoCurrencyStatus` +- `Empty` — no tokens + +All subtypes carry `totalFiatBalance: TotalFiatBalance` and `sortedBy: TokensSortType`. + +## Account + +**Location:** `domain/models` — `com.tangem.domain.models.account.Account` + +Model representing a user account. Subtypes: +- **`Account.CryptoPortfolio`** — crypto portfolio with coins. All tokens in the account share the account's derivation (main account is an exception). Has a `DerivationIndex`: `0` for main account, `1..19` for secondary +- **`Account.Payment`** — account for Visa card integration + +## AccountStatus + +**Location:** `domain/models` — `com.tangem.domain.models.account.AccountStatus` + +Model representing an account with balances. Has a similar structure to `Account`: `CryptoPortfolio` and `Payment` subtypes. + +## AccountList + +**Location:** `domain/account` — `com.tangem.domain.account.models.AccountList` + +List of all accounts for a user wallet (`UserWallet`). + +Business rules (enforced by factory returning `Either`): +- Accounts list cannot be empty +- Exactly 1 main account +- Max 20 active accounts (`MAX_ACCOUNTS_COUNT`), max 1000 archived +- No duplicate AccountIds or custom AccountNames +- `totalAccounts >= activeAccounts` + +## AccountStatusList + +**Location:** `domain/account` — `com.tangem.domain.account.models.AccountStatusList` + +Same as `AccountList` but with balances (wraps `AccountStatus` instead of `Account`). + +## UserWallet + +**Location:** `domain/models` — `com.tangem.domain.models.wallet.UserWallet` + +Top-level model representing a user's wallet stored in the app. Subtypes: +- **`Cold`** — wallet backed by a physical Tangem card (NFC). Contains `ScanResponse`, card info, backup state +- **`Hot`** — software (hot) wallet without a physical card + +## Model Hierarchy + +``` +UserWallet + └─ AccountList / AccountStatusList + └─ Account.CryptoPortfolio / AccountStatus.CryptoPortfolio + ├─ CryptoCurrency (Coin | Token) + │ └─ CryptoCurrencyStatus (currency + Value state) + │ ├─ built from NetworkStatus (per network) + │ ├─ built from QuoteStatus (per rawCurrencyId) + │ └─ built from YieldBalance (per stakingId) + ├─ AccountId (SHA-256 hash) + ├─ DerivationIndex (0 = main) + └─ CryptoPortfolioIcon (icon + color) +``` \ No newline at end of file diff --git a/.claude/rules/git-rules.md b/.claude/rules/git-rules.md new file mode 100644 index 0000000000..fbfba63f29 --- /dev/null +++ b/.claude/rules/git-rules.md @@ -0,0 +1,22 @@ +# Git Rules + +## Branch Naming + +| Type | Format | Example | +|------|--------|---------| +| Feature | `feature/AND-xxx_short_description` | `feature/AND-13391_balance_fetcher` | +| Bugfix | `bugfix/AND-xxx_short_description` | `bugfix/AND-14000_fix_crash` | +| Pre-release | `x.x_pre_release` | `5.36_pre_release` | + +**Key branches:** +- `develop` — main integration branch, all feature/bugfix branches merge here +- `x.x_pre_release` — branched from `develop` on the last day of sprint for the upcoming release; receives regression bugfixes and additional release items +- `release` — merging into this branch triggers appTester build and production artifacts; PRs come from `x.x_pre_release` + +## Commit Messages + +Format: `AND-xxx Description` + +- Start with the Jira task number (AND-xxx) +- Followed by a space and a short description in English +- Example: `[REDACTED_TASK_KEY] Finalize CryptoCurrencyBalanceFetcher refactoring` \ No newline at end of file diff --git a/.claude/rules/tangem-sdk.md b/.claude/rules/tangem-sdk.md new file mode 100644 index 0000000000..daeb149428 --- /dev/null +++ b/.claude/rules/tangem-sdk.md @@ -0,0 +1,16 @@ +# Tangem SDK & Libraries + +## In-house SDKs (via `tangem_dependencies.toml`) + +- **Blockchain SDK** (`com.tangem:blockchain`) — multichain SDK for working with blockchains: creating/signing transactions, fetching balances, managing addresses. Wrapped in `libs/blockchain-sdk/` +- **Card SDK** (`com.tangem.tangem-sdk-kotlin:core`, `:android`) — SDK for interacting with physical Tangem cards via NFC: scanning, wallet creation, key derivation, passcode management, backup. Wrapped in `libs/tangem-sdk-api/` +- **Hot SDK** (`com.tangem.tangem-hot-sdk-kotlin:core`, `:android`) — SDK for hot (software) wallets +- **Vico** (`com.tangem.vico`) — forked charting library Vico, adapted for project needs + +## Wrapper Modules (`libs/`) + +- `libs/blockchain-sdk/` — wrapper around Blockchain SDK, provides domain-level abstractions for blockchain operations +- `libs/tangem-sdk-api/` — wrapper around Card SDK, exposes NFC card interaction API to the app +- `libs/crypto/` — cryptographic utilities: derivation, address handling, blockchain-specific helpers +- `libs/auth/` — API key provider interfaces for external services (Express, StakeKit) +- `libs/visa/` — Visa integration: smart contracts, limits, balances via Web3j \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..32252fee68 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,114 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Build & Test Commands + +```bash +# Build debug APK (Google flavor) +./gradlew :app:assembleGoogleDebug + +# Run all unit tests (debug/googleDebug variants + JVM modules) +./gradlew unitTest + +# Run tests for a single module +./gradlew :features:wallet:impl:testDebugUnitTest # Android library module +./gradlew :app:testGoogleDebugUnitTest # App module +./gradlew :domain:tokens:test # Pure JVM module + +# Run a single test class +./gradlew :core:ui:testDebugUnitTest --tests "com.tangem.core.ui.format.BigDecimalCryptoFormatTest" + +# Detekt (static analysis) — runs automatically via convention plugin on applicable modules +./gradlew detekt detektMain + +# Build UI tests APKs (for Marathon) +./gradlew :app:assembleGoogleMocked :app:assembleGoogleMockedAndroidTest +# 1. :app:assembleGoogleMocked — main APK (mocked build) +# 2. :app:assembleGoogleMockedAndroidTest — test APK with instrumented tests +``` + +**Product flavors:** `google` and `huawei` (dimension: `service`). Default development flavor is `google`. + +**Build types:** `debug`, `mocked`, `internal`, `external`, `release`. + +## Architecture Overview + +### Module Layers + +The project is a heavily modularized Android app (~220 modules) organized in layers: + +- **`app/`** — Application entry point, Hilt setup, navigation root +- **`domain/`** — Business logic and models. Each domain area (e.g., `tokens`, `wallets`, `card`) has a `models` submodule for pure data types and a core module for use cases +- **`data/`** — Repository implementations and data sources, mirrors domain structure +- **`features/`** — UI features using **API/Impl split pattern**: `features:foo:api` defines the public contract, `features:foo:impl` contains the implementation. This enforces clean dependency boundaries +- **`core/`** — Cross-cutting concerns: `ui`, `analytics`, `datasource`, `decompose`, `navigation`, `res`, `utils`, `security`, `pagination` +- **`common/`** — Shared models, routing, UI components, test utilities +- **`libs/`** — SDK wrappers: `blockchain-sdk`, `tangem-sdk-api`, `crypto`, `auth`, `visa` + +### Component Architecture (Decompose) + +The app uses [Decompose](https://github.com/arkivanov/Decompose) for lifecycle-aware components. Every feature screen follows this structure: + +**API module** (`features/{name}/api/`): +- `{Name}Component` interface implementing `ComposableContentComponent` +- Inner `Params` data class for input parameters +- Inner `Factory` interface: `fun create(context: AppComponentContext, params: Params): {Name}Component` + +**Impl module** (`features/{name}/impl/`): +- `Default{Name}Component` with `@AssistedInject` constructor taking `@Assisted appComponentContext: AppComponentContext` and `@Assisted params` +- Delegates `AppComponentContext by appComponentContext` +- Creates model via `getOrCreateModel(params)` +- `@Composable Content(modifier)` collects model state via `collectAsStateWithLifecycle()` +- Inner `@AssistedFactory` interface extending the public `Factory` + +**Model** (`features/{name}/impl/.../model/`): +- `{Name}Model` extending `Model` base class, annotated `@ModelScoped`, uses `@Inject` constructor +- Receives params via `ParamsContainer.require()` +- Exposes `StateFlow<{Name}UM>` (UM = UI Model, state class in `ui/state/` subpackage) +- Has `modelScope` (SupervisorJob + mainImmediate), auto-cancelled on destroy + +**Child navigation within features:** +- `childStack()` — stacked screen navigation (back stack) +- `childSlot()` — optional overlays/bottom sheets (single or no child) +- `InnerRouter` — feature-internal navigation that delegates unknown routes to parent router + +### Feature Package Conventions + +- API package: `com.tangem.features.{name}.api` (plural `features`) +- Impl package: `com.tangem.feature.{name}.impl` (singular `feature` — legacy inconsistency, follow existing pattern per feature) +- Component: `{Name}Component` (api), `Default{Name}Component` (impl) +- Model: `{Name}Model` in `model/` subpackage +- UI State: `{Name}UM` in `ui/state/` subpackage +- UI Composable: in `ui/` subpackage + +### Key Frameworks & Patterns + +- **DI:** Hilt with `@SingletonComponent` scope and custom `@ModelScoped` scope for model-lifecycle dependencies +- **UI:** Jetpack Compose with Material3. Image loading via Coil +- **Navigation:** Custom `AppRouter` + `AppRoute` sealed classes with deep link support via `DeepLinkBuilder` +- **Networking:** Retrofit + Moshi for API communication +- **Local storage:** `AppPreferencesStore` for key-value pairs, `DataStore` for larger data +- **Async:** Kotlin Coroutines + Flow. Inject `CoroutineDispatcherProvider` (from `core/utils`) instead of using `Dispatchers.*` directly — provides `main`, `mainImmediate`, `io`, `default`, `single` +- **Error handling:** Arrow's `Either` pattern throughout domain/data layers. `DataError` sealed hierarchy for domain errors. See `domain/core/CLAUDE.md` for the LCE pattern +- **Analytics:** `AnalyticsEvent(category, event, params)` in `core/analytics/models/`. Feature events are sealed class hierarchies extending `AnalyticsEvent`. Send via injected `AnalyticsEventHandler` +- **Feature toggles:** `FeatureTogglesManager` in `core/config-toggles/`. Toggles are defined in `core/config-toggles/src/main/assets/configs/feature_toggles_config.json` and auto-generated into a `FeatureToggles` enum by the convention plugin at build time. Each feature module exposes its own `XxxFeatureToggles` interface (in `api/`) with a `DefaultXxxFeatureToggles` implementation (in `impl/`) that delegates to `FeatureTogglesManager` +- **Supported languages:** `SupportedLanguages` in `core/utils/` defines the app's supported locales: en, ru, de, fr, it, ja, uk, zh, es. `getCurrentSupportedLanguageCode()` returns the device locale if supported, otherwise falls back to English. Used by API calls that accept a language parameter + +### Build System + +- **Gradle 8.14.1**, AGP 8.10.1, Kotlin 2.1.10 +- **Version catalogs:** `gradle/dependencies.toml` (external/third-party dependencies) and `gradle/tangem_dependencies.toml` (in-house Tangem SDK dependencies) +- **Convention plugin:** `plugins/configuration/` — applies Detekt, configures test settings, generates environment configs and feature toggles +- **Custom Detekt rules:** `plugins/detekt-rules/`. Detekt configuration is in the `tangem-android-tools` git submodule. Key rule: `UnsafeStringResourceUsage` — prevents direct `stringResource()` / `pluralStringResource()` calls; use the `Safe`-suffixed variants instead +- **Localization:** Managed via [Lokalise](https://lokalise.com). Update strings by running `python3 lokalize.py` +- **GitHub Packages auth:** Requires `gpr.user` and `gpr.key` in `local.properties` for Tangem SDK dependencies + +### Testing + +- **JUnit 5** (Jupiter) for unit tests +- **MockK** for mocking +- **Turbine** for Flow testing +- **Truth** for assertions +- **Marathon** for UI tests (emulator-based, configured via `Marathonfile`) +- Shared test utilities in `common:test` and `test/core/` \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListRepositoryModule.kt similarity index 99% rename from app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt rename to app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListRepositoryModule.kt index 1fab5009e0..d7d9089767 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListRepositoryModule.kt @@ -41,7 +41,7 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -internal object UserWalletsListManagerModule { +internal object UserWalletsListRepositoryModule { @Provides @Singleton diff --git a/core/datasource/CLAUDE.md b/core/datasource/CLAUDE.md new file mode 100644 index 0000000000..1d3332998b --- /dev/null +++ b/core/datasource/CLAUDE.md @@ -0,0 +1,30 @@ +# core/datasource + +## API Integration Guide + +### Config Structure + +- `ApiConfig` — base API config with `id` (`ApiConfig.ID`), `defaultEnvironment` (`ApiEnvironment`), and `environmentConfigs` (list of `ApiEnvironmentConfig`) +- `ApiEnvironmentConfig` — per-environment settings: `environment`, `baseUrl`, and `headers` (map of header name to `Provider`) + +### Config Management + +- `ApiConfigsManager` — DI-available component for accessing configs via `getEnvironmentConfig(id: ApiConfig.ID): ApiEnvironmentConfig` +- Two implementations: `ProdApiConfigsManager` (release) and `DevApiConfigsManager` (extends `MutableApiConfigsManager`, used when `BuildConfig.TESTER_MENU_ENABLED`) +- `MutableApiConfigsManager` allows runtime environment switching via Tester Menu without app restart + +### Adding a New API + +1. Create `ApiConfig` subclass in `com.tangem.datasource.api.common.config` — override `defaultEnvironment` and `environmentConfigs`. DI dependencies can be injected via constructor +2. Register the new config ID in `ApiConfig.initializeId(...)` +3. Provide the config in `ApiConfigsModule` using `@Provides @IntoSet` +4. Provide the API Retrofit service in `NetworkModule`: + - Get environment config: `apiConfigsManager.getEnvironmentConfig(id)` + - Use `environmentConfig.baseUrl` for Retrofit base URL + - Apply headers via `OkHttpClient.Builder().applyApiConfig(id, apiConfigsManager)` + +### Testing + +- Add the new config to `API_CONFIGS` list in `ProdApiConfigsManagerTest` +- Add a test model in the `data` method with expected `ApiEnvironmentConfig` values +- If the config has constructor dependencies, mock them and set up behavior in `setup()` \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/converter/BlockchainSDKConfigConverter.kt b/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/converter/BlockchainSDKConfigConverter.kt deleted file mode 100644 index fab336d555..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/converter/BlockchainSDKConfigConverter.kt +++ /dev/null @@ -1,122 +0,0 @@ -package com.tangem.datasource.local.config.environment.converter - -import com.tangem.blockchain.common.* -import com.tangem.datasource.local.config.environment.models.EnvironmentConfigModel -import com.tangem.utils.converter.Converter - -/** - * Converts [EnvironmentConfigModel] to [BlockchainSdkConfig] - * -[REDACTED_AUTHOR] - */ -internal object BlockchainSDKConfigConverter : Converter { - - override fun convert(value: EnvironmentConfigModel): BlockchainSdkConfig { - return BlockchainSdkConfig( - blockchairCredentials = BlockchairCredentials( - apiKey = value.blockchairApiKeys, - authToken = value.blockchairAuthorizationToken, - ), - blockcypherTokens = value.blockcypherTokens, - quickNodeSolanaCredentials = QuickNodeCredentials( - apiKey = value.quiknodeApiKey, - subdomain = value.quiknodeSubdomain, - ), - quickNodeBscCredentials = QuickNodeCredentials( - apiKey = value.bscQuiknodeApiKey, - subdomain = value.bscQuiknodeSubdomain, - ), - quickNodePlasmaCredentials = QuickNodeCredentials( - apiKey = value.quiknodePlasmaApiKey, - subdomain = value.quiknodePlasmaSubdomain, - ), - quickNodeMonadCredentials = QuickNodeCredentials( - apiKey = value.quiknodeMonadApiKey, - subdomain = value.quiknodeMonadSubdomain, - ), - infuraProjectId = value.infuraProjectId, - tronGridApiKey = value.tronGridApiKey, - nowNodeCredentials = NowNodeCredentials(value.nowNodesApiKey), - getBlockCredentials = createGetBlockCredentials(value), - kaspaSecondaryApiUrl = value.kaspaSecondaryApiUrl, - tonCenterCredentials = TonCenterCredentials( - mainnetApiKey = value.tonCenterKeys.mainnet, - testnetApiKey = value.tonCenterKeys.testnet, - ), - chiaFireAcademyApiKey = value.chiaFireAcademyApiKey, - chiaTangemApiKey = value.chiaTangemApiKey, - hederaArkhiaApiKey = value.hederaArkhiaKey, - polygonScanApiKey = value.polygonScanApiKey, - bittensorDwellirApiKey = value.bittensorDwellirApiKey, - bittensorOnfinalityApiKey = value.bittensorOnfinalityKey, - dwellirApiKey = value.dwellirApiKey, - koinosProApiKey = value.koinosProApiKey, - alephiumApiKey = value.alephiumTangemApiKey, - moralisApiKey = value.moralisApiKey, - etherscanApiKey = value.etherScanApiKey, - blinkApiKey = value.blinkApiKey, - tatumApiKey = value.tatumApiKey, - ) - } - - private fun createGetBlockCredentials(configValues: EnvironmentConfigModel): GetBlockCredentials? { - return configValues.getBlockAccessTokens?.let { accessTokens -> - GetBlockCredentials( - xrp = GetBlockAccessToken(jsonRpc = accessTokens.xrp?.jsonRPC), - cardano = GetBlockAccessToken(rosetta = accessTokens.cardano?.rosetta), - avalanche = GetBlockAccessToken(jsonRpc = accessTokens.avalanche?.jsonRPC), - eth = GetBlockAccessToken(jsonRpc = accessTokens.eth?.jsonRPC), - etc = GetBlockAccessToken(jsonRpc = accessTokens.etc?.jsonRPC), - fantom = GetBlockAccessToken(jsonRpc = accessTokens.fantom?.jsonRPC), - rsk = GetBlockAccessToken(jsonRpc = accessTokens.rsk?.jsonRPC), - bsc = GetBlockAccessToken(jsonRpc = accessTokens.bsc?.jsonRPC), - polygon = GetBlockAccessToken(jsonRpc = accessTokens.polygon?.jsonRPC), - gnosis = GetBlockAccessToken(jsonRpc = accessTokens.gnosis?.jsonRPC), - cronos = GetBlockAccessToken(jsonRpc = accessTokens.cronos?.jsonRPC), - solana = GetBlockAccessToken(jsonRpc = accessTokens.solana?.jsonRPC), - ton = GetBlockAccessToken(jsonRpc = accessTokens.ton?.jsonRPC), - tron = GetBlockAccessToken(rest = accessTokens.tron?.rest), - cosmos = GetBlockAccessToken(rest = accessTokens.cosmos?.rest), - near = GetBlockAccessToken(jsonRpc = accessTokens.near?.jsonRPC), - aptos = GetBlockAccessToken(rest = accessTokens.aptos?.rest), - dogecoin = GetBlockAccessToken( - jsonRpc = accessTokens.dogecoin?.jsonRPC, - blockBookRest = accessTokens.dogecoin?.blockBookRest, - ), - litecoin = GetBlockAccessToken( - jsonRpc = accessTokens.litecoin?.jsonRPC, - blockBookRest = accessTokens.litecoin?.blockBookRest, - ), - dash = GetBlockAccessToken( - jsonRpc = accessTokens.dash?.jsonRPC, - blockBookRest = accessTokens.dash?.blockBookRest, - ), - bitcoin = GetBlockAccessToken( - jsonRpc = accessTokens.bitcoin?.jsonRPC, - blockBookRest = accessTokens.bitcoin?.blockBookRest, - ), - algorand = GetBlockAccessToken(rest = accessTokens.algorand?.rest), - zkSyncEra = GetBlockAccessToken(jsonRpc = accessTokens.zksync?.jsonRPC), - polygonZkEvm = GetBlockAccessToken(jsonRpc = accessTokens.polygonZkevm?.jsonRPC), - base = GetBlockAccessToken(jsonRpc = accessTokens.base?.jsonRPC), - blast = GetBlockAccessToken(jsonRpc = accessTokens.blast?.jsonRPC), - filecoin = GetBlockAccessToken(jsonRpc = accessTokens.filecoin?.jsonRPC), - arbitrum = GetBlockAccessToken(jsonRpc = accessTokens.arbitrum?.jsonRPC), - bitcoinCash = GetBlockAccessToken( - jsonRpc = accessTokens.bitcoinCash?.jsonRPC, - blockBookRest = accessTokens.bitcoinCash?.blockBookRest, - ), - kusama = GetBlockAccessToken(jsonRpc = accessTokens.kusama?.jsonRPC), - moonbeam = GetBlockAccessToken(jsonRpc = accessTokens.moonbeam?.jsonRPC), - optimism = GetBlockAccessToken(jsonRpc = accessTokens.optimism?.jsonRPC), - polkadot = GetBlockAccessToken(jsonRpc = accessTokens.polkadot?.jsonRPC), - shibarium = GetBlockAccessToken(jsonRpc = accessTokens.shibarium?.jsonRPC), - sui = GetBlockAccessToken(jsonRpc = accessTokens.sui?.jsonRPC), - telos = GetBlockAccessToken(jsonRpc = accessTokens.telos?.jsonRPC), - tezos = GetBlockAccessToken(rest = accessTokens.tezos?.rest), - monad = GetBlockAccessToken(rest = accessTokens.monad?.rest), - stellar = GetBlockAccessToken(rest = accessTokens.stellar?.rest), - ) - } - } -} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/converter/EnvironmentConfigConverter.kt b/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/converter/EnvironmentConfigConverter.kt deleted file mode 100644 index 6d7733109a..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/converter/EnvironmentConfigConverter.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.tangem.datasource.local.config.environment.converter - -import com.tangem.datasource.local.config.environment.EnvironmentConfig -import com.tangem.datasource.local.config.environment.models.EnvironmentConfigModel -import com.tangem.utils.converter.Converter - -/** - * Converter from [EnvironmentConfigModel] to [EnvironmentConfig] - * -[REDACTED_AUTHOR] - */ -internal object EnvironmentConfigConverter : Converter { - - override fun convert(value: EnvironmentConfigModel): EnvironmentConfig { - return EnvironmentConfig( - moonPayApiKey = value.moonPayApiKey, - moonPayApiSecretKey = value.moonPayApiSecretKey, - mercuryoWidgetId = value.mercuryoWidgetId, - mercuryoSecret = value.mercuryoSecret, - blockchainSdkConfig = BlockchainSDKConfigConverter.convert(value = value), - amplitudeApiKey = value.amplitudeApiKey, - appsFlyerApiKey = value.appsFlyer.appsFlyerDevKey, - appsAppId = value.appsFlyer.appsFlyerAppID, - walletConnectProjectId = value.walletConnectProjectId, - express = value.express, - devExpress = value.devExpress, - stakeKitApiKey = value.stakeKitApiKey, - p2pApiKey = value.p2pApiKey, - blockAidApiKey = value.blockaidApiKey, - tangemApiKey = value.tangemApiKey, - tangemApiKeyDev = value.tangemApiKeyDev, - tangemApiKeyStage = value.tangemApiKeyStage, - yieldModuleApiKey = value.yieldModuleApiKey, - yieldModuleApiKeyDev = value.yieldModuleApiKeyDev, - bffStaticToken = value.bffStaticToken, - bffStaticTokenDev = value.bffStaticTokenDev, - gaslessTxApiKeyDev = value.gaslessTxApiKeyDev, - gaslessTxApiKey = value.gaslessTxApiKey, - ) - } -} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/models/EnvironmentConfigModel.kt b/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/models/EnvironmentConfigModel.kt deleted file mode 100644 index 152c16e531..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/models/EnvironmentConfigModel.kt +++ /dev/null @@ -1,140 +0,0 @@ -package com.tangem.datasource.local.config.environment.models - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -@Suppress("LongParameterList") -@JsonClass(generateAdapter = true) -class EnvironmentConfigModel( - @Json(name = "mercuryoWidgetId") val mercuryoWidgetId: String, - @Json(name = "mercuryoSecret") val mercuryoSecret: String, - @Json(name = "moonPayApiKey") val moonPayApiKey: String, - @Json(name = "moonPayApiSecretKey") val moonPayApiSecretKey: String, - @Json(name = "blockchairApiKeys") val blockchairApiKeys: List, - @Json(name = "blockchairAuthorizationToken") val blockchairAuthorizationToken: String?, - @Json(name = "quiknodeSubdomain") val quiknodeSubdomain: String, - @Json(name = "quiknodeApiKey") val quiknodeApiKey: String, - @Json(name = "bscQuiknodeSubdomain") val bscQuiknodeSubdomain: String, - @Json(name = "bscQuiknodeApiKey") val bscQuiknodeApiKey: String, - @Json(name = "quiknodePlasmaSubdomain") val quiknodePlasmaSubdomain: String, - @Json(name = "quiknodePlasmaApiKey") val quiknodePlasmaApiKey: String, - @Json(name = "quiknodeMonadSubdomain") val quiknodeMonadSubdomain: String, - @Json(name = "quiknodeMonadApiKey") val quiknodeMonadApiKey: String, - @Json(name = "nowNodesApiKey") val nowNodesApiKey: String, - @Json(name = "getBlockAccessTokens") val getBlockAccessTokens: GetBlockAccessTokens?, - @Json(name = "tonCenterApiKey") val tonCenterKeys: TonCenterKeys, - @Json(name = "blockcypherTokens") val blockcypherTokens: Set?, - @Json(name = "infuraProjectId") val infuraProjectId: String?, - @Json(name = "tronGridApiKey") val tronGridApiKey: String, - @Json(name = "amplitudeApiKey") val amplitudeApiKey: String, - @Json(name = "appsFlyer") val appsFlyer: AppsFlyerModel, - @Json(name = "kaspaSecondaryApiUrl") val kaspaSecondaryApiUrl: String, - @Json(name = "walletConnectProjectId") val walletConnectProjectId: String, - @Json(name = "chiaFireAcademyApiKey") val chiaFireAcademyApiKey: String?, - @Json(name = "chiaTangemApiKey") val chiaTangemApiKey: String?, - @Json(name = "devExpress") val devExpress: ExpressModel?, - @Json(name = "express") val express: ExpressModel?, - @Json(name = "hederaArkhiaKey") val hederaArkhiaKey: String?, - @Json(name = "polygonScanApiKey") val polygonScanApiKey: String?, - @Json(name = "stakeKitApiKey") val stakeKitApiKey: String?, - @Json(name = "p2pApiKey") val p2pApiKey: P2PKeys?, - @Json(name = "bittensorDwellirKey") val bittensorDwellirApiKey: String?, - @Json(name = "bittensorOnfinalityKey") val bittensorOnfinalityKey: String?, - @Json(name = "dwellirApiKey") val dwellirApiKey: String?, - @Json(name = "koinosProApiKey") val koinosProApiKey: String?, - @Json(name = "alephiumTangemApiKey") val alephiumTangemApiKey: String?, - @Json(name = "moralisApiKey") val moralisApiKey: String?, - @Json(name = "nftScanApiKey") val nftScanApiKey: String?, - @Json(name = "blockaidApiKey") val blockaidApiKey: String?, - @Json(name = "tangemApiKey") val tangemApiKey: String?, - @Json(name = "tangemApiKeyDev") val tangemApiKeyDev: String?, - @Json(name = "tangemApiKeyStage") val tangemApiKeyStage: String?, - @Json(name = "etherscanApiKey") val etherScanApiKey: String?, - @Json(name = "yieldModuleApiKey") val yieldModuleApiKey: String?, - @Json(name = "yieldModuleApiKeyDev") val yieldModuleApiKeyDev: String?, - @Json(name = "blinkApiKey") val blinkApiKey: String?, - @Json(name = "tatumApiKey") val tatumApiKey: String?, - @Json(name = "bffStaticToken") val bffStaticToken: String?, - @Json(name = "bffStaticTokenDev") val bffStaticTokenDev: String?, - @Json(name = "gaslessTxApiKeyDev") val gaslessTxApiKeyDev: String?, - @Json(name = "gaslessTxApiKey") val gaslessTxApiKey: String?, -) - -@JsonClass(generateAdapter = true) -data class GetBlockAccessTokens( - @Json(name = "xrp") val xrp: GetBlockToken?, - @Json(name = "cardano") val cardano: GetBlockToken?, - @Json(name = "avalanche") val avalanche: GetBlockToken?, - @Json(name = "ethereum") val eth: GetBlockToken?, - @Json(name = "ethereumClassic") val etc: GetBlockToken?, - @Json(name = "fantom") val fantom: GetBlockToken?, - @Json(name = "rsk") val rsk: GetBlockToken?, - @Json(name = "bsc") val bsc: GetBlockToken?, - @Json(name = "polygon") val polygon: GetBlockToken?, - @Json(name = "xdai") val gnosis: GetBlockToken?, - @Json(name = "cronos") val cronos: GetBlockToken?, - @Json(name = "solana") val solana: GetBlockToken?, - @Json(name = "ton") val ton: GetBlockToken?, - @Json(name = "tron") val tron: GetBlockToken?, - @Json(name = "cosmos-hub") val cosmos: GetBlockToken?, - @Json(name = "near") val near: GetBlockToken?, - @Json(name = "aptos") val aptos: GetBlockToken?, - @Json(name = "dogecoin") val dogecoin: GetBlockToken?, - @Json(name = "litecoin") val litecoin: GetBlockToken?, - @Json(name = "dash") val dash: GetBlockToken?, - @Json(name = "bitcoin") val bitcoin: GetBlockToken?, - @Json(name = "algorand") val algorand: GetBlockToken?, - @Json(name = "polygon-zkevm") val polygonZkevm: GetBlockToken?, - @Json(name = "zksync") val zksync: GetBlockToken?, - @Json(name = "base") val base: GetBlockToken?, - @Json(name = "blast") val blast: GetBlockToken?, - @Json(name = "filecoin") val filecoin: GetBlockToken?, - @Json(name = "arbitrum-one") val arbitrum: GetBlockToken?, - @Json(name = "bitcoinCash") val bitcoinCash: GetBlockToken?, - @Json(name = "kusama") val kusama: GetBlockToken?, - @Json(name = "moonbeam") val moonbeam: GetBlockToken?, - @Json(name = "optimism") val optimism: GetBlockToken?, - @Json(name = "polkadot") val polkadot: GetBlockToken?, - @Json(name = "shibarium") val shibarium: GetBlockToken?, - @Json(name = "sui") val sui: GetBlockToken?, - @Json(name = "telos") val telos: GetBlockToken?, - @Json(name = "tezos") val tezos: GetBlockToken?, - @Json(name = "monad") val monad: GetBlockToken?, - @Json(name = "stellar") val stellar: GetBlockToken?, -) - -@JsonClass(generateAdapter = true) -data class TonCenterKeys( - @Json(name = "mainnet") val mainnet: String, - @Json(name = "testnet") val testnet: String, -) - -@JsonClass(generateAdapter = true) -data class P2PKeys( - @Json(name = "mainnet") val mainnet: String, - @Json(name = "hoodi") val hoodi: String, -) - -@JsonClass(generateAdapter = true) -data class GetBlockToken( - @Json(name = "jsonRpc") val jsonRPC: String?, - @Json(name = "blockBookRest") val blockBookRest: String?, - @Json(name = "rest") val rest: String?, - @Json(name = "rosetta") val rosetta: String?, -) - -@JsonClass(generateAdapter = true) -data class ExpressModel( - @Json(name = "apiKey") - val apiKey: String, - @Json(name = "signVerifierPublicKey") - val signVerifierPublicKey: String, -) - -@JsonClass(generateAdapter = true) -data class AppsFlyerModel( - @Json(name = "appsFlyerDevKey") - val appsFlyerDevKey: String, - @Json(name = "appsFlyerAppID") - val appsFlyerAppID: String, -) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/models/EnvironmentConfigModels.kt b/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/models/EnvironmentConfigModels.kt new file mode 100644 index 0000000000..b3dcc73c77 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/models/EnvironmentConfigModels.kt @@ -0,0 +1,5 @@ +package com.tangem.datasource.local.config.environment.models + +data class ExpressModel(val apiKey: String, val signVerifierPublicKey: String) + +data class P2PKeys(val mainnet: String, val hoodi: String) \ No newline at end of file diff --git a/domain/core/CLAUDE.md b/domain/core/CLAUDE.md new file mode 100644 index 0000000000..facad07dc2 --- /dev/null +++ b/domain/core/CLAUDE.md @@ -0,0 +1,77 @@ +# domain/core + +Cross-cutting domain utilities for async data loading, error handling, and reactive streams. Not business logic — foundational abstractions used across all domain modules. + +## LCE (Loading-Content-Error) Pattern + +`Lce` — sealed class representing async operation state: +- `Loading(partialContent?)` — in progress, may carry partial data +- `Content(content)` — success +- `Error(error)` — failure with typed error + +Key APIs: +- `lce { }` builder — executes block in `LceRaise` context with Arrow's Raise DSL for typed error handling +- `lceFlow { }` builder — creates `LceFlow` (alias for `Flow>`) via channel-based producer DSL +- `LceRaise.bind()` — extracts content from Lce/Either or short-circuits on error +- Extensions: `fold()`, `map()`, `mapError()`, `toLce()`, `toEither()` + +## Flow Packaging + +A pattern for complex data streams where work on a single flow is split into three logically separate components: **Producer** (creation), **Supplier** (delivery/caching), and **Fetcher** (refresh). Use it only when you need flexibility in creating, reusing, fetching, and updating a data stream (e.g., network status). Do NOT use for simple cases like reading preferences. + +### FlowProducer + +`FlowProducer` — creates the data flow. Implement: +- `fallback: Data` — emitted when the flow throws an exception +- `produce(): Flow` — the actual flow creation logic + +Built-in `produceWithFallback()` catches errors, emits `fallback`, waits 2s, then retries — keeping the flow alive for subscribers. + +`FlowProducer.Factory` — creates a Producer from params. Typically implemented via Hilt `@AssistedFactory`. + +**Implementation pattern:** +1. Define interface extending `FlowProducer` with inner `Params` data class and `Factory` interface +2. Create `Default*Producer` with `@AssistedInject` constructor taking `@Assisted params` + dependencies +3. Override `fallback` and `produce()` +4. Declare inner `@AssistedFactory` interface extending the Producer's Factory + +### FlowSupplier / FlowCachingSupplier + +`FlowSupplier` — delivers a flow by params via `operator fun invoke(params): Flow`. Also provides `getSyncOrNull(params, timeout)` for one-shot access. + +`FlowCachingSupplier` — abstract implementation that caches flows by key. Implement: +- `factory: FlowProducer.Factory` — to create producers +- `keyCreator: (Params) -> String` — to generate cache keys + +Behavior: returns cached flow if exists, otherwise creates via `factory.create(params).produceWithFallback()`, caches it, and auto-evicts on terminal exception. + +**Implementation pattern:** +1. Define abstract class extending `FlowCachingSupplier` with `factory` and `keyCreator` in constructor +2. In DI module, create anonymous subclass providing the factory (injected) and keyCreator lambda + +### FlowFetcher + +`FlowFetcher` — triggers data refresh, returns `Either`. Typically updates a store/data source, causing the Producer's flow to re-emit. + +**Implementation pattern:** +1. Define interface extending `FlowFetcher` with inner `Params` data class +2. Create `Default*Fetcher` with `@Inject` constructor, override `invoke` wrapping logic in `Either.catch { }`, handle errors with `.onLeft { }` + +### Testing + +- **FlowProducer**: test flow creation logic, params usage, emission behavior, exception handling +- **FlowFetcher**: test successful update path and error path (exception thrown) + +## Chain Processing + +- `Chain` / `ResultChain` — single operation in a chain, works with `Either` +- `ChainProcessor` — folds chains sequentially, stops on first error + +## Error Types + +- `DataError` — sealed domain error hierarchy: `NetworkError.NoInternetConnection`, `UserWalletError.WrongUserWallet` + +## Either Extensions + +- `Either.catchOn(dispatcher, block)` — executes on dispatcher, catches exceptions +- `eitherOn(dispatcher, block)` — Raise DSL block on specified dispatcher \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt deleted file mode 100644 index cccba8a293..0000000000 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt +++ /dev/null @@ -1,161 +0,0 @@ -package com.tangem.domain.wallets.legacy - -import com.tangem.common.CompletionResult -import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.models.wallet.UserWalletId -import kotlinx.coroutines.flow.Flow - -interface UserWalletsListManager { - - /** - * Indicates that the [UserWalletsListManager] is [UserWalletsListManager.Lockable] - * */ - val isLockable: Boolean - - /** [Flow] with all saved [UserWallet]s updates */ - val userWallets: Flow> - - /** Count saved wallets updates */ - val savedWalletsCount: Flow - - /** [Flow] with selected [UserWallet] updates */ - @Deprecated("You should provide the selected wallet via routing parameters due to the scalability of the features") - val selectedUserWallet: Flow - - /** [List] with all saved [UserWallet]s updates */ - val userWalletsSync: List - - /** Selected [UserWallet] */ - @Deprecated("You should provide the selected wallet via routing parameters due to the scalability of the features") - val selectedUserWalletSync: UserWallet? - - /** Indicates that the [UserWalletsListManager] contains at least one saved [UserWallet] */ - val hasUserWallets: Boolean - - /** Count of saved user wallets */ - val walletsCount: Int - - /** - * Set [UserWallet] with provided [UserWalletId] as selected - * - * @param userWalletId [UserWalletId] of [UserWallet] which must be selected - * - * @return [CompletionResult.Success] with selected [UserWallet] or [CompletionResult.Failure] with - * [NoSuchElementException] if [UserWallet] with [userWalletId] not found - */ - suspend fun select(userWalletId: UserWalletId): CompletionResult - - /** - * Save provided user wallet and set it as selected - * - * @param userWallet [UserWallet] to save - * @param canOverride If false, then terminate with [UserWalletsListError.WalletAlreadySaved] when user tries - * to save an already saved card - * - * @return [CompletionResult] of operation - */ - suspend fun save(userWallet: UserWallet, canOverride: Boolean = false): CompletionResult - - /** - * Same as [save] but not change selected user wallet ID and not terminate with - * [UserWalletsListError.WalletAlreadySaved] if [UserWallet] already saved. - * Can terminate with [NoSuchElementException] if unable to find [UserWallet] with provided [UserWalletId]. - * - * @param userWalletId update [UserWallet] with that [UserWalletId] - * @param update lambda that receives stored [UserWallet] and returns updated [UserWallet] - * - * @return [CompletionResult.Success] with updated [UserWallet] or [CompletionResult.Failure] with - * [NoSuchElementException] if [UserWallet] with [userWalletId] not found - */ - suspend fun update( - userWalletId: UserWalletId, - update: suspend (UserWallet) -> UserWallet, - ): CompletionResult - - /** - * Delete saved [UserWallet]s with provided [UserWalletId]s. - * Sets [isLocked] as true if [userWallets] is empty or if all [userWallets] are locked. - * - * @param userWalletIds [UserWalletId]s of [UserWallet]s which must be deleted - * - * @return [CompletionResult] of operation - */ - suspend fun delete(userWalletIds: List): CompletionResult - - /** - * Clear all saved [UserWallet]s and set [isLocked] as true - * - * @return [CompletionResult] of operation - */ - suspend fun clear(): CompletionResult - - /** - * Get [UserWallet] with provided [UserWalletId] - * - * @return [CompletionResult.Success] with found [UserWallet] or [CompletionResult.Failure] with - * [NoSuchElementException] if [UserWallet] with [userWalletId] not found - */ - suspend fun get(userWalletId: UserWalletId): CompletionResult - - interface Lockable : UserWalletsListManager { - - /** - * Indicates that all [UserWallet]s is locked - * - * @see [isLocked] - * @see [UserWallet.isLocked] - */ - val lockedState: Flow - - /** - * Indicates that all [UserWallet]s is locked. Sync version. - * - * @see [lockedState] - * @see [UserWallet.isLocked] - */ - val isLocked: Boolean - - /** - * Receive saved [UserWallet]s, populate [userWallets] flow with it and set [lockedState] as false. - * - * @param type Defines the behavior of the operation. - * - * @return [CompletionResult] of operation, with selected [UserWallet] - * or null if there is no selected [UserWallet] - */ - suspend fun unlock(type: UnlockType): CompletionResult - - /** Remove [UserWallet]s from [userWallets] and set [lockedState] as true */ - fun lock() - - /** - * Defines the behavior of the [unlock] operation. - * */ - enum class UnlockType { - /** - * Ensures that all stored [UserWallet]s are unlocked, - * or throws [UserWalletsListError.NotAllUserWalletsUnlocked]. - * - * In this type [selectedUserWallet] is either a previously selected [UserWallet] or the first stored - * [UserWallet]. - * */ - ALL, - - /** - * Ensures that at least one stored [UserWallet] is unlocked, - * or throws [UserWalletsListError.NoUserWalletSelected]. - * - * In this type [selectedUserWallet] is the first stored and unlocked [UserWallet]. - * */ - ANY, - - /** - * Same as [ALL] type, but this type can not change [selectedUserWallet] while unlocking. - * */ - ALL_WITHOUT_SELECT, - } - } - - // For provider - companion object -} \ No newline at end of file