Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-20 12:47:25 +03:00
commit c50840894f
739 changed files with 27664 additions and 4701 deletions

View file

@ -0,0 +1,320 @@
---
name: cleanup-feature-toggles
description: Remove released feature toggles (version <= target) — deletes from config, removes toggle properties, inlines `true` in calling code, removes dead branches. CI-safe, no prompts.
allowed-tools: Read, Grep, Glob, Bash, Edit, Write, Agent
argument-hint: <version> [--dry-run] [--only <TOGGLE_NAME>]
---
Remove all feature toggles whose version is less than or equal to the target release version.
**CRITICAL: This skill runs on CI. NEVER ask questions. If anything is ambiguous, make the safer choice or skip the toggle.**
## Constants
- **Config file**: `core/config-toggles/src/main/assets/configs/feature_toggles_config.json`
- **Generated enum** (DO NOT edit): `core/config-toggles/build/generated/source/toggles/com/tangem/core/configtoggle/FeatureToggles.kt`
- **Dry-run mode**: check if `$ARGUMENTS` contains `--dry-run`. In dry-run mode, make NO file changes — only output what WOULD be removed (including affected files and usage sites).
- **Version**: extract the version number from `$ARGUMENTS` (e.g., `5.35`, `5.35.0`). The version is the first argument that matches a semver-like pattern (`X.Y` or `X.Y.Z`).
- **Only mode**: check if `$ARGUMENTS` contains `--only <TOGGLE_NAME>`. If present, process ONLY the specified toggle (it must still satisfy the version check). Multiple `--only` flags can be provided.
## Phase 0: Preflight Checks
### 0a. Parse Arguments
Extract `<version>`, optional `--dry-run`, and optional `--only <TOGGLE_NAME>` (repeatable) from `$ARGUMENTS`.
- If no version found: STOP with `FATAL: No version provided. Usage: /cleanup-feature-toggles <version> [--dry-run] [--only <TOGGLE_NAME>]`
- Validate version matches pattern `\d+\.\d+(\.\d+)?` — if not, STOP with `FATAL: Invalid version format.`
- Normalize version: if only `X.Y` is given, treat as `X.Y.0` for comparison.
- If `--only` flags are present, collect the toggle names into a filter list.
### 0b. Verify Git State
```bash
git status --porcelain 2>&1
```
- If output is empty (clean working tree) — OK.
- If there are uncommitted changes — STOP with: `FATAL: Working tree is not clean. Commit or stash changes before running this skill.`
Initialize an internal results list to track each toggle's outcome.
## Phase 1: Identify Toggles to Remove
1. Read `core/config-toggles/src/main/assets/configs/feature_toggles_config.json`.
2. For each toggle entry in the JSON array:
- If `version == "undefined"` → skip (unreleased feature, must not be removed).
- Parse the toggle's version as semver (normalize `X.Y` to `X.Y.0`).
- If toggle version **<=** target version → mark for removal.
3. If `--only` filter is active: keep only toggles whose `name` matches one of the `--only` values. If a `--only` toggle doesn't satisfy the version check, output a warning but still skip it.
4. Output the list of toggles marked for removal with their versions.
5. If no toggles match → output `No toggles to remove for version <version>` and stop.
6. If `--dry-run` mode → proceed to Phase 2 (research only, all toggles in parallel), then skip to Phase 7 to output the detailed summary. Do NOT make any file changes.
## Phase 2: Research (parallel)
Collect all information about all toggles **in parallel** before making any edits. Launch one `Agent` per toggle (all in a single message so they run concurrently). Each agent receives the toggle name and must return a structured report.
**Error handling rule**: if research fails for a toggle, record the failure reason and continue. Do NOT stop processing.
### Per-toggle research task (runs inside each Agent)
Each Agent performs the following read-only searches and returns a structured report:
#### 2a. Find the toggle property declaration and direct usages
Use `Grep` to search for `FeatureToggles.<TOGGLE_NAME>` (e.g., `FeatureToggles.WALLET_REORDER_FEATURE_ENABLED`) across the **entire** codebase.
This will find:
1. **`DefaultXxxFeatureToggles` property** — the standard wrapper. Extract:
- The **property name** (e.g., `isWalletReorderFeatureEnabled`)
- The **DefaultXxxFeatureToggles file path**
- The **XxxFeatureToggles interface name** (from the class's supertype)
2. **Direct `FeatureTogglesManager.isFeatureEnabled()` calls** — code that bypasses the wrapper and calls the manager directly. These are additional usage sites.
Also find the interface file:
- Use `Glob` to find the `XxxFeatureToggles.kt` file in `features/*/api/` or `core/*/`
Check if the toggle property or the `DefaultXxxFeatureToggles` class has **comments referencing additional cleanup** (e.g., `// Remove GiveTxPermissionBottomSheet and all dependencies with this toggle`). If found, record the comment text.
If the toggle reference is not found anywhere: report as `Skipped (no property found)`.
#### 2b. Find `@RemoveWithToggle` annotated code
Use `Grep` to search for `RemoveWithToggle` (without `@` or package prefix) across the entire codebase (excluding the annotation definition itself). Then filter matches to only those where the `toggleName` argument equals the current toggle name.
The annotation is defined in `core/utils/src/main/java/com/tangem/utils/annotations/RemoveWithToggle.kt` (`com.tangem.utils.annotations.RemoveWithToggle`). It has two parameters: `toggleName: String` (the toggle name) and `description: String` (optional hint).
Support all Kotlin annotation forms:
- `@RemoveWithToggle("TOGGLE_NAME")`
- `@RemoveWithToggle(toggleName = "TOGGLE_NAME")`
- `@com.tangem.utils.annotations.RemoveWithToggle("TOGGLE_NAME")`
- `@com.tangem.utils.annotations.RemoveWithToggle(toggleName = "TOGGLE_NAME")`
For each filtered match, record:
- The file path and line number
- The annotated element name (class, function, property)
- The `description` value if present
#### 2c. Find all usages of the property in calling code
Use `Grep` to search for the property name (e.g., `isWalletReorderFeatureEnabled`) across the entire codebase.
Categorize results:
- **Interface declaration** — the `val isX: Boolean` in `XxxFeatureToggles.kt`
- **Implementation** — the `override val isX` in `DefaultXxxFeatureToggles.kt`
- **Calling code** — any other file that reads `*.isX` (include file path, line number, and the matched line content)
#### Agent report format
Each Agent must return a report with:
- Toggle name
- Property name (e.g., `isWalletReorderFeatureEnabled`) or `null` if not found
- Interface name and file path
- Implementation file path
- List of calling code sites: `[{file, line, content}]`
- List of direct `FeatureTogglesManager` usage sites: `[{file, line, content}]`
- List of `@RemoveWithToggle` sites: `[{file, line, element, description}]`
- Cleanup comments (if any)
- Status: `ready` or `skipped (reason)`
### After all Agents complete
Collect all reports. If `--dry-run` → skip to Phase 6 with the collected data.
## Phase 3: Edit (sequential)
Process each toggle **sequentially** using the research data from Phase 2. Only toggles with status `ready` are processed.
**Error handling rule**: if ANY step fails for a toggle, record the failure reason and continue to the next toggle. Do NOT stop processing.
### Step 3a: Replace usages in calling code with `true` and simplify
For each calling code usage site (from Phase 2 report), `Read` the surrounding context (at least 20 lines around the usage) and apply the appropriate simplification:
| Pattern | Simplification |
|---------|---------------|
| `if (toggles.isX) { body }` | Remove `if`, keep `body` (unindent) |
| `if (toggles.isX) { A } else { B }` | Keep only `A`, remove if/else structure |
| `if (!toggles.isX) { body }` | Remove entire if-block |
| `if (!toggles.isX) { A } else { B }` | Keep only `B`, remove if/else structure |
| `toggles.isX && expr` | Replace with `expr` |
| `expr && toggles.isX` | Replace with `expr` |
| `toggles.isX \|\| expr` | Replace with `true` (or simplify enclosing condition since it's always true) |
| `val x = toggles.isX` | Replace with `val x = true`, then check if `x` is used in one of the patterns above and simplify transitively |
| `property = toggles.isX` | Replace with `property = true` |
| `when { toggles.isX -> A; else -> B }` | Keep only `A`, remove the `when` structure |
| `when { !toggles.isX -> A; else -> B }` | Keep only `B`, remove the `when` structure |
| `when(value) { ... }` with toggle in a branch condition | Evaluate the toggle to `true`, simplify the `when` accordingly |
| Complex boolean expression | Replace `toggles.isX` with `true` and algebraically simplify |
Also process any direct `FeatureTogglesManager.isFeatureEnabled()` call sites the same way (replace with `true` and simplify).
**After replacing**, check if the file still references the `XxxFeatureToggles` type:
- If not → remove the import of `XxxFeatureToggles`
- If the type was a constructor/inject parameter and is no longer used → remove the parameter and any `@Inject`/`@Assisted` annotations associated with it
- If removing a constructor parameter from a Decompose Model or Component, also remove it from the caller that creates the instance
**Important**: Use `Edit` for precise changes. Read enough context to make correct edits. Do NOT accidentally delete unrelated code.
### Step 3b: Remove the property from interface and implementation
1. **In `XxxFeatureToggles` interface**: remove the `val isPropertyName: Boolean` line.
2. **In `DefaultXxxFeatureToggles`**: remove the `override val isPropertyName: Boolean` property (including the `get() = ...` line).
3. Check if `DefaultXxxFeatureToggles` still has other properties:
- If **yes** → done with this toggle.
- If **no** (all properties removed) → the interface and implementation are now empty. Check the **protected list** below — if the interface is protected, keep it and skip deletion. Otherwise, delete them:
**Protected interfaces (never delete even if empty):**
- `TokensFeatureToggles`
- `BlockchainSDKFeatureToggles`
- `StakingFeatureToggles`
- `CardSdkFeatureToggles`
- `TangemPayFeatureToggles`
- `SwapFeatureToggles`
- `SendFeatureToggles`
If the interface is **protected**: remove the `featureTogglesManager` / `featureToggles` constructor parameter from `DefaultXxxFeatureToggles`, remove unused imports (`FeatureTogglesManager`, `FeatureToggles`), but keep both files.
If the interface is **not protected** → delete them:
1. Delete the `XxxFeatureToggles` interface file.
2. Delete the `DefaultXxxFeatureToggles` implementation file.
3. Find and remove the Hilt binding for this interface (typically a `@Binds` method in a `*FeatureTogglesModule` or similar Hilt module). If the Hilt module has no remaining bindings after removal, delete the module file as well.
4. Use `Grep` to find all remaining references to `XxxFeatureToggles` and `DefaultXxxFeatureToggles` across the codebase. For each reference:
- **Constructor/inject parameter** → remove the parameter. If the surrounding class/function no longer uses any feature toggles, cascade the removal to its callers.
- **Import statement** → remove it.
- **Any other reference** → assess and remove or update as needed.
Record status as `Removed` with the count of usage sites simplified.
## Phase 4: Update JSON Config
1. Read `feature_toggles_config.json`.
2. Remove all entries whose `name` matches a successfully removed toggle (status = `Removed`).
3. Write back the JSON with proper formatting:
- 2-space indentation
- Each entry on its own lines
- No trailing commas
- Match the existing file format exactly
## Phase 5: Build, Test & Lint Verification
Run all verification tasks in a **single Gradle invocation** to avoid repeated cold starts:
### 5a. Build + Tests + Detekt
```bash
./gradlew assembleGoogleDebug unitTest detekt detektMain :app:assembleGoogleMocked :app:assembleGoogleMockedAndroidTest
```
- If the command **fails**:
- Read the error output to determine which task failed.
- **Compilation error** (`assembleGoogleDebug` or `assembleGoogleMocked`): attempt to fix (one retry — usually missing import removal or unused parameter). If still fails: revert all changes with `git checkout -- .` and output `FATAL: Build failed after cleanup. All changes reverted.` with the error details.
- **Unit test failure**: attempt to fix (one retry). If still fails: output the failures as warnings in the summary but do NOT revert.
- **Detekt violation**: attempt to fix (one retry — usually unused imports or parameters). If still fails: output the violations as warnings in the summary.
- **UI test compilation failure**: attempt to fix (one retry). If still fails: output the failures as warnings in the summary.
- After fixing, re-run the **full command** to verify everything passes together.
## Phase 6: Branch, Commit, Push & PR
Skip this phase entirely in `--dry-run` mode.
### 6a. Create Branch and Commit
```bash
git checkout -b tech/cleanup-toggles-<version>
git add -A
git commit -m "[Tech] Remove feature toggles <= <version>"
```
Replace `<version>` with the target version (e.g., `tech/cleanup-toggles-5.35`).
### 6b. Push
```bash
git push -u origin tech/cleanup-toggles-<version>
```
### 6c. Create Pull Request
Use `gh pr create` targeting `develop`:
```bash
gh pr create --base develop --title "Remove feature toggles <= <version>" --body "$(cat <<'EOF'
## Summary
Automated cleanup of feature toggles that are permanently enabled (version <= <version>).
### Removed toggles
- `TOGGLE_NAME_1` (version)
- `TOGGLE_NAME_2` (version)
- ...
### Manual review required
<List all @RemoveWithToggle-annotated elements found in Phase 2 research (file path, element name, description) and any cleanup comments. If none found, write "None">
## Test plan
- [x] `assembleGoogleDebug` passes
- [x] `unitTest` passes
- [x] `detekt detektMain` passes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
EOF
)"
```
Output the PR URL.
## Phase 7: Output Summary
Output results as a Markdown table:
```markdown
## Feature Toggle Cleanup Summary
| Toggle | Version | Interface | Usages Simplified | Status |
|--------|---------|-----------|-------------------|--------|
| WALLET_REORDER_FEATURE_ENABLED | 5.34 | WalletFeatureToggles | 3 | Removed |
| EARN_BLOCK_ENABLED | 5.35 | EarnFeatureToggles | 1 | Removed |
| SOME_TOGGLE | 5.33 | SomeFeatureToggles | — | Skipped (no property found) |
**Total:** X toggles processed, Y removed, Z skipped/failed
**Target version:** <version>
```
### Manual Review Hints
If any toggle had a comment referencing additional cleanup (found in Phase 2 research), output a separate section:
```markdown
### Manual Review Required
- **GASLESS_APPROVAL_ENABLED**: `// Remove GiveTxPermissionBottomSheet and all dependencies with this toggle`
- **OTHER_TOGGLE**: `// Also remove legacy FooBar component`
```
### Dry-run mode output
In `--dry-run` mode: prepend `[DRY RUN]` to the header, set all statuses to `Would remove`, and add a detailed section per toggle:
```markdown
### WALLET_REORDER_FEATURE_ENABLED (5.34) — Would remove
**Property:** `WalletFeatureToggles.isWalletReorderFeatureEnabled`
**Files affected:**
- `features/details/impl/.../UserWalletListModel.kt:42``walletFeatureToggles.isWalletReorderFeatureEnabled && userWallets.size > 1`
- `features/wallet/impl/.../SomeOtherFile.kt:88``if (walletFeatureToggles.isWalletReorderFeatureEnabled)`
```
### Warnings
If unit tests or detekt failed after fix attempts, list the remaining issues:
```markdown
### Warnings
- **Unit test failure:** `:features:wallet:impl:testDebugUnitTest` — WalletModelTest.someTest (may need manual update)
- **Detekt violation:** UnusedPrivateMember in `SomeFile.kt:15`
```

View file

@ -161,7 +161,7 @@ dependencies {
implementation(projects.domain.hotWallet)
implementation(projects.domain.news)
implementation(projects.domain.earn)
implementation(projects.domain.tokensync)
implementation(projects.domain.assetsdiscovery)
implementation(projects.domain.search)
implementation(projects.common)
@ -192,7 +192,7 @@ dependencies {
implementation(projects.data.common)
implementation(projects.data.settings)
implementation(projects.data.tokens)
implementation(projects.data.tokensync)
implementation(projects.data.assetsdiscovery)
implementation(projects.data.txhistory)
implementation(projects.data.wallets)
implementation(projects.data.analytics)
@ -274,6 +274,8 @@ dependencies {
implementation(projects.features.nft.impl)
implementation(projects.features.walletconnect.api)
implementation(projects.features.walletconnect.impl)
implementation(projects.features.commonFeatures.api)
implementation(projects.features.commonFeatures.impl)
implementation(projects.features.usedesk.api)
implementation(projects.features.usedesk.impl)
implementation(projects.features.hotWallet.api)

View file

@ -8,6 +8,8 @@ import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.intent.Intents
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeoutOrNull
import com.kaspersky.components.alluresupport.interceptors.step.ScreenshotStepInterceptor
import com.kaspersky.components.alluresupport.withForcedAllureSupport
import com.kaspersky.components.composesupport.config.addComposeSupport
@ -20,13 +22,14 @@ import com.tangem.common.rules.ApiEnvironmentRule
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.walletmanager.WalletManagersStore
import com.tangem.datasource.utils.WireMockRedirectInterceptor
import com.tangem.domain.promo.PromoRepository
import com.tangem.domain.promo.models.PromoId
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.tap.MainActivity
import dagger.hilt.android.testing.HiltAndroidRule
import io.qameta.allure.kotlin.Allure
import kotlinx.coroutines.runBlocking
import org.junit.Rule
import org.junit.rules.RuleChain
import org.junit.rules.TestRule
@ -58,6 +61,12 @@ abstract class BaseTestCase : TestCase(
@Inject
lateinit var promoRepository: PromoRepository
@Inject
lateinit var walletManagersStore: WalletManagersStore
@Inject
lateinit var getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase
private val hiltRule = HiltAndroidRule(this)
private val apiEnvironmentRule = ApiEnvironmentRule()
private val permissionRule = GrantPermissionRule.grant(
@ -72,7 +81,11 @@ abstract class BaseTestCase : TestCase(
private val semanticTreePrinterRule = object : TestWatcher() {
override fun failed(e: Throwable?, description: Description?) {
runCatching { printAllRoots() }
runCatching {
runBlocking {
withTimeoutOrNull(SEMANTIC_TREE_PRINT_TIMEOUT_MS) { printAllRoots() }
}
}
}
}
@ -174,5 +187,6 @@ abstract class BaseTestCase : TestCase(
private companion object {
const val WIREMOCK_BASE_URL_ARG = "wiremockBaseUrl"
const val SEMANTIC_TREE_PRINT_TIMEOUT_MS = 5_000L
}
}

View file

@ -35,6 +35,7 @@ object TestConstants {
const val WAIT_UNTIL_TIMEOUT = 20_000L
const val WAIT_UNTIL_TIMEOUT_LONG = 30_000L
const val WAIT_UNTIL_TIMEOUT_VERY_LONG = 60_000L
const val MARKETS_MAIN_NETWORK_SUFFIX = "MAIN"
@ -43,4 +44,14 @@ object TestConstants {
const val USER_TOKENS_API_SCENARIO = "user_tokens_api"
const val QUOTES_API_SCENARIO = "quotes_api"
const val SEED_PHRASE_12 = "they cram join fantasy unfair observe true theory buffalo bus exchange walk"
const val SEED_PHRASE_15 = "genuine try deer upset connect sausage diary rule price shallow fit faculty leopard " +
"hawk when"
const val SEED_PHRASE_18 = "crush idle include refuse expose kiss slot budget uphold when dinner certain holiday " +
"slow word armor butter suffer"
const val SEED_PHRASE_21 = "employ space oval venue wash clog zebra cover icon wash assist word debris inform " +
"cable meadow add game meat rigid pride"
const val SEED_PHRASE_24 = "force visit fresh brown razor target ill scissors figure cave feel genre cargo category " +
"bread much nature basic fun iron benefit egg error prosper"
}

View file

@ -0,0 +1,88 @@
package com.tangem.common.utils
import com.tangem.utils.logging.TangemLogger
import org.json.JSONArray
import org.json.JSONObject
import org.junit.Assert.assertTrue
/**
* Compares addresses from the app (clipboard JSON) with reference addresses from the QA tools API.
*
* Both JSON arrays contain objects with fields: blockchain, derivationPath, token (nullable), addresses (array).
* Comparison normalizes and sorts both arrays before diffing.
*/
object AddressComparisonHelper {
fun compareAddresses(appJson: String, apiJson: String) {
val appEntries = parseAndNormalize(appJson)
val apiEntries = parseAndNormalize(apiJson)
val missingInApp = apiEntries - appEntries.toSet()
val extraInApp = appEntries - apiEntries.toSet()
if (missingInApp.isEmpty() && extraInApp.isEmpty()) {
TangemLogger.i("Address comparison passed: ${appEntries.size} entries match")
return
}
val report = buildString {
appendLine("Address comparison FAILED")
if (missingInApp.isNotEmpty()) {
appendLine("\nMissing in app (expected from API but not found):")
missingInApp.forEach { appendLine(" - $it") }
}
if (extraInApp.isNotEmpty()) {
appendLine("\nExtra in app (found in app but not in API):")
extraInApp.forEach { appendLine(" - $it") }
}
appendLine("\nApp entries: ${appEntries.size}, API entries: ${apiEntries.size}")
}
TangemLogger.e(report)
assertTrue(report, false)
}
private fun parseAndNormalize(json: String): List<AddressEntry> {
val array = JSONArray(json)
val entries = mutableListOf<AddressEntry>()
for (i in 0 until array.length()) {
val obj = array.getJSONObject(i)
entries.add(
AddressEntry(
blockchain = normalizeBlockchainName(obj.getString("blockchain")),
derivationPath = obj.getString("derivationPath").trim(),
token = obj.optString("token", null)?.trim()?.lowercase(),
addresses = parseAddresses(obj).sorted(),
),
)
}
return entries.sortedWith(
compareBy<AddressEntry> { it.blockchain }
.thenBy { it.derivationPath }
.thenBy { it.token },
)
}
private val blockchainNameOverrides = mapOf(
"chia network" to "chia",
)
private fun normalizeBlockchainName(name: String): String {
val normalized = name.trim().lowercase()
return blockchainNameOverrides[normalized] ?: normalized
}
private fun parseAddresses(obj: JSONObject): List<String> {
val addressesArray = obj.getJSONArray("addresses")
return (0 until addressesArray.length()).map { addressesArray.getString(it) }
}
private data class AddressEntry(
val blockchain: String,
val derivationPath: String,
val token: String?,
val addresses: List<String>,
)
}

View file

@ -16,10 +16,10 @@ fun getWcUri(
TangemLogger.i("Getting WC URI for network: $network")
val client = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS) // Таймаут подключения
.readTimeout(60, TimeUnit.SECONDS) // Таймаут чтения ответа
.writeTimeout(30, TimeUnit.SECONDS) // Таймаут записи
.callTimeout(90, TimeUnit.SECONDS) // Общий таймаут запроса
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.callTimeout(90, TimeUnit.SECONDS)
.build()
val request = Request.Builder()
@ -58,6 +58,59 @@ fun getWcUri(
}
}
fun getAddressesFromApi(
seedKey: String,
baseUrl: String = "[REDACTED_ENV_URL]",
): String? {
TangemLogger.i("Getting addresses for seed key: $seedKey")
val client = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.callTimeout(90, TimeUnit.SECONDS)
.build()
val request = Request.Builder()
.url("$baseUrl/addresses")
.get()
.build()
return try {
client.newCall(request).execute().use { response ->
TangemLogger.i("Response code: ${response.code}")
if (response.isSuccessful) {
val body = response.body?.string() ?: ""
val contentType = response.header("Content-Type") ?: ""
if (!contentType.contains("application/json") && !body.trimStart().startsWith("{")) {
TangemLogger.e("Unexpected response (not JSON), Content-Type: $contentType, body: $body")
return null
}
val jsonObject = JSONObject(body)
val data = jsonObject.optJSONObject("data") ?: jsonObject
val seedData = data.optJSONArray(seedKey)
if (seedData != null) {
TangemLogger.i("Got addresses for $seedKey: ${seedData.length()} entries")
seedData.toString()
} else {
TangemLogger.e("No data found for seed key: $seedKey")
null
}
} else {
val errorBody = response.body?.string() ?: "No error body"
TangemLogger.e("Request failed: ${response.code}, body: $errorBody")
null
}
}
} catch (e: Exception) {
TangemLogger.e("Error getting addresses", e)
null
}
}
fun checkServiceHealth(
baseUrl: String = "[REDACTED_ENV_URL]"
): String? {

View file

@ -0,0 +1,122 @@
package com.tangem.scenarios
import android.view.KeyEvent
import androidx.test.core.app.ApplicationProvider
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_VERY_LONG
import com.tangem.common.utils.AddressComparisonHelper
import com.tangem.common.utils.getClipboardText
import com.tangem.screens.onMainScreen
import com.tangem.screens.onTesterMenuScreen
import com.tangem.utils.StringsSigns.DASH_SIGN
import com.tangem.utils.logging.TangemLogger
import io.qameta.allure.kotlin.Allure.step
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import org.junit.Assert.assertNotNull
private const val WALLET_MANAGERS_SETTLE_MS = 5_000L
private const val WALLET_MANAGERS_POLL_INTERVAL_MS = 500L
fun BaseTestCase.verifyAddresses(seedPhrase: String, apiAddressesJson: String) {
var appAddressesJson: String? = null
step("Open 'Main Screen' with existing hot wallet") {
openMainScreenWithExistingHotWallet(seedPhrase)
}
step("Assert wallet balance = '$DASH_SIGN'") {
composeTestRule.waitUntil(timeoutMillis = WAIT_UNTIL_TIMEOUT_VERY_LONG) {
runCatching { onMainScreen { totalBalanceText.assertTextContains(DASH_SIGN) } }.isSuccess
}
}
step("Assert 'Organize tokens' button is enabled") {
composeTestRule.waitUntil(timeoutMillis = WAIT_UNTIL_TIMEOUT_VERY_LONG) {
runCatching { onMainScreen { organizeTokensButton().assertIsEnabled() } }.isSuccess
}
}
step("Wait for all wallet managers to initialize") {
awaitWalletManagersStabilized()
}
step("Open tester menu") {
openTesterMenu()
}
step("Click on 'Addresses info' button") {
onTesterMenuScreen { addressesInfoButton.performClick() }
}
step("Click on 'JSON' tab") {
onTesterMenuScreen { jsonTab.performClick() }
}
step("Click on 'Copy' button") {
onTesterMenuScreen { copyButton.performClick() }
}
step("Get addresses JSON from clipboard") {
appAddressesJson = getClipboardText(ApplicationProvider.getApplicationContext())
assertNotNull("Clipboard is empty after copying addresses", appAddressesJson)
}
step("Compare app addresses with API reference") {
AddressComparisonHelper.compareAddresses(
appJson = requireNotNull(appAddressesJson),
apiJson = apiAddressesJson,
)
}
}
private const val TESTER_MENU_MAX_ATTEMPTS = 3
/**
* Presses 'Volume Down' twice to open tester menu.
* Retries up to [TESTER_MENU_MAX_ATTEMPTS] times if the menu doesn't appear.
*/
private fun BaseTestCase.openTesterMenu() {
repeat(TESTER_MENU_MAX_ATTEMPTS) { attempt ->
waitForIdle()
device.uiDevice.pressKeyCode(KeyEvent.KEYCODE_VOLUME_DOWN)
device.uiDevice.pressKeyCode(KeyEvent.KEYCODE_VOLUME_DOWN)
val opened = runCatching {
onTesterMenuScreen { addressesInfoButton.assertIsDisplayed() }
}.isSuccess
if (opened) {
TangemLogger.i("Tester menu opened on attempt ${attempt + 1}")
return
}
TangemLogger.w("Tester menu not opened on attempt ${attempt + 1}, retrying...")
}
error("Failed to open tester menu after $TESTER_MENU_MAX_ATTEMPTS attempts")
}
/**
* Polls [walletManagersStore] until the wallet manager count stops growing for [WALLET_MANAGERS_SETTLE_MS].
*
* Uses [getAllSync] with a polling interval instead of Flow, because the Flow only emits on changes
* if the count stabilizes, there would be no new emission to check the settle timeout against.
*/
private fun BaseTestCase.awaitWalletManagersStabilized() {
val walletId = getSelectedWalletSyncUseCase().getOrNull()?.walletId
?: error("No selected wallet found")
var lastSize = -1
var stableStart = System.currentTimeMillis()
runBlocking {
withTimeout(WAIT_UNTIL_TIMEOUT_VERY_LONG) {
while (true) {
val currentSize = walletManagersStore.getAllSync(walletId).size
val now = System.currentTimeMillis()
if (currentSize != lastSize) {
TangemLogger.i("Wallet managers count: $currentSize (was $lastSize)")
lastSize = currentSize
stableStart = now
} else if (now - stableStart >= WALLET_MANAGERS_SETTLE_MS) {
TangemLogger.i("Wallet managers stabilized at $currentSize entries")
return@withTimeout
}
delay(WALLET_MANAGERS_POLL_INTERVAL_MS)
}
}
}
}

View file

@ -15,11 +15,10 @@ fun BaseTestCase.scanCard(
mockContent: MockContent? = null,
isTwinsCard: Boolean = false,
) {
if (productType != null) {
MockProvider.setMocks(productType)
}
if (mockContent != null) {
MockProvider.setMocks(mockContent)
when {
mockContent != null -> MockProvider.setMocks(mockContent)
productType != null -> MockProvider.setMocks(productType)
else -> MockProvider.setMocks(ProductType.Wallet)
}
step("Click on 'Accept' button") {
onDisclaimerScreen { acceptButton.clickWithAssertion() }
@ -60,6 +59,57 @@ fun BaseTestCase.openMainScreen(
}
}
fun BaseTestCase.openMainScreenWithExistingHotWallet(seedPhrase: String) {
step("Click on 'Accept' button") {
onDisclaimerScreen { acceptButton.clickWithAssertion() }
}
step("Click on 'Get started' button") {
onStoriesScreen { getStartedButton.clickWithAssertion() }
}
step("Click on 'Start with Mobile Wallet' button") {
onCreateWalletStartScreen { startWithMobileWalletButton.performClick() }
}
step("Click on 'Import existing wallet' button") {
onCreateMobileWalletScreen { importExistingWalletButton.performClick() }
}
step("Click on 'Phrase text field'") {
onImportWalletScreen { phraseTextField.performClick() }
}
step("Type seed phrase in 'Phrase text field'") {
onImportWalletScreen { phraseTextField.performTextReplacement(seedPhrase) }
}
step("Click on 'Import' button") {
onImportWalletScreen {
importButton.assertIsEnabled()
importButton.performClick()
}
}
step("Click on 'Continue' button") {
onImportWalletScreen {
continueButton.assertIsEnabled()
continueButton.performClick()
}
}
step("Click on 'Skip' button") {
onImportWalletScreen { skipButton.performClick() }
}
step("Click on 'Skip anyway' dialog button") {
onDialog { skipAnywayButton.performClick() }
}
step("Click on 'Finish' button") {
onImportWalletScreen {
finishButton.assertIsEnabled()
finishButton.performClick()
}
}
step("Assert 'Main' screen is displayed") {
onMainScreen { screenContainer.assertIsDisplayed() }
}
step("Dismiss Market Tooltip by clicking close button") {
onMarketsTooltipScreen { closeButton.clickWithAssertion() }
}
}
fun BaseTestCase.synchronizeAddresses(
balance: String? = null,
isBalanceAvailable: Boolean = true

View file

@ -3,13 +3,11 @@ package com.tangem.scenarios
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG
import com.tangem.common.extensions.assertIsDimmed
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.utils.setWireMockScenarioState
import com.tangem.screens.*
import com.tangem.screens.onMainScreen
import com.tangem.screens.onSendConfirmScreen
import com.tangem.screens.onSendScreen
import com.tangem.screens.onTokenDetailsScreen
import io.qameta.allure.kotlin.Allure.step
fun BaseTestCase.openSendScreen(tokenName: String, mockState: String = "") {
@ -72,8 +70,10 @@ fun BaseTestCase.openSendConfirmScreen(
step("Type recipient address") {
onSendAddressScreen { addressTextField.performTextReplacement(recipientAddress) }
}
step("Click on 'Next' button") {
onSendAddressScreen { nextButton.clickWithAssertion() }
step("Click 'Next' button until 'Send Confirm' screen opens") {
composeTestRule.waitUntil(timeoutMillis = WAIT_UNTIL_TIMEOUT_LONG) {
runCatching { openSendConfirmScreenViaNextButton() }.isSuccess
}
}
}
@ -84,6 +84,9 @@ fun BaseTestCase.openSendAddressScreen(
step("Click on token with name: '$tokenName'") {
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
}
step("Assert 'Send' button is not dimmed") {
onTokenDetailsScreen { sendButton().assertIsDimmed(false) }
}
step("Click on 'Send' button") {
onTokenDetailsScreen { sendButton().performClick() }
}

View file

@ -0,0 +1,23 @@
package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.common.R
import com.tangem.core.ui.test.BaseButtonTestTags
import io.github.kakaocup.compose.node.element.ComposeScreen
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
import io.github.kakaocup.compose.node.element.KNode
import io.github.kakaocup.kakao.common.utilities.getResourceString
class CreateMobileWalletPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<CreateMobileWalletPageObject>(semanticsProvider = semanticsProvider) {
val importExistingWalletButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(R.string.hw_import_existing_wallet))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onCreateMobileWalletScreen(function: CreateMobileWalletPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -17,6 +17,11 @@ class CreateWalletStartPageObject(semanticsProvider: SemanticsNodeInteractionsPr
hasText(getResourceString(OnboardingImplR.string.welcome_unlock_card))
useUnmergedTree = true
}
val startWithMobileWalletButton: KNode = child {
hasText(getResourceString(OnboardingImplR.string.welcome_create_wallet_mobile_title))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onCreateWalletStartScreen(function: CreateWalletStartPageObject.() -> Unit) =

View file

@ -64,6 +64,11 @@ class DialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
hasTestTag(BaseButtonTestTags.BUTTON)
hasText(getResourceString(R.string.common_change))
}
val skipAnywayButton: KNode = child {
hasTestTag(BaseButtonTestTags.BUTTON)
hasText(getResourceString(R.string.access_code_alert_skip_ok))
}
}
internal fun BaseTestCase.onDialog(function: DialogPageObject.() -> Unit) =

View file

@ -0,0 +1,54 @@
package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.common.R
import com.tangem.core.ui.test.BaseButtonTestTags
import com.tangem.core.ui.test.ImportWalletScreenTestTags
import com.tangem.core.ui.test.TopAppBarTestTags
import io.github.kakaocup.compose.node.element.ComposeScreen
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
import io.github.kakaocup.compose.node.element.KNode
import io.github.kakaocup.kakao.common.utilities.getResourceString
import androidx.compose.ui.test.hasText as withText
class ImportWalletPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<ImportWalletPageObject>(semanticsProvider = semanticsProvider) {
val phraseTextField: KNode = child {
hasTestTag(ImportWalletScreenTestTags.PHRASE_TEXT_FIELD)
useUnmergedTree = true
}
val passphraseTextField: KNode = child {
hasTestTag(ImportWalletScreenTestTags.PASSPHRASE_TEXT_FIELD)
useUnmergedTree = true
}
val importButton: KNode = child {
hasTestTag(BaseButtonTestTags.BUTTON)
hasAnyChild(withText(getResourceString(R.string.common_import)))
useUnmergedTree = true
}
val continueButton: KNode = child {
hasTestTag(BaseButtonTestTags.BUTTON)
hasAnyChild(withText(getResourceString(R.string.common_continue)))
useUnmergedTree = true
}
val skipButton: KNode = child {
hasTestTag(TopAppBarTestTags.MORE_BUTTON)
hasText(getResourceString(R.string.common_skip))
useUnmergedTree = true
}
val finishButton: KNode = child {
hasTestTag(BaseButtonTestTags.BUTTON)
hasAnyChild(withText(getResourceString(R.string.common_finish)))
useUnmergedTree = true
}
}
internal fun BaseTestCase.onImportWalletScreen(function: ImportWalletPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -0,0 +1,39 @@
package com.tangem.screens
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import com.tangem.common.BaseTestCase
import com.tangem.feature.tester.impl.R as TesterImplR
import com.tangem.core.ui.test.BaseButtonTestTags
import com.tangem.core.ui.test.TopAppBarTestTags
import io.github.kakaocup.compose.node.element.ComposeScreen
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
import io.github.kakaocup.compose.node.element.KNode
import io.github.kakaocup.kakao.common.utilities.getResourceString
class TesterMenuPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
ComposeScreen<TesterMenuPageObject>(semanticsProvider = semanticsProvider) {
val backButton: KNode = child {
hasTestTag(TopAppBarTestTags.CLOSE_BUTTON)
useUnmergedTree = true
}
val addressesInfoButton: KNode = child {
hasTestTag(BaseButtonTestTags.TEXT)
hasText(getResourceString(TesterImplR.string.addresses_info))
useUnmergedTree = true
}
val jsonTab: KNode = child {
hasText("JSON")
useUnmergedTree = true
}
val copyButton: KNode = child {
hasContentDescription("Copy")
useUnmergedTree = true
}
}
internal fun BaseTestCase.onTesterMenuScreen(function: TesterMenuPageObject.() -> Unit) =
onComposeScreen(composeTestRule, function)

View file

@ -0,0 +1,86 @@
package com.tangem.tests.hotWallet
import com.tangem.common.BaseTestCase
import com.tangem.common.annotations.ApiEnv
import com.tangem.common.annotations.ApiEnvConfig
import com.tangem.common.constants.TestConstants.SEED_PHRASE_12
import com.tangem.common.constants.TestConstants.SEED_PHRASE_15
import com.tangem.common.constants.TestConstants.SEED_PHRASE_18
import com.tangem.common.constants.TestConstants.SEED_PHRASE_21
import com.tangem.common.constants.TestConstants.SEED_PHRASE_24
import com.tangem.common.utils.checkServiceHealth
import com.tangem.common.utils.getAddressesFromApi
import com.tangem.datasource.api.common.config.ApiConfig
import com.tangem.datasource.api.common.config.ApiEnvironment
import com.tangem.scenarios.verifyAddresses
import dagger.hilt.android.testing.HiltAndroidTest
import io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName
import org.junit.Assert.assertNotNull
import org.junit.Test
@HiltAndroidTest
class AddressesTest : BaseTestCase() {
private var apiAddressesJson: String? = null
private fun setupAddressTestHooks(seedKey: String) = setupHooks(
additionalBeforeAppLaunchSection = {
val status = checkServiceHealth()
assertNotNull("QA tools service is unreachable ([REDACTED_ENV_URL]", status)
apiAddressesJson = getAddressesFromApi(seedKey)
assertNotNull("Failed to fetch reference addresses for '$seedKey'", apiAddressesJson)
},
)
@ApiEnv(ApiEnvConfig(ApiConfig.ID.TangemTech, ApiEnvironment.PROD))
@AllureId("1792")
@DisplayName("Hot wallet: auto derivation addresses for seed 12")
@Test
fun seed12AddressesTest() {
setupAddressTestHooks("twelve").run {
verifyAddresses(SEED_PHRASE_12, requireNotNull(apiAddressesJson))
}
}
@ApiEnv(ApiEnvConfig(ApiConfig.ID.TangemTech, ApiEnvironment.PROD))
@AllureId("5106")
@DisplayName("Hot wallet: auto derivation addresses for seed 15")
@Test
fun seed15AddressesTest() {
setupAddressTestHooks("fifteen").run {
verifyAddresses(SEED_PHRASE_15, requireNotNull(apiAddressesJson))
}
}
@ApiEnv(ApiEnvConfig(ApiConfig.ID.TangemTech, ApiEnvironment.PROD))
@AllureId("5107")
@DisplayName("Hot wallet: auto derivation addresses for seed 18")
@Test
fun seed18AddressesTest() {
setupAddressTestHooks("eighteen").run {
verifyAddresses(SEED_PHRASE_18, requireNotNull(apiAddressesJson))
}
}
@ApiEnv(ApiEnvConfig(ApiConfig.ID.TangemTech, ApiEnvironment.PROD))
@AllureId("5108")
@DisplayName("Hot wallet: auto derivation addresses for seed 21")
@Test
fun seed21AddressesTest() {
setupAddressTestHooks("twenty_one").run {
verifyAddresses(SEED_PHRASE_21, requireNotNull(apiAddressesJson))
}
}
@ApiEnv(ApiEnvConfig(ApiConfig.ID.TangemTech, ApiEnvironment.PROD))
@AllureId("5109")
@DisplayName("Hot wallet: auto derivation addresses for seed 24")
@Test
fun seed24AddressesTest() {
setupAddressTestHooks("twenty_four").run {
verifyAddresses(SEED_PHRASE_24, requireNotNull(apiAddressesJson))
}
}
}

View file

@ -0,0 +1,151 @@
package com.tangem.tests.send.warnings
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.CARDANO_ADDRESS
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
import com.tangem.common.extensions.clickWithAssertion
import com.tangem.common.utils.resetWireMockScenarioState
import com.tangem.scenarios.checkSendWarning
import com.tangem.scenarios.openSendScreen
import com.tangem.screens.onSendAddressScreen
import com.tangem.screens.onSendScreen
import com.tangem.wallet.R
import dagger.hilt.android.testing.HiltAndroidTest
import io.github.kakaocup.kakao.common.utilities.getResourceString
import io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName
import org.junit.Test
@HiltAndroidTest
class CardanoWarningsTest : BaseTestCase() {
private val tokenName = "Cardano"
private val minAmount = "ADA 1.00"
private val invalidAmountTitle = getResourceString(R.string.send_notification_invalid_amount_title)
private val invalidAmountMessage =
getResourceString(R.string.send_notification_invalid_minimum_amount_text, minAmount, minAmount)
@AllureId("4204")
@DisplayName("Warnings: check warning, when remains less than 1 ADA")
@Test
fun afterTransactionRemainsLessThanMinimumAmountTest() {
val sendAmount = "19"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
resetWireMockScenarioState(QUOTES_API_SCENARIO)
}
).run {
step("Open 'Send Screen' with token: $tokenName") {
openSendScreen(tokenName)
}
step("Type '$sendAmount' in input text field") {
onSendScreen {
amountInputTextField.performClick()
amountInputTextField.performTextReplacement(sendAmount)
}
}
step("Click on 'Next' button") {
onSendScreen { nextButton.clickWithAssertion() }
}
step("Type address in input text field") {
onSendAddressScreen { addressTextField.performTextReplacement(CARDANO_ADDRESS) }
}
step("Click on 'Next' button") {
onSendAddressScreen { nextButton.clickWithAssertion() }
}
step("Assert 'Invalid amount' warning is displayed") {
checkSendWarning(
title = invalidAmountTitle,
message = invalidAmountMessage,
isDisplayed = true
)
}
}
}
@AllureId("4207")
@DisplayName("Warnings: check warning, when amount more than 1 ADA")
@Test
fun transactionAmountMoreThanOneTest() {
val sendAmount = "2.5"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
resetWireMockScenarioState(QUOTES_API_SCENARIO)
}
).run {
step("Open 'Send Screen' with token: $tokenName") {
openSendScreen(tokenName)
}
step("Type '$sendAmount' in input text field") {
onSendScreen {
amountInputTextField.performClick()
amountInputTextField.performTextReplacement(sendAmount)
}
}
step("Click on 'Next' button") {
onSendScreen { nextButton.clickWithAssertion() }
}
step("Type address in input text field") {
onSendAddressScreen { addressTextField.performTextReplacement(CARDANO_ADDRESS) }
}
step("Click on 'Next' button") {
onSendAddressScreen { nextButton.clickWithAssertion() }
}
step("Assert 'Invalid amount' warning is not displayed") {
checkSendWarning(
title = invalidAmountTitle,
message = invalidAmountMessage,
isDisplayed = false
)
}
}
}
@AllureId("4210")
@DisplayName("Warnings: check warning, when remains more than 1 ADA")
@Test
fun afterTransactionRemainsMoreThanMinimumAmountTest() {
val sendAmount = "18"
setupHooks(
additionalAfterSection = {
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
resetWireMockScenarioState(QUOTES_API_SCENARIO)
}
).run {
step("Open 'Send Screen' with token: $tokenName") {
openSendScreen(tokenName)
}
step("Type '$sendAmount' in input text field") {
onSendScreen {
amountInputTextField.performClick()
amountInputTextField.performTextReplacement(sendAmount)
}
}
step("Click on 'Next' button") {
onSendScreen { nextButton.clickWithAssertion() }
}
step("Type address in input text field") {
onSendAddressScreen { addressTextField.performTextReplacement(CARDANO_ADDRESS) }
}
step("Click on 'Next' button") {
onSendAddressScreen { nextButton.clickWithAssertion() }
}
step("Assert 'Invalid amount' warning is not displayed") {
checkSendWarning(
title = invalidAmountTitle,
message = invalidAmountMessage,
isDisplayed = false
)
}
}
}
}

View file

@ -0,0 +1,160 @@
package com.tangem.tests.send.warnings
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.ETHEREUM_RECIPIENT_ADDRESS
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG
import com.tangem.scenarios.checkSendWarning
import com.tangem.scenarios.openMainScreen
import com.tangem.scenarios.openSendConfirmScreen
import com.tangem.scenarios.synchronizeAddresses
import com.tangem.screens.onSendConfirmScreen
import com.tangem.screens.onSendSelectNetworkFeeBottomSheet
import com.tangem.wallet.R
import dagger.hilt.android.testing.HiltAndroidTest
import io.github.kakaocup.kakao.common.utilities.getResourceString
import io.qameta.allure.kotlin.AllureId
import io.qameta.allure.kotlin.junit4.DisplayName
import org.junit.Test
@HiltAndroidTest
class CommonWarningsTest : BaseTestCase() {
@AllureId("4293")
@DisplayName("Warnings: check warning, when custom fee lower than 'Slow'")
@Test
fun warningDisplayedWhenCustomFeeLowerThanSlowTest() {
val tokenName = "Ethereum"
val sendAmount = "0.01"
val feeUpTo = getResourceString(R.string.send_max_fee)
val customFee = "0.000000001"
val warningTitle = getResourceString(R.string.send_notification_transaction_delay_title)
val warningMessage = getResourceString(R.string.send_notification_transaction_delay_text)
setupHooks().run {
step("Open 'Main' screen") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Open 'Send Confirm' screen with token: $tokenName") {
openSendConfirmScreen(tokenName, sendAmount, ETHEREUM_RECIPIENT_ADDRESS)
}
step("Click on fee selector icon") {
flakySafely(WAIT_UNTIL_TIMEOUT) {
onSendConfirmScreen { feeSelectorIcon.performClick() }
onSendSelectNetworkFeeBottomSheet { customSelectorItem.assertIsDisplayed() }
}
}
step("Click on 'Custom' selector item") {
onSendSelectNetworkFeeBottomSheet { customSelectorItem.performClick() }
}
step("Type '$customFee' into input text field") {
onSendSelectNetworkFeeBottomSheet {
inputTextFieldValue(title = feeUpTo).performClick()
inputTextFieldValue(title = feeUpTo).performTextReplacement(customFee)
}
}
step("Click on 'Done' button") {
onSendSelectNetworkFeeBottomSheet { doneButton.performClick() }
}
step("Assert 'Transaction delays are possible' warning is displayed") {
checkSendWarning(
title = warningTitle,
message = warningMessage,
isDisplayed = true,
sendButtonIsDisabled = false
)
}
}
}
@AllureId("4221")
@DisplayName("Warnings: check warning, when amount exceeds total balance")
@Test
fun warningDisplayedWhenAmountExceedsTotalBalanceTest() {
val tokenName = "Ethereum"
val sendAmount = "0.9999"
val network = "ETH 0.00032"
val amount = "\$0.81"
val warningTitle = getResourceString(R.string.send_network_fee_warning_title)
val warningMessage = getResourceString(R.string.common_network_fee_warning_content, network, amount)
setupHooks().run {
step("Open 'Main' screen") {
openMainScreen()
}
step("Synchronize addresses") {
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
synchronizeAddresses()
}
}
step("Open 'Send Confirm' screen with token: $tokenName") {
openSendConfirmScreen(tokenName, sendAmount, ETHEREUM_RECIPIENT_ADDRESS)
}
step("Assert 'Network fee coverage' warning is displayed") {
checkSendWarning(
title = warningTitle,
message = warningMessage,
isDisplayed = true,
sendButtonIsDisabled = false
)
}
}
}
@AllureId("4294")
@DisplayName("Warnings: check warning, when custom fee is high")
@Test
fun warningDisplayedWhenCustomFeeIsHighTest() {
val tokenName = "Ethereum"
val sendAmount = "0.01"
val feeUpTo = getResourceString(R.string.send_max_fee)
val customFee = "0.004"
val timesHigher = "7"
val warningTitle = getResourceString(R.string.send_notification_fee_too_high_title)
val warningMessage = getResourceString(R.string.send_notification_fee_too_high_text, timesHigher)
setupHooks().run {
step("Open 'Main' screen") {
openMainScreen()
}
step("Synchronize addresses") {
synchronizeAddresses()
}
step("Open 'Send Confirm' screen with token: $tokenName") {
openSendConfirmScreen(tokenName, sendAmount, ETHEREUM_RECIPIENT_ADDRESS)
}
step("Click on fee selector icon") {
flakySafely(WAIT_UNTIL_TIMEOUT) {
onSendConfirmScreen { feeSelectorIcon.performClick() }
onSendSelectNetworkFeeBottomSheet { customSelectorItem.assertIsDisplayed() }
}
}
step("Click on 'Custom' selector item") {
onSendSelectNetworkFeeBottomSheet { customSelectorItem.performClick() }
}
step("Type '$customFee' into input text field") {
onSendSelectNetworkFeeBottomSheet {
inputTextFieldValue(title = feeUpTo).performClick()
inputTextFieldValue(title = feeUpTo).performTextReplacement(customFee)
}
}
step("Click on 'Done' button") {
onSendSelectNetworkFeeBottomSheet { doneButton.performClick() }
}
step("Assert 'Custom fee is high' warning is displayed") {
checkSendWarning(
title = warningTitle,
message = warningMessage,
isDisplayed = true,
sendButtonIsDisabled = false
)
}
}
}
}

View file

@ -232,6 +232,17 @@
android:scheme="tangem" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="token_exchanges"
android:scheme="tangem" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />

View file

@ -43,7 +43,6 @@ import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
import com.tangem.hot.sdk.TangemHotSdk
import com.tangem.tap.common.analytics.CustomerIoFeatureToggles
import com.tangem.tap.common.analytics.handlers.BlockchainExceptionHandler
import com.tangem.tap.common.analytics.handlers.appsflyer.AppsFlyerClient
import com.tangem.tap.common.log.TangemAppLoggerInitializer
@ -151,7 +150,5 @@ interface ApplicationEntryPoint {
fun getAppsFlyerClientFactory(): AppsFlyerClient.Factory
fun getCustomerIoFeatureToggles(): CustomerIoFeatureToggles
fun getScanFailsRequester(): ScanFailsRequester
}

View file

@ -60,7 +60,6 @@ import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
import com.tangem.operations.attestation.api.TangemApiServiceSettings
import com.tangem.tap.common.analytics.AnalyticsFactory
import com.tangem.tap.common.analytics.CustomerIoFeatureToggles
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
import com.tangem.tap.common.analytics.handlers.BlockchainExceptionHandler
import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler
@ -236,9 +235,6 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration.
private val appsFlyerClientFactory: AppsFlyerClient.Factory
get() = entryPoint.getAppsFlyerClientFactory()
private val customerIoFeatureToggles: CustomerIoFeatureToggles
get() = entryPoint.getCustomerIoFeatureToggles()
private val scanFailsRequester
get() = entryPoint.getScanFailsRequester()
@ -415,9 +411,7 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration.
factory.addHandlerBuilder(FirebaseAnalyticsHandler.Builder())
factory.addHandlerBuilder(AppsFlyerAnalyticsHandler.Builder(appsFlyerClientFactory))
if (customerIoFeatureToggles.isFeatureEnabled) {
factory.addHandlerBuilder(CustomerIoAnalyticsHandler.Builder())
}
factory.addFilter(oneTimeEventFilter)
factory.addFilter(AppsFlyerEventFilter())

View file

@ -1,13 +0,0 @@
package com.tangem.tap.common.analytics
import com.tangem.core.configtoggle.FeatureToggles
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
import javax.inject.Inject
class CustomerIoFeatureToggles @Inject constructor(
private val featureTogglesManager: FeatureTogglesManager,
) {
val isFeatureEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(toggle = FeatureToggles.CUSTOMER_IO_ENABLED)
}

View file

@ -3,19 +3,12 @@ package com.tangem.tap.common.pushes
import android.annotation.SuppressLint
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import com.tangem.tap.common.analytics.CustomerIoFeatureToggles
import com.tangem.utils.logging.TangemLogger
import dagger.hilt.android.AndroidEntryPoint
import io.customer.messagingpush.CustomerIOFirebaseMessagingService
import javax.inject.Inject
@AndroidEntryPoint
@SuppressLint("MissingFirebaseInstanceTokenRefresh")
internal class TangemPushNotificationService : FirebaseMessagingService() {
@Inject
lateinit var customerIoFeatureToggles: CustomerIoFeatureToggles
private val pushNotificationDelegate: PushNotificationDelegate by lazy {
PushNotificationDelegate(applicationContext)
}
@ -24,21 +17,17 @@ internal class TangemPushNotificationService : FirebaseMessagingService() {
super.onNewToken(token)
TangemLogger.d("New FCM token received: $token")
if (customerIoFeatureToggles.isFeatureEnabled) {
CustomerIOFirebaseMessagingService.onNewToken(applicationContext, token)
}
}
override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message)
if (customerIoFeatureToggles.isFeatureEnabled) {
CustomerIOFirebaseMessagingService.onMessageReceived(
context = applicationContext,
remoteMessage = message,
handleNotificationTrigger = false,
)
}
val notification = message.notification ?: return
val channelId = notification.channelId ?: TANGEM_CHANNEL_ID

View file

@ -1,7 +1,6 @@
package com.tangem.tap.common.redux
import com.tangem.tap.common.redux.global.globalReducer
import com.tangem.tap.features.details.redux.DetailsReducer
import com.tangem.tap.proxy.redux.DaggerGraphReducer
import org.rekotlin.Action
@ -10,7 +9,6 @@ fun appReducer(action: Action, state: AppState): AppState {
return AppState(
globalState = globalReducer(action, state),
detailsState = DetailsReducer.reduce(action, state),
daggerGraphState = DaggerGraphReducer.reduce(action, state),
)
}

View file

@ -1,10 +1,6 @@
package com.tangem.tap.common.redux
import com.tangem.tap.common.redux.global.GlobalMiddleware
import com.tangem.tap.common.redux.global.GlobalState
import com.tangem.tap.common.redux.legacy.LegacyMiddleware
import com.tangem.tap.features.details.redux.DetailsMiddleware
import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.proxy.redux.DaggerGraphMiddleware
import com.tangem.tap.proxy.redux.DaggerGraphState
import org.rekotlin.Middleware
@ -12,7 +8,6 @@ import org.rekotlin.StateType
data class AppState(
val globalState: GlobalState = GlobalState(),
val detailsState: DetailsState = DetailsState(),
val daggerGraphState: DaggerGraphState = DaggerGraphState(),
) : StateType {
@ -20,12 +15,9 @@ data class AppState(
fun getMiddleware(): List<Middleware<AppState>> {
return listOf(
logMiddleware,
GlobalMiddleware.handler,
DetailsMiddleware().detailsMiddleware,
LockUserWalletsTimerMiddleware().middleware,
AccessCodeRequestPolicyMiddleware().middleware,
DaggerGraphMiddleware.daggerGraphMiddleware,
LegacyMiddleware.legacyMiddleware,
)
}
}

View file

@ -1,6 +1,5 @@
package com.tangem.tap.common.redux.global
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.scan.ScanResponse
import org.rekotlin.Action
@ -8,10 +7,5 @@ sealed class GlobalAction : Action {
data class SaveScanResponse(val scanResponse: ScanResponse) : GlobalAction()
data class ChangeAppCurrency(val appCurrency: AppCurrency) : GlobalAction()
object RestoreAppCurrency : GlobalAction() {
data class Success(val appCurrency: AppCurrency) : GlobalAction()
}
data class IsSignWithRing(val isSignWithRing: Boolean) : GlobalAction()
}

View file

@ -1,43 +0,0 @@
package com.tangem.tap.common.redux.global
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.scope
import com.tangem.tap.store
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import org.rekotlin.Action
import org.rekotlin.Middleware
object GlobalMiddleware {
val handler = globalMiddlewareHandler
}
private val globalMiddlewareHandler: Middleware<AppState> = { _, _ ->
{ nextDispatch ->
{ action ->
handleAction(action)
nextDispatch(action)
}
}
}
private fun handleAction(action: Action) {
when (action) {
is GlobalAction.RestoreAppCurrency -> restoreAppCurrency()
}
}
private fun restoreAppCurrency() {
scope.launch {
val currency = store.inject(DaggerGraphState::appCurrencyRepository)
.getSelectedAppCurrency()
.firstOrNull()
?: AppCurrency.Default
store.dispatchWithMain(GlobalAction.RestoreAppCurrency.Success(currency))
}
}

View file

@ -13,13 +13,6 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
is GlobalAction.SaveScanResponse -> {
globalState.copy(scanResponse = action.scanResponse)
}
is GlobalAction.ChangeAppCurrency -> {
globalState.copy(appCurrency = action.appCurrency)
}
is GlobalAction.RestoreAppCurrency.Success -> {
globalState.copy(appCurrency = action.appCurrency)
}
is GlobalAction.IsSignWithRing -> globalState.copy(isLastSignWithRing = action.isSignWithRing)
else -> globalState
}
}

View file

@ -1,6 +1,5 @@
package com.tangem.tap.common.redux.global
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.domain.TapWalletManager
import org.rekotlin.StateType
@ -9,7 +8,6 @@ data class GlobalState(
@Deprecated("Use scan response from selected user wallet")
val scanResponse: ScanResponse? = null,
val tapWalletManager: TapWalletManager = TapWalletManager(),
val appCurrency: AppCurrency = AppCurrency.Default,
val isLastSignWithRing: Boolean = false,
) : StateType

View file

@ -1,79 +0,0 @@
package com.tangem.tap.common.redux.legacy
import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.redux.LegacyAction
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.details.redux.AppSettingsState
import com.tangem.tap.features.details.redux.DetailsAction
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
import org.rekotlin.Middleware
@Suppress("MemberNameEqualsClassName")
internal object LegacyMiddleware {
private val prepareDetailsScreenJobHolder = JobHolder()
val legacyMiddleware: Middleware<AppState> = { _, _ ->
{ next ->
{ action ->
when (action) {
is LegacyAction.PrepareDetailsScreen -> {
selectedUserWallet()
.distinctUntilChanged { old, new ->
if (old is UserWallet.Cold && new is UserWallet.Cold) {
old.walletId == new.walletId &&
old.scanResponse == new.scanResponse
} else {
old.walletId == new.walletId
}
}
.onEach { selectedUserWallet ->
val initializedAppSettingsStateContent = initializeAppSettingsState()
store.dispatchWithMain(
DetailsAction.PrepareScreen(
scanResponse = (selectedUserWallet as? UserWallet.Cold)?.scanResponse,
initializedAppSettingsState = initializedAppSettingsStateContent,
),
)
}
.flowOn(Dispatchers.IO)
.launchIn(scope)
.saveIn(prepareDetailsScreenJobHolder)
}
}
next(action)
}
}
}
private fun selectedUserWallet(): Flow<UserWallet> {
return store.inject(DaggerGraphState::userWalletsListRepository).selectedUserWallet.filterNotNull()
}
/**
* LEGACY: We need to initialize [AppSettingsState] async to avoid drawing blocking
* previously it was initialized in runBlocking and blocked details screen
*/
private suspend fun initializeAppSettingsState(): AppSettingsState {
return AppSettingsState(
selectedAppCurrency = store.state.globalState.appCurrency,
selectedThemeMode = store.inject(DaggerGraphState::appThemeModeRepository).getAppThemeMode().firstOrNull()
?: AppThemeMode.DEFAULT,
requireAccessCode = store.inject(DaggerGraphState::walletsRepository).requireAccessCode(),
useBiometricAuthentication = store.inject(DaggerGraphState::walletsRepository).useBiometricAuthentication(),
isHidingEnabled = store.inject(DaggerGraphState::balanceHidingRepository)
.getBalanceHidingSettings().isHidingEnabledInSettings,
needEnrollBiometrics = runCatching(tangemSdkManager::needEnrollBiometrics).getOrNull() == true,
hasSecuredWallets = store.inject(DaggerGraphState::userWalletsListRepository).hasSecuredWallets(),
)
}
}

View file

@ -1,13 +0,0 @@
package com.tangem.tap.core.ui
import com.tangem.core.configtoggle.FeatureToggles
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
import com.tangem.core.ui.HoldToConfirmButtonFeatureToggles
import javax.inject.Inject
class DefaultHoldToConfirmButtonFeatureToggles @Inject constructor(
private val featureTogglesManager: FeatureTogglesManager,
) : HoldToConfirmButtonFeatureToggles {
override val isHoldToConfirmEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(FeatureToggles.HOLD_TO_CONFIRM_BUTTON_ENABLED)
}

View file

@ -1,9 +1,7 @@
package com.tangem.tap.di.core.ui
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.core.ui.HoldToConfirmButtonFeatureToggles
import com.tangem.tap.core.ui.DefaultDesignFeatureToggles
import com.tangem.tap.core.ui.DefaultHoldToConfirmButtonFeatureToggles
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
@ -15,9 +13,4 @@ interface CoreUiBindsModule {
@Binds
fun bindDesignFeatureToggles(impl: DefaultDesignFeatureToggles): DesignFeatureToggles
@Binds
fun bindHoldToConfirmButtonFeatureToggles(
impl: DefaultHoldToConfirmButtonFeatureToggles,
): HoldToConfirmButtonFeatureToggles
}

View file

@ -0,0 +1,52 @@
package com.tangem.tap.di.domain
import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase
import com.tangem.domain.assetsdiscovery.repository.AssetsDiscoveryRepository
import com.tangem.domain.assetsdiscovery.usecase.AcknowledgeAssetsDiscoveryCompletionUseCase
import com.tangem.domain.assetsdiscovery.usecase.ObserveAssetsDiscoveryUseCase
import com.tangem.domain.assetsdiscovery.usecase.StartAssetsDiscoveryUseCase
import com.tangem.utils.coroutines.AppCoroutineScope
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object AssetsDiscoveryDomainModule {
@Provides
@Singleton
fun provideObserveAssetsDiscoveryUseCase(
assetsDiscoveryRepository: AssetsDiscoveryRepository,
): ObserveAssetsDiscoveryUseCase {
return ObserveAssetsDiscoveryUseCase(
assetsDiscoveryRepository = assetsDiscoveryRepository,
)
}
@Provides
@Singleton
fun provideAcknowledgeAssetsDiscoveryCompletionUseCase(
assetsDiscoveryRepository: AssetsDiscoveryRepository,
): AcknowledgeAssetsDiscoveryCompletionUseCase {
return AcknowledgeAssetsDiscoveryCompletionUseCase(
assetsDiscoveryRepository = assetsDiscoveryRepository,
)
}
@Provides
@Singleton
fun provideStartAssetsDiscoveryUseCase(
assetsDiscoveryRepository: AssetsDiscoveryRepository,
manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase,
appCoroutineScope: AppCoroutineScope,
): StartAssetsDiscoveryUseCase {
return StartAssetsDiscoveryUseCase(
assetsDiscoveryRepository = assetsDiscoveryRepository,
manageCryptoCurrenciesUseCase = manageCryptoCurrenciesUseCase,
appCoroutineScope = appCoroutineScope,
)
}
}

View file

@ -0,0 +1,78 @@
package com.tangem.tap.di.domain
import com.tangem.domain.dynamicaddresses.CreateConsolidationTransactionUseCase
import com.tangem.domain.dynamicaddresses.DisableDynamicAddressesUseCase
import com.tangem.domain.dynamicaddresses.EnableDynamicAddressesUseCase
import com.tangem.domain.dynamicaddresses.GetDynamicAddressesStatusUseCase
import com.tangem.domain.dynamicaddresses.GetDynamicReceiveAddressUseCase
import com.tangem.domain.dynamicaddresses.GetDerivedXpubUseCase
import com.tangem.domain.dynamicaddresses.IsXpubSupportedUseCase
import com.tangem.domain.dynamicaddresses.repository.ConsolidationRepository
import com.tangem.domain.dynamicaddresses.repository.DynamicAddressesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.derivations.DerivationsRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object DynamicAddressesDomainModule {
@Provides
@Singleton
fun provideEnableDynamicAddressesUseCase(
dynamicAddressesRepository: DynamicAddressesRepository,
): EnableDynamicAddressesUseCase {
return EnableDynamicAddressesUseCase(dynamicAddressesRepository)
}
@Provides
@Singleton
fun provideDisableDynamicAddressesUseCase(
dynamicAddressesRepository: DynamicAddressesRepository,
): DisableDynamicAddressesUseCase {
return DisableDynamicAddressesUseCase(dynamicAddressesRepository)
}
@Provides
@Singleton
fun provideGetDynamicAddressesStatusUseCase(
dynamicAddressesRepository: DynamicAddressesRepository,
): GetDynamicAddressesStatusUseCase {
return GetDynamicAddressesStatusUseCase(dynamicAddressesRepository)
}
@Provides
@Singleton
fun provideGetDynamicReceiveAddressUseCase(
dynamicAddressesRepository: DynamicAddressesRepository,
): GetDynamicReceiveAddressUseCase {
return GetDynamicReceiveAddressUseCase(dynamicAddressesRepository)
}
@Provides
@Singleton
fun provideCreateConsolidationTransactionUseCase(
consolidationRepository: ConsolidationRepository,
): CreateConsolidationTransactionUseCase {
return CreateConsolidationTransactionUseCase(consolidationRepository)
}
@Provides
@Singleton
fun provideIsXpubSupportedUseCase(walletManagersFacade: WalletManagersFacade): IsXpubSupportedUseCase {
return IsXpubSupportedUseCase(walletManagersFacade)
}
@Provides
@Singleton
fun provideGetDerivedXpubUseCase(
walletManagersFacade: WalletManagersFacade,
derivationsRepository: DerivationsRepository,
): GetDerivedXpubUseCase {
return GetDerivedXpubUseCase(walletManagersFacade, derivationsRepository)
}
}

View file

@ -2,6 +2,7 @@ package com.tangem.tap.di.domain
import com.tangem.domain.staking.*
import com.tangem.domain.staking.repositories.*
import com.tangem.domain.staking.toggles.StakingFeatureToggles
import com.tangem.domain.staking.single.SingleStakingBalanceFetcher
import com.tangem.domain.staking.usecase.StakingAvailabilityListUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade
@ -226,8 +227,14 @@ internal object StakingDomainModule {
@Provides
@Singleton
fun provideStakingIdFactory(walletManagersFacade: WalletManagersFacade): StakingIdFactory {
return StakingIdFactory(walletManagersFacade = walletManagersFacade)
fun provideStakingIdFactory(
walletManagersFacade: WalletManagersFacade,
stakingFeatureToggles: StakingFeatureToggles,
): StakingIdFactory {
return StakingIdFactory(
walletManagersFacade = walletManagersFacade,
stakingFeatureToggles = stakingFeatureToggles,
)
}
@Provides

View file

@ -1,50 +0,0 @@
package com.tangem.tap.di.domain
import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase
import com.tangem.domain.tokensync.repository.TokenSyncRepository
import com.tangem.domain.tokensync.usecase.AcknowledgeTokenSyncCompletionUseCase
import com.tangem.domain.tokensync.usecase.ObserveTokenSyncUseCase
import com.tangem.domain.tokensync.usecase.StartTokenSyncUseCase
import com.tangem.utils.coroutines.AppCoroutineScope
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object TokenSyncDomainModule {
@Provides
@Singleton
fun provideObserveTokenSyncUseCase(tokenSyncRepository: TokenSyncRepository): ObserveTokenSyncUseCase {
return ObserveTokenSyncUseCase(
tokenSyncRepository = tokenSyncRepository,
)
}
@Provides
@Singleton
fun provideAcknowledgeTokenSyncCompletionUseCase(
tokenSyncRepository: TokenSyncRepository,
): AcknowledgeTokenSyncCompletionUseCase {
return AcknowledgeTokenSyncCompletionUseCase(
tokenSyncRepository = tokenSyncRepository,
)
}
@Provides
@Singleton
fun provideStartTokenSyncUseCase(
tokenSyncRepository: TokenSyncRepository,
manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase,
appCoroutineScope: AppCoroutineScope,
): StartTokenSyncUseCase {
return StartTokenSyncUseCase(
tokenSyncRepository = tokenSyncRepository,
manageCryptoCurrenciesUseCase = manageCryptoCurrenciesUseCase,
appCoroutineScope = appCoroutineScope,
)
}
}

View file

@ -5,6 +5,9 @@ import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.demo.models.DemoConfig
import com.tangem.domain.dynamicaddresses.DynamicAddressesFeatureToggles
import com.tangem.domain.dynamicaddresses.GetDynamicReceiveAddressUseCase
import com.tangem.domain.dynamicaddresses.repository.DynamicAddressesRepository
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
import com.tangem.domain.notifications.repository.PushNotificationsRepository
@ -257,10 +260,16 @@ internal object TransactionDomainModule {
fun provideReceiveAddressesFactory(
getEnsNameUseCase: GetEnsNameUseCase,
getViewedTokenReceiveWarningUseCase: GetViewedTokenReceiveWarningUseCase,
getDynamicReceiveAddressUseCase: GetDynamicReceiveAddressUseCase,
dynamicAddressesRepository: DynamicAddressesRepository,
dynamicAddressesFeatureToggles: DynamicAddressesFeatureToggles,
): ReceiveAddressesFactory {
return ReceiveAddressesFactory(
getEnsNameUseCase = getEnsNameUseCase,
getViewedTokenReceiveWarningUseCase = getViewedTokenReceiveWarningUseCase,
getDynamicReceiveAddressUseCase = getDynamicReceiveAddressUseCase,
dynamicAddressesRepository = dynamicAddressesRepository,
dynamicAddressesFeatureToggles = dynamicAddressesFeatureToggles,
)
}

View file

@ -1,6 +1,13 @@
package com.tangem.tap.di.domain
import com.tangem.blockchain.common.TransactionSigner
import com.tangem.data.wallets.hot.TangemHotWalletSigner
import com.tangem.domain.card.common.TapWorkarounds.isTangemTwins
import com.tangem.domain.card.models.TwinKey
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.walletconnect.CheckIsWalletConnectAvailableUseCase
import com.tangem.domain.walletconnect.WcTransactionSignerProvider
import com.tangem.domain.walletconnect.repository.WalletConnectRepository
import com.tangem.domain.walletconnect.repository.WcSessionsManager
import com.tangem.domain.walletconnect.usecase.WcSessionsUseCase
@ -27,4 +34,27 @@ internal object WalletConnectDomainModule {
fun providesWcSessionsUseCase(sessionsManager: WcSessionsManager): WcSessionsUseCase {
return WcSessionsUseCase(sessionsManager)
}
@Provides
@Singleton
fun providesWcTransactionSignerProvider(
cardSdkConfigRepository: CardSdkConfigRepository,
tangemHotWalletSignerFactory: TangemHotWalletSigner.Factory,
): WcTransactionSignerProvider {
return object : WcTransactionSignerProvider {
override fun createSigner(wallet: UserWallet): TransactionSigner {
return when (wallet) {
is UserWallet.Hot -> tangemHotWalletSignerFactory.create(wallet)
is UserWallet.Cold -> {
val card = wallet.scanResponse.card
val isCardNotBackedUp = card.backupStatus?.isActive != true && !card.isTangemTwins
cardSdkConfigRepository.getCommonSigner(
cardId = card.cardId.takeIf { isCardNotBackedUp },
twinKey = TwinKey.getOrNull(scanResponse = wallet.scanResponse),
)
}
}
}
}
}
}

View file

@ -10,6 +10,7 @@ import com.tangem.common.KeyPair
import com.tangem.common.SuccessResponse
import com.tangem.common.authentication.keystore.DummyKeystoreManager
import com.tangem.common.core.CardSessionRunnable
import com.tangem.common.core.TangemSdkError
import com.tangem.common.core.UserCodeRequestPolicy
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.services.InMemoryStorage
@ -32,6 +33,8 @@ import com.tangem.sdk.api.TangemSdkManager
import com.tangem.sdk.api.visa.VisaCardActivationResponse
import com.tangem.sdk.api.visa.VisaCardActivationTaskMode
import com.tangem.tap.domain.sdk.mocks.MockProvider
import com.tangem.tap.domain.sdk.mocks.showMockCardPicker
import com.tangem.tap.foregroundActivityObserver
@Suppress("TooManyFunctions")
class MockTangemSdkManager(
@ -61,6 +64,17 @@ class MockTangemSdkManager(
allowsRequestAccessCodeFromRepository: Boolean,
shouldCheckIsAlreadyActivated: Boolean,
): CompletionResult<ScanResponse> {
if (!MockProvider.isPreset) {
val activity = foregroundActivityObserver.foregroundActivity
if (activity != null) {
val selectedMock = showMockCardPicker(activity)
if (selectedMock != null) {
MockProvider.setMocksWithoutPresetFlag(selectedMock)
} else {
return CompletionResult.Failure(TangemSdkError.UserCancelled())
}
}
}
return MockProvider.getScanResponse()
}

View file

@ -0,0 +1,33 @@
package com.tangem.tap.domain.sdk.mocks
import androidx.appcompat.app.AlertDialog
import com.tangem.wallet.R
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import kotlin.coroutines.resume
internal suspend fun showMockCardPicker(activity: AppCompatActivity): MockContent? = withContext(Dispatchers.Main) {
suspendCancellableCoroutine { continuation ->
val mocks = MockProvider.availableMocks
val names = mocks.map { it.first }.toTypedArray()
val dialog = AlertDialog.Builder(activity)
.setTitle(R.string.mock_card_picker_title)
.setItems(names) { _, which ->
if (continuation.isActive) {
continuation.resume(mocks[which].second)
}
}
.setOnCancelListener {
if (continuation.isActive) {
continuation.resume(null)
}
}
.create()
continuation.invokeOnCancellation { activity.runOnUiThread { dialog.dismiss() } }
dialog.show()
}
}

View file

@ -11,10 +11,40 @@ object MockProvider {
private var content: MockContent = getMockContent(ProductType.Wallet)
var isPreset: Boolean = false
private set
private var isEmulatingError: Boolean = false
private var emulatedError: TangemError = TangemSdkError.TagLost()
val availableMocks: List<Pair<String, MockContent>> = listOf(
"Wallet" to WalletMockContent,
"Note" to NoteMockContent,
"Twins" to TwinsMockContent,
"Ring" to RingMockContent,
"Wallet 2" to Wallet2MockContent,
"Wallet 2 (No Backup)" to Wallet2NoBackupMockContent,
"Wallet 2 (No Backup, No Wallets)" to Wallet2NoBackupNoWalletsMockContent,
"Wallet 2 (Seed Phrase)" to Wallet2WithSeedPhraseMockContent,
"Shiba" to ShibaMockContent,
"Shiba (No Backup)" to ShibaNoBackupMockContent,
"Shiba (No Backup, No Wallets)" to ShibaNoBackupNoWalletsMockContent,
"Ed25519 Curve" to EdCurveMockContent,
"Secp256k1 Curve" to Secpk1CurveMockContent,
"Backup Wallet" to BackupWalletMockContent,
"Dev Wallet" to DevWalletMockContent,
"Firmware 4.12" to Firmware412MockContent,
"French Blue (Triple)" to FrenchBlueMockContent,
"French White (Double)" to FrenchWhiteMockContent,
"Football Black (Double)" to FootballBlackMockContent,
"Football Dark Green (Triple)" to FootballDarkGreenMockContent,
"Metaplanet (Triple)" to MetaplanetMockContent,
"Metaplanet (Double)" to MetaplanetDoubleMockContent,
"Red Panda (Triple)" to RedPandaMockContent,
"Red Panda (Double)" to RedPandaDoubleMockContent,
)
fun setEmulateError(error: TangemError? = null) {
isEmulatingError = true
error?.let {
@ -28,10 +58,16 @@ object MockProvider {
fun setMocks(productType: ProductType) {
content = getMockContent(productType)
isPreset = true
}
fun setMocks(mockContent: MockContent) {
content = mockContent
isPreset = true
}
fun setMocksWithoutPresetFlag(mockContent: MockContent) {
content = mockContent
}
fun getSuccessResponse() = CompletionResult.Success(content.successResponse).orFailure()

View file

@ -0,0 +1,307 @@
package com.tangem.tap.domain.sdk.mocks.content
import com.tangem.common.SuccessResponse
import com.tangem.common.card.*
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ProductType
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.operations.attestation.Attestation
import com.tangem.operations.backup.PrimaryCard
import com.tangem.operations.derivation.DerivationTaskResponse
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.sdk.api.CreateProductWalletTaskResponse
import com.tangem.tap.domain.sdk.mocks.MockContent
import java.util.Date
object FootballBlackMockContent : MockContent {
private val primaryCard = PrimaryCard(
cardId = "AF99009000000000",
batchId = "AF990090",
cardPublicKey = byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5),
linkingKey = byteArrayOf( //
2, 121, 98, 127, -70, 14, 5, -23, -76, 115, -30, -26, 111, 17, 110, 34, -100, -121,
-57, -123, 74, 3, -91, 56, -20, 56, 50, -40, -101, 96, 82, 70, -91,
),
existingWalletsCount = 5, isHDWalletAllowed = true,
issuer = Card.Issuer(
name = "TANGEM SDK",
publicKey = byteArrayOf(2, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34),
),
manufacturer = Card.Manufacturer(
name = "TANGEM",
manufactureDate = Date(1743759687),
signature = byteArrayOf(),
),
walletCurves = listOf(
EllipticCurve.Secp256k1,
EllipticCurve.Ed25519,
EllipticCurve.Bls12381G2Aug,
EllipticCurve.Secp256r1,
EllipticCurve.Ed25519Slip0010,
EllipticCurve.Bls12381G2,
EllipticCurve.Bls12381G2Pop,
EllipticCurve.Bip0340,
),
firmwareVersion = FirmwareVersion(
major = 6,
minor = 33,
patch = 0,
type = FirmwareVersion.FirmwareType.Release,
),
isKeysImportAllowed = false,
certificate = null,
)
override val cardDto = CardDTO(
cardId = "AF99009000000000",
batchId = "AF990090",
cardPublicKey = byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5),
firmwareVersion = CardDTO.FirmwareVersion(
major = 6,
minor = 33,
patch = 0,
type = FirmwareVersion.FirmwareType.Release,
),
manufacturer = CardDTO.Manufacturer(
name = "TANGEM",
manufactureDate = Date(1698094800000),
signature = byteArrayOf(51, -12, 14, -56, 7, -39, 5, 63, 59, 24, 102, 99, -126, 124, -127, -108, -118, 71, -19, -71, 4, -47, -121, -46, 49, -51, -31, 100, -56, -15, 96, -37, 25, 82, 94, -88, 48, 98, -105, -97, -40, 41, 27, 116, 65, 26, 78, -85, 66, -94, -92, 15, 50, -2, 7, -69, 41, 56, -75, 59, 86, 68, -38, -3),
),
issuer = CardDTO.Issuer(
name = "TANGEM SDK",
publicKey = byteArrayOf(2, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34),
),
settings = CardDTO.Settings(
securityDelay = 15000,
maxWalletsCount = 20,
isSettingAccessCodeAllowed = true,
isSettingPasscodeAllowed = true,
isResettingUserCodesAllowed = false,
isLinkedTerminalEnabled = true,
isBackupAllowed = true,
supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None),
isFilesAllowed = true,
isHDWalletAllowed = true,
isKeysImportAllowed = true,
),
userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = true),
linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None,
isAccessCodeSet = true,
isPasscodeSet = false,
supportedCurves = listOf(
EllipticCurve.Secp256k1,
EllipticCurve.Ed25519,
EllipticCurve.Bls12381G2Aug,
EllipticCurve.Secp256r1,
EllipticCurve.Ed25519Slip0010,
EllipticCurve.Bls12381G2,
EllipticCurve.Bls12381G2Pop,
EllipticCurve.Bip0340,
),
wallets = listOf(
CardDTO.Wallet(
publicKey = byteArrayOf(3, 17, 59, -102, -56, 66, 10, 36, 97, -106, -92, -117, -105, -88, -2, -3, -95, -75, -54, -2, 57, 27, -90, -19, 82, 103, -12, -52, -126, -105, -120, -55, -2),
chainCode = byteArrayOf(-34, -28, -28, -12, 80, -109, -90, -31, -74, 36, -78, -21, 39, -125, -39, -91, 63, 40, -26, 3, 66, 27, -62, -55, -97, 52, -65, -11, 26, -80, 33, -45),
curve = EllipticCurve.Secp256k1,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 0,
hasBackup = true,
derivedKeys = mapOf(
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(2, 55, -114, -94, -73, -61, -50, 51, -115, 55, -79, 63, -96, -44, -64, -24, -36, -122, -123, -38, 81, -15, -127, -97, -42, 72, -85, -62, -98, 46, 119, 16, -55),
chainCode = byteArrayOf(31, 17, 71, -28, -29, 17, 72, -29, -98, 112, 31, -8, 72, -75, 4, -11, 60, -100, 9, 35, 58, -42, -38, 96, -71, -68, 24, -119, -43, -18, -122, 72),
),
DerivationPath("m/84'/0'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(2, -26, 103, 96, 112, 127, -125, 0, 7, 53, 30, -12, -82, 45, 14, 107, -9, 126, 75, 104, -67, -49, 35, -12, -82, -90, 101, -101, 125, -76, 88, -54, 99),
chainCode = byteArrayOf(-42, 36, 97, 65, -64, -113, 76, -91, -9, 11, 89, 123, -9, -3, 21, 103, -113, -60, 48, -31, -34, 108, 111, -38, -110, -80, 109, 17, -29, 2, 45, -71),
),
DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(2, -96, -3, -83, 101, 63, -25, -125, -4, -65, -42, -56, 24, -52, 118, -11, -104, -105, 40, -59, 20, -109, -97, 29, -95, -6, -80, 2, 67, 103, -80, -22, -94),
chainCode = byteArrayOf(-125, 27, -91, 38, -66, 109, -92, 16, -37, 93, 107, -29, -128, -1, 115, -64, 108, -63, 17, 27, 58, 78, -2, 39, 88, -39, 44, 89, 32, -38, -16, -38),
),
),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(-109, 42, -80, -115, -12, 44, 48, -118, -89, 10, 21, 59, 98, -110, -86, -14, 123, 105, -30, -49, -40, -4, -7, 32, 60, 67, -88, -52, -96, -10, -14, 123),
chainCode = byteArrayOf(-101, -10, -92, 72, 19, 121, -38, 105, -55, -82, -68, 13, -6, 17, 66, -10, -75, 58, 119, -127, 74, 68, 82, 25, 45, -110, 3, 3, 108, -97, -51, -127),
),
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(-109, 42, -80, -115, -12, 44, 48, -118, -89, 10, 21, 59, 98, -110, -86, -14, 123, 105, -30, -49, -40, -4, -7, 32, 60, 67, -88, -52, -96, -10, -14, 123),
chainCode = byteArrayOf(-101, -10, -92, 72, 19, 121, -38, 105, -55, -82, -68, 13, -6, 17, 66, -10, -75, 58, 119, -127, 74, 68, 82, 25, 45, -110, 3, 3, 108, -97, -51, -127),
curve = EllipticCurve.Ed25519,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 1,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(-64, 18, -128, 121, -89, -37, -99, 44, -125, -72, -111, -79, 7, 85, 40, 67, -39, 117, 123, 11, 105, -6, -5, -79, 19, -10, -29, 20, -14, -40, 5, 90),
chainCode = byteArrayOf(33, -97, 53, -112, 61, 112, -24, 74, -87, -85, -124, -4, 103, -94, -97, 76, -41, -27, 118, 33, 55, 121, -17, -52, 60, 122, 27, 25, 29, -76, 78, 11),
),
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(-118, -77, -109, 8, -119, 101, -91, 46, -44, 15, 52, -64, -92, 5, -102, 126, 52, 121, 63, -76, -54, 80, -82, -5, 31, -35, 105, -119, -96, -54, 38, 2, -6, 109, 117, -26, -47, -20, 108, 106, -69, 72, -37, -117, -30, -9, 104, -123),
chainCode = null,
curve = EllipticCurve.Bls12381G2Aug,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 1,
remainingSignatures = null,
index = 2,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = null,
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(17, -78, 53, 101, 96, -110, -10, -48, -84, 88, -64, 58, -26, -13, -2, -6, -25, 23, -79, -45, 58, 77, 52, -75, -123, 121, 32, 92, 84, -74, -111, -8),
chainCode = byteArrayOf(-110, 73, -124, -65, -1, -70, 64, -89, 95, -74, 15, 46, -110, 87, 15, -61, 120, -51, 111, 111, -45, 37, 123, -114, 65, -116, 21, 39, -77, 86, -3, -26),
curve = EllipticCurve.Bip0340,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 3,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(115, 14, 11, 0, -93, 81, -103, -95, -75, -84, 18, -120, -31, 76, -83, -81, 91, 25, -75, 36, -99, -53, -25, -15, -1, -57, 14, -39, 98, -116, -63, -123),
chainCode = byteArrayOf(23, 5, 38, -48, 67, -42, -31, -21, 89, 11, 22, -28, 44, -19, -115, -78, 123, -27, 57, 57, -24, -86, 55, 15, 104, 114, -36, 80, 81, -108, -41, 112),
),
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(-52, -92, 64, 108, -8, -100, 87, -86, -60, 18, -18, -81, 114, -84, 76, -24, 84, 52, -34, 79, -30, -66, -112, -55, -35, -119, 127, -109, 35, 18, -29, -25),
chainCode = byteArrayOf(94, -33, -94, -63, 40, -20, -26, 71, 111, 9, 87, -36, -42, -33, 40, -53, 85, 31, -36, -29, 65, -121, 2, -23, -119, -51, 114, -17, -39, -74, 23, -97),
curve = EllipticCurve.Ed25519Slip0010,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 4,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(-43, 1, -81, -47, -8, -103, -66, 42, 37, -7, 65, 54, 57, -24, 127, -89, 69, -112, 42, -46, -128, 36, -117, -28, 30, -48, 37, 52, 93, -47, 92, -47),
chainCode = byteArrayOf(4, -97, 81, -37, 76, -67, -87, -4, -82, -36, -45, -28, -117, -59, -62, 93, -73, 50, 65, -91, -83, 25, -95, 89, -64, -40, 113, 28, 59, 113, -99, 89),
),
isImported = false,
),
),
attestation = Attestation(
cardKeyAttestation = Attestation.Status.Verified,
walletKeysAttestation = Attestation.Status.Skipped,
firmwareAttestation = Attestation.Status.Skipped,
cardUniquenessAttestation = Attestation.Status.Skipped,
),
backupStatus = CardDTO.BackupStatus.Active(1),
)
override val scanResponse = ScanResponse(
card = cardDto,
productType = ProductType.Wallet2,
walletData = null,
secondTwinPublicKey = null,
derivedKeys = emptyMap(),
primaryCard = null,
)
override val derivationTaskResponse = DerivationTaskResponse(
entries = mapOf(
ByteArrayKey(
byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5),
)
to
ExtendedPublicKeysMap(
mapOf(
DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch
publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121),
chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge
publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124),
chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
),
),
),
)
override val extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(2, -93, -36, -105, 121, -52, -30, -43, -67, -7, -31, -26, -35, 25, 99, 25, -20, 118, -20, -125, -89, 12, -101, -86, 74, -91, 23, -24, 93, -86, 20, -53, -8),
chainCode = byteArrayOf(32, 60, 63, -96, 97, 58, 121, 108, 75, 59, 63, -113, 60, -49, 47, 33, 15, -65, -69, 45, -7, -26, 65, -5, -91, -55, -42, -102, -127, -104, -111, 96),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
)
override val successResponse = SuccessResponse(cardId = "AF99009000000000")
override val createProductWalletTaskResponse = CreateProductWalletTaskResponse(
card = cardDto,
derivedKeys = mapOf(
ByteArrayKey(
byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5),
)
to
ExtendedPublicKeysMap(
mapOf(
DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
),
),
),
primaryCard = primaryCard,
)
override val importWalletResponse: CreateProductWalletTaskResponse
get() = TODO("Not yet implemented")
override val createFirstTwinResponse: CreateWalletResponse
get() = error("Available only for Twin")
override val createSecondTwinResponse: CreateWalletResponse
get() = error("Available only for Twin")
override val finalizeTwinResponse: ScanResponse
get() = error("Available only for Twin")
}

View file

@ -0,0 +1,307 @@
package com.tangem.tap.domain.sdk.mocks.content
import com.tangem.common.SuccessResponse
import com.tangem.common.card.*
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ProductType
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.operations.attestation.Attestation
import com.tangem.operations.backup.PrimaryCard
import com.tangem.operations.derivation.DerivationTaskResponse
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.sdk.api.CreateProductWalletTaskResponse
import com.tangem.tap.domain.sdk.mocks.MockContent
import java.util.Date
object FootballDarkGreenMockContent : MockContent {
private val primaryCard = PrimaryCard(
cardId = "AF99008900000000",
batchId = "AF990089",
cardPublicKey = byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5),
linkingKey = byteArrayOf( //
2, 121, 98, 127, -70, 14, 5, -23, -76, 115, -30, -26, 111, 17, 110, 34, -100, -121,
-57, -123, 74, 3, -91, 56, -20, 56, 50, -40, -101, 96, 82, 70, -91,
),
existingWalletsCount = 5, isHDWalletAllowed = true,
issuer = Card.Issuer(
name = "TANGEM SDK",
publicKey = byteArrayOf(2, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34),
),
manufacturer = Card.Manufacturer(
name = "TANGEM",
manufactureDate = Date(1743759687),
signature = byteArrayOf(),
),
walletCurves = listOf(
EllipticCurve.Secp256k1,
EllipticCurve.Ed25519,
EllipticCurve.Bls12381G2Aug,
EllipticCurve.Secp256r1,
EllipticCurve.Ed25519Slip0010,
EllipticCurve.Bls12381G2,
EllipticCurve.Bls12381G2Pop,
EllipticCurve.Bip0340,
),
firmwareVersion = FirmwareVersion(
major = 6,
minor = 33,
patch = 0,
type = FirmwareVersion.FirmwareType.Release,
),
isKeysImportAllowed = false,
certificate = null,
)
override val cardDto = CardDTO(
cardId = "AF99008900000000",
batchId = "AF990089",
cardPublicKey = byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5),
firmwareVersion = CardDTO.FirmwareVersion(
major = 6,
minor = 33,
patch = 0,
type = FirmwareVersion.FirmwareType.Release,
),
manufacturer = CardDTO.Manufacturer(
name = "TANGEM",
manufactureDate = Date(1698094800000),
signature = byteArrayOf(51, -12, 14, -56, 7, -39, 5, 63, 59, 24, 102, 99, -126, 124, -127, -108, -118, 71, -19, -71, 4, -47, -121, -46, 49, -51, -31, 100, -56, -15, 96, -37, 25, 82, 94, -88, 48, 98, -105, -97, -40, 41, 27, 116, 65, 26, 78, -85, 66, -94, -92, 15, 50, -2, 7, -69, 41, 56, -75, 59, 86, 68, -38, -3),
),
issuer = CardDTO.Issuer(
name = "TANGEM SDK",
publicKey = byteArrayOf(2, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34),
),
settings = CardDTO.Settings(
securityDelay = 15000,
maxWalletsCount = 20,
isSettingAccessCodeAllowed = true,
isSettingPasscodeAllowed = true,
isResettingUserCodesAllowed = false,
isLinkedTerminalEnabled = true,
isBackupAllowed = true,
supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None),
isFilesAllowed = true,
isHDWalletAllowed = true,
isKeysImportAllowed = true,
),
userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = true),
linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None,
isAccessCodeSet = true,
isPasscodeSet = false,
supportedCurves = listOf(
EllipticCurve.Secp256k1,
EllipticCurve.Ed25519,
EllipticCurve.Bls12381G2Aug,
EllipticCurve.Secp256r1,
EllipticCurve.Ed25519Slip0010,
EllipticCurve.Bls12381G2,
EllipticCurve.Bls12381G2Pop,
EllipticCurve.Bip0340,
),
wallets = listOf(
CardDTO.Wallet(
publicKey = byteArrayOf(3, 17, 59, -102, -56, 66, 10, 36, 97, -106, -92, -117, -105, -88, -2, -3, -95, -75, -54, -2, 57, 27, -90, -19, 82, 103, -12, -52, -126, -105, -120, -55, -2),
chainCode = byteArrayOf(-34, -28, -28, -12, 80, -109, -90, -31, -74, 36, -78, -21, 39, -125, -39, -91, 63, 40, -26, 3, 66, 27, -62, -55, -97, 52, -65, -11, 26, -80, 33, -45),
curve = EllipticCurve.Secp256k1,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 0,
hasBackup = true,
derivedKeys = mapOf(
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(2, 55, -114, -94, -73, -61, -50, 51, -115, 55, -79, 63, -96, -44, -64, -24, -36, -122, -123, -38, 81, -15, -127, -97, -42, 72, -85, -62, -98, 46, 119, 16, -55),
chainCode = byteArrayOf(31, 17, 71, -28, -29, 17, 72, -29, -98, 112, 31, -8, 72, -75, 4, -11, 60, -100, 9, 35, 58, -42, -38, 96, -71, -68, 24, -119, -43, -18, -122, 72),
),
DerivationPath("m/84'/0'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(2, -26, 103, 96, 112, 127, -125, 0, 7, 53, 30, -12, -82, 45, 14, 107, -9, 126, 75, 104, -67, -49, 35, -12, -82, -90, 101, -101, 125, -76, 88, -54, 99),
chainCode = byteArrayOf(-42, 36, 97, 65, -64, -113, 76, -91, -9, 11, 89, 123, -9, -3, 21, 103, -113, -60, 48, -31, -34, 108, 111, -38, -110, -80, 109, 17, -29, 2, 45, -71),
),
DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(2, -96, -3, -83, 101, 63, -25, -125, -4, -65, -42, -56, 24, -52, 118, -11, -104, -105, 40, -59, 20, -109, -97, 29, -95, -6, -80, 2, 67, 103, -80, -22, -94),
chainCode = byteArrayOf(-125, 27, -91, 38, -66, 109, -92, 16, -37, 93, 107, -29, -128, -1, 115, -64, 108, -63, 17, 27, 58, 78, -2, 39, 88, -39, 44, 89, 32, -38, -16, -38),
),
),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(-109, 42, -80, -115, -12, 44, 48, -118, -89, 10, 21, 59, 98, -110, -86, -14, 123, 105, -30, -49, -40, -4, -7, 32, 60, 67, -88, -52, -96, -10, -14, 123),
chainCode = byteArrayOf(-101, -10, -92, 72, 19, 121, -38, 105, -55, -82, -68, 13, -6, 17, 66, -10, -75, 58, 119, -127, 74, 68, 82, 25, 45, -110, 3, 3, 108, -97, -51, -127),
),
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(-109, 42, -80, -115, -12, 44, 48, -118, -89, 10, 21, 59, 98, -110, -86, -14, 123, 105, -30, -49, -40, -4, -7, 32, 60, 67, -88, -52, -96, -10, -14, 123),
chainCode = byteArrayOf(-101, -10, -92, 72, 19, 121, -38, 105, -55, -82, -68, 13, -6, 17, 66, -10, -75, 58, 119, -127, 74, 68, 82, 25, 45, -110, 3, 3, 108, -97, -51, -127),
curve = EllipticCurve.Ed25519,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 1,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(-64, 18, -128, 121, -89, -37, -99, 44, -125, -72, -111, -79, 7, 85, 40, 67, -39, 117, 123, 11, 105, -6, -5, -79, 19, -10, -29, 20, -14, -40, 5, 90),
chainCode = byteArrayOf(33, -97, 53, -112, 61, 112, -24, 74, -87, -85, -124, -4, 103, -94, -97, 76, -41, -27, 118, 33, 55, 121, -17, -52, 60, 122, 27, 25, 29, -76, 78, 11),
),
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(-118, -77, -109, 8, -119, 101, -91, 46, -44, 15, 52, -64, -92, 5, -102, 126, 52, 121, 63, -76, -54, 80, -82, -5, 31, -35, 105, -119, -96, -54, 38, 2, -6, 109, 117, -26, -47, -20, 108, 106, -69, 72, -37, -117, -30, -9, 104, -123),
chainCode = null,
curve = EllipticCurve.Bls12381G2Aug,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 1,
remainingSignatures = null,
index = 2,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = null,
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(17, -78, 53, 101, 96, -110, -10, -48, -84, 88, -64, 58, -26, -13, -2, -6, -25, 23, -79, -45, 58, 77, 52, -75, -123, 121, 32, 92, 84, -74, -111, -8),
chainCode = byteArrayOf(-110, 73, -124, -65, -1, -70, 64, -89, 95, -74, 15, 46, -110, 87, 15, -61, 120, -51, 111, 111, -45, 37, 123, -114, 65, -116, 21, 39, -77, 86, -3, -26),
curve = EllipticCurve.Bip0340,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 3,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(115, 14, 11, 0, -93, 81, -103, -95, -75, -84, 18, -120, -31, 76, -83, -81, 91, 25, -75, 36, -99, -53, -25, -15, -1, -57, 14, -39, 98, -116, -63, -123),
chainCode = byteArrayOf(23, 5, 38, -48, 67, -42, -31, -21, 89, 11, 22, -28, 44, -19, -115, -78, 123, -27, 57, 57, -24, -86, 55, 15, 104, 114, -36, 80, 81, -108, -41, 112),
),
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(-52, -92, 64, 108, -8, -100, 87, -86, -60, 18, -18, -81, 114, -84, 76, -24, 84, 52, -34, 79, -30, -66, -112, -55, -35, -119, 127, -109, 35, 18, -29, -25),
chainCode = byteArrayOf(94, -33, -94, -63, 40, -20, -26, 71, 111, 9, 87, -36, -42, -33, 40, -53, 85, 31, -36, -29, 65, -121, 2, -23, -119, -51, 114, -17, -39, -74, 23, -97),
curve = EllipticCurve.Ed25519Slip0010,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 4,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(-43, 1, -81, -47, -8, -103, -66, 42, 37, -7, 65, 54, 57, -24, 127, -89, 69, -112, 42, -46, -128, 36, -117, -28, 30, -48, 37, 52, 93, -47, 92, -47),
chainCode = byteArrayOf(4, -97, 81, -37, 76, -67, -87, -4, -82, -36, -45, -28, -117, -59, -62, 93, -73, 50, 65, -91, -83, 25, -95, 89, -64, -40, 113, 28, 59, 113, -99, 89),
),
isImported = false,
),
),
attestation = Attestation(
cardKeyAttestation = Attestation.Status.Verified,
walletKeysAttestation = Attestation.Status.Skipped,
firmwareAttestation = Attestation.Status.Skipped,
cardUniquenessAttestation = Attestation.Status.Skipped,
),
backupStatus = CardDTO.BackupStatus.Active(2),
)
override val scanResponse = ScanResponse(
card = cardDto,
productType = ProductType.Wallet2,
walletData = null,
secondTwinPublicKey = null,
derivedKeys = emptyMap(),
primaryCard = null,
)
override val derivationTaskResponse = DerivationTaskResponse(
entries = mapOf(
ByteArrayKey(
byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5),
)
to
ExtendedPublicKeysMap(
mapOf(
DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch
publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121),
chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge
publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124),
chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
),
),
),
)
override val extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(2, -93, -36, -105, 121, -52, -30, -43, -67, -7, -31, -26, -35, 25, 99, 25, -20, 118, -20, -125, -89, 12, -101, -86, 74, -91, 23, -24, 93, -86, 20, -53, -8),
chainCode = byteArrayOf(32, 60, 63, -96, 97, 58, 121, 108, 75, 59, 63, -113, 60, -49, 47, 33, 15, -65, -69, 45, -7, -26, 65, -5, -91, -55, -42, -102, -127, -104, -111, 96),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
)
override val successResponse = SuccessResponse(cardId = "AF99008900000000")
override val createProductWalletTaskResponse = CreateProductWalletTaskResponse(
card = cardDto,
derivedKeys = mapOf(
ByteArrayKey(
byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5),
)
to
ExtendedPublicKeysMap(
mapOf(
DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
),
),
),
primaryCard = primaryCard,
)
override val importWalletResponse: CreateProductWalletTaskResponse
get() = TODO("Not yet implemented")
override val createFirstTwinResponse: CreateWalletResponse
get() = error("Available only for Twin")
override val createSecondTwinResponse: CreateWalletResponse
get() = error("Available only for Twin")
override val finalizeTwinResponse: ScanResponse
get() = error("Available only for Twin")
}

View file

@ -0,0 +1,307 @@
package com.tangem.tap.domain.sdk.mocks.content
import com.tangem.common.SuccessResponse
import com.tangem.common.card.*
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ProductType
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.operations.attestation.Attestation
import com.tangem.operations.backup.PrimaryCard
import com.tangem.operations.derivation.DerivationTaskResponse
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.sdk.api.CreateProductWalletTaskResponse
import com.tangem.tap.domain.sdk.mocks.MockContent
import java.util.Date
object FrenchBlueMockContent : MockContent {
private val primaryCard = PrimaryCard(
cardId = "AF99008400000000",
batchId = "AF990084",
cardPublicKey = byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5),
linkingKey = byteArrayOf( //
2, 121, 98, 127, -70, 14, 5, -23, -76, 115, -30, -26, 111, 17, 110, 34, -100, -121,
-57, -123, 74, 3, -91, 56, -20, 56, 50, -40, -101, 96, 82, 70, -91,
),
existingWalletsCount = 5, isHDWalletAllowed = true,
issuer = Card.Issuer(
name = "TANGEM SDK",
publicKey = byteArrayOf(2, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34),
),
manufacturer = Card.Manufacturer(
name = "TANGEM",
manufactureDate = Date(1743759687),
signature = byteArrayOf(),
),
walletCurves = listOf(
EllipticCurve.Secp256k1,
EllipticCurve.Ed25519,
EllipticCurve.Bls12381G2Aug,
EllipticCurve.Secp256r1,
EllipticCurve.Ed25519Slip0010,
EllipticCurve.Bls12381G2,
EllipticCurve.Bls12381G2Pop,
EllipticCurve.Bip0340,
),
firmwareVersion = FirmwareVersion(
major = 6,
minor = 33,
patch = 0,
type = FirmwareVersion.FirmwareType.Release,
),
isKeysImportAllowed = false,
certificate = null,
)
override val cardDto = CardDTO(
cardId = "AF99008400000000",
batchId = "AF990084",
cardPublicKey = byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5),
firmwareVersion = CardDTO.FirmwareVersion(
major = 6,
minor = 33,
patch = 0,
type = FirmwareVersion.FirmwareType.Release,
),
manufacturer = CardDTO.Manufacturer(
name = "TANGEM",
manufactureDate = Date(1698094800000),
signature = byteArrayOf(51, -12, 14, -56, 7, -39, 5, 63, 59, 24, 102, 99, -126, 124, -127, -108, -118, 71, -19, -71, 4, -47, -121, -46, 49, -51, -31, 100, -56, -15, 96, -37, 25, 82, 94, -88, 48, 98, -105, -97, -40, 41, 27, 116, 65, 26, 78, -85, 66, -94, -92, 15, 50, -2, 7, -69, 41, 56, -75, 59, 86, 68, -38, -3),
),
issuer = CardDTO.Issuer(
name = "TANGEM SDK",
publicKey = byteArrayOf(2, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34),
),
settings = CardDTO.Settings(
securityDelay = 15000,
maxWalletsCount = 20,
isSettingAccessCodeAllowed = true,
isSettingPasscodeAllowed = true,
isResettingUserCodesAllowed = false,
isLinkedTerminalEnabled = true,
isBackupAllowed = true,
supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None),
isFilesAllowed = true,
isHDWalletAllowed = true,
isKeysImportAllowed = true,
),
userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = true),
linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None,
isAccessCodeSet = true,
isPasscodeSet = false,
supportedCurves = listOf(
EllipticCurve.Secp256k1,
EllipticCurve.Ed25519,
EllipticCurve.Bls12381G2Aug,
EllipticCurve.Secp256r1,
EllipticCurve.Ed25519Slip0010,
EllipticCurve.Bls12381G2,
EllipticCurve.Bls12381G2Pop,
EllipticCurve.Bip0340,
),
wallets = listOf(
CardDTO.Wallet(
publicKey = byteArrayOf(3, 17, 59, -102, -56, 66, 10, 36, 97, -106, -92, -117, -105, -88, -2, -3, -95, -75, -54, -2, 57, 27, -90, -19, 82, 103, -12, -52, -126, -105, -120, -55, -2),
chainCode = byteArrayOf(-34, -28, -28, -12, 80, -109, -90, -31, -74, 36, -78, -21, 39, -125, -39, -91, 63, 40, -26, 3, 66, 27, -62, -55, -97, 52, -65, -11, 26, -80, 33, -45),
curve = EllipticCurve.Secp256k1,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 0,
hasBackup = true,
derivedKeys = mapOf(
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(2, 55, -114, -94, -73, -61, -50, 51, -115, 55, -79, 63, -96, -44, -64, -24, -36, -122, -123, -38, 81, -15, -127, -97, -42, 72, -85, -62, -98, 46, 119, 16, -55),
chainCode = byteArrayOf(31, 17, 71, -28, -29, 17, 72, -29, -98, 112, 31, -8, 72, -75, 4, -11, 60, -100, 9, 35, 58, -42, -38, 96, -71, -68, 24, -119, -43, -18, -122, 72),
),
DerivationPath("m/84'/0'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(2, -26, 103, 96, 112, 127, -125, 0, 7, 53, 30, -12, -82, 45, 14, 107, -9, 126, 75, 104, -67, -49, 35, -12, -82, -90, 101, -101, 125, -76, 88, -54, 99),
chainCode = byteArrayOf(-42, 36, 97, 65, -64, -113, 76, -91, -9, 11, 89, 123, -9, -3, 21, 103, -113, -60, 48, -31, -34, 108, 111, -38, -110, -80, 109, 17, -29, 2, 45, -71),
),
DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(2, -96, -3, -83, 101, 63, -25, -125, -4, -65, -42, -56, 24, -52, 118, -11, -104, -105, 40, -59, 20, -109, -97, 29, -95, -6, -80, 2, 67, 103, -80, -22, -94),
chainCode = byteArrayOf(-125, 27, -91, 38, -66, 109, -92, 16, -37, 93, 107, -29, -128, -1, 115, -64, 108, -63, 17, 27, 58, 78, -2, 39, 88, -39, 44, 89, 32, -38, -16, -38),
),
),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(-109, 42, -80, -115, -12, 44, 48, -118, -89, 10, 21, 59, 98, -110, -86, -14, 123, 105, -30, -49, -40, -4, -7, 32, 60, 67, -88, -52, -96, -10, -14, 123),
chainCode = byteArrayOf(-101, -10, -92, 72, 19, 121, -38, 105, -55, -82, -68, 13, -6, 17, 66, -10, -75, 58, 119, -127, 74, 68, 82, 25, 45, -110, 3, 3, 108, -97, -51, -127),
),
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(-109, 42, -80, -115, -12, 44, 48, -118, -89, 10, 21, 59, 98, -110, -86, -14, 123, 105, -30, -49, -40, -4, -7, 32, 60, 67, -88, -52, -96, -10, -14, 123),
chainCode = byteArrayOf(-101, -10, -92, 72, 19, 121, -38, 105, -55, -82, -68, 13, -6, 17, 66, -10, -75, 58, 119, -127, 74, 68, 82, 25, 45, -110, 3, 3, 108, -97, -51, -127),
curve = EllipticCurve.Ed25519,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 1,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(-64, 18, -128, 121, -89, -37, -99, 44, -125, -72, -111, -79, 7, 85, 40, 67, -39, 117, 123, 11, 105, -6, -5, -79, 19, -10, -29, 20, -14, -40, 5, 90),
chainCode = byteArrayOf(33, -97, 53, -112, 61, 112, -24, 74, -87, -85, -124, -4, 103, -94, -97, 76, -41, -27, 118, 33, 55, 121, -17, -52, 60, 122, 27, 25, 29, -76, 78, 11),
),
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(-118, -77, -109, 8, -119, 101, -91, 46, -44, 15, 52, -64, -92, 5, -102, 126, 52, 121, 63, -76, -54, 80, -82, -5, 31, -35, 105, -119, -96, -54, 38, 2, -6, 109, 117, -26, -47, -20, 108, 106, -69, 72, -37, -117, -30, -9, 104, -123),
chainCode = null,
curve = EllipticCurve.Bls12381G2Aug,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 1,
remainingSignatures = null,
index = 2,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = null,
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(17, -78, 53, 101, 96, -110, -10, -48, -84, 88, -64, 58, -26, -13, -2, -6, -25, 23, -79, -45, 58, 77, 52, -75, -123, 121, 32, 92, 84, -74, -111, -8),
chainCode = byteArrayOf(-110, 73, -124, -65, -1, -70, 64, -89, 95, -74, 15, 46, -110, 87, 15, -61, 120, -51, 111, 111, -45, 37, 123, -114, 65, -116, 21, 39, -77, 86, -3, -26),
curve = EllipticCurve.Bip0340,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 3,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(115, 14, 11, 0, -93, 81, -103, -95, -75, -84, 18, -120, -31, 76, -83, -81, 91, 25, -75, 36, -99, -53, -25, -15, -1, -57, 14, -39, 98, -116, -63, -123),
chainCode = byteArrayOf(23, 5, 38, -48, 67, -42, -31, -21, 89, 11, 22, -28, 44, -19, -115, -78, 123, -27, 57, 57, -24, -86, 55, 15, 104, 114, -36, 80, 81, -108, -41, 112),
),
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(-52, -92, 64, 108, -8, -100, 87, -86, -60, 18, -18, -81, 114, -84, 76, -24, 84, 52, -34, 79, -30, -66, -112, -55, -35, -119, 127, -109, 35, 18, -29, -25),
chainCode = byteArrayOf(94, -33, -94, -63, 40, -20, -26, 71, 111, 9, 87, -36, -42, -33, 40, -53, 85, 31, -36, -29, 65, -121, 2, -23, -119, -51, 114, -17, -39, -74, 23, -97),
curve = EllipticCurve.Ed25519Slip0010,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 4,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(-43, 1, -81, -47, -8, -103, -66, 42, 37, -7, 65, 54, 57, -24, 127, -89, 69, -112, 42, -46, -128, 36, -117, -28, 30, -48, 37, 52, 93, -47, 92, -47),
chainCode = byteArrayOf(4, -97, 81, -37, 76, -67, -87, -4, -82, -36, -45, -28, -117, -59, -62, 93, -73, 50, 65, -91, -83, 25, -95, 89, -64, -40, 113, 28, 59, 113, -99, 89),
),
isImported = false,
),
),
attestation = Attestation(
cardKeyAttestation = Attestation.Status.Verified,
walletKeysAttestation = Attestation.Status.Skipped,
firmwareAttestation = Attestation.Status.Skipped,
cardUniquenessAttestation = Attestation.Status.Skipped,
),
backupStatus = CardDTO.BackupStatus.Active(2),
)
override val scanResponse = ScanResponse(
card = cardDto,
productType = ProductType.Wallet2,
walletData = null,
secondTwinPublicKey = null,
derivedKeys = emptyMap(),
primaryCard = null,
)
override val derivationTaskResponse = DerivationTaskResponse(
entries = mapOf(
ByteArrayKey(
byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5),
)
to
ExtendedPublicKeysMap(
mapOf(
DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch
publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121),
chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge
publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124),
chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
),
),
),
)
override val extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(2, -93, -36, -105, 121, -52, -30, -43, -67, -7, -31, -26, -35, 25, 99, 25, -20, 118, -20, -125, -89, 12, -101, -86, 74, -91, 23, -24, 93, -86, 20, -53, -8),
chainCode = byteArrayOf(32, 60, 63, -96, 97, 58, 121, 108, 75, 59, 63, -113, 60, -49, 47, 33, 15, -65, -69, 45, -7, -26, 65, -5, -91, -55, -42, -102, -127, -104, -111, 96),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
)
override val successResponse = SuccessResponse(cardId = "AF99008400000000")
override val createProductWalletTaskResponse = CreateProductWalletTaskResponse(
card = cardDto,
derivedKeys = mapOf(
ByteArrayKey(
byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5),
)
to
ExtendedPublicKeysMap(
mapOf(
DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
),
),
),
primaryCard = primaryCard,
)
override val importWalletResponse: CreateProductWalletTaskResponse
get() = TODO("Not yet implemented")
override val createFirstTwinResponse: CreateWalletResponse
get() = error("Available only for Twin")
override val createSecondTwinResponse: CreateWalletResponse
get() = error("Available only for Twin")
override val finalizeTwinResponse: ScanResponse
get() = error("Available only for Twin")
}

View file

@ -0,0 +1,307 @@
package com.tangem.tap.domain.sdk.mocks.content
import com.tangem.common.SuccessResponse
import com.tangem.common.card.*
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ProductType
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.operations.attestation.Attestation
import com.tangem.operations.backup.PrimaryCard
import com.tangem.operations.derivation.DerivationTaskResponse
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.sdk.api.CreateProductWalletTaskResponse
import com.tangem.tap.domain.sdk.mocks.MockContent
import java.util.Date
object FrenchWhiteMockContent : MockContent {
private val primaryCard = PrimaryCard(
cardId = "AF99008500000000",
batchId = "AF990085",
cardPublicKey = byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5),
linkingKey = byteArrayOf( //
2, 121, 98, 127, -70, 14, 5, -23, -76, 115, -30, -26, 111, 17, 110, 34, -100, -121,
-57, -123, 74, 3, -91, 56, -20, 56, 50, -40, -101, 96, 82, 70, -91,
),
existingWalletsCount = 5, isHDWalletAllowed = true,
issuer = Card.Issuer(
name = "TANGEM SDK",
publicKey = byteArrayOf(2, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34),
),
manufacturer = Card.Manufacturer(
name = "TANGEM",
manufactureDate = Date(1743759687),
signature = byteArrayOf(),
),
walletCurves = listOf(
EllipticCurve.Secp256k1,
EllipticCurve.Ed25519,
EllipticCurve.Bls12381G2Aug,
EllipticCurve.Secp256r1,
EllipticCurve.Ed25519Slip0010,
EllipticCurve.Bls12381G2,
EllipticCurve.Bls12381G2Pop,
EllipticCurve.Bip0340,
),
firmwareVersion = FirmwareVersion(
major = 6,
minor = 33,
patch = 0,
type = FirmwareVersion.FirmwareType.Release,
),
isKeysImportAllowed = false,
certificate = null,
)
override val cardDto = CardDTO(
cardId = "AF99008500000000",
batchId = "AF990085",
cardPublicKey = byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5),
firmwareVersion = CardDTO.FirmwareVersion(
major = 6,
minor = 33,
patch = 0,
type = FirmwareVersion.FirmwareType.Release,
),
manufacturer = CardDTO.Manufacturer(
name = "TANGEM",
manufactureDate = Date(1698094800000),
signature = byteArrayOf(51, -12, 14, -56, 7, -39, 5, 63, 59, 24, 102, 99, -126, 124, -127, -108, -118, 71, -19, -71, 4, -47, -121, -46, 49, -51, -31, 100, -56, -15, 96, -37, 25, 82, 94, -88, 48, 98, -105, -97, -40, 41, 27, 116, 65, 26, 78, -85, 66, -94, -92, 15, 50, -2, 7, -69, 41, 56, -75, 59, 86, 68, -38, -3),
),
issuer = CardDTO.Issuer(
name = "TANGEM SDK",
publicKey = byteArrayOf(2, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34),
),
settings = CardDTO.Settings(
securityDelay = 15000,
maxWalletsCount = 20,
isSettingAccessCodeAllowed = true,
isSettingPasscodeAllowed = true,
isResettingUserCodesAllowed = false,
isLinkedTerminalEnabled = true,
isBackupAllowed = true,
supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None),
isFilesAllowed = true,
isHDWalletAllowed = true,
isKeysImportAllowed = true,
),
userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = true),
linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None,
isAccessCodeSet = true,
isPasscodeSet = false,
supportedCurves = listOf(
EllipticCurve.Secp256k1,
EllipticCurve.Ed25519,
EllipticCurve.Bls12381G2Aug,
EllipticCurve.Secp256r1,
EllipticCurve.Ed25519Slip0010,
EllipticCurve.Bls12381G2,
EllipticCurve.Bls12381G2Pop,
EllipticCurve.Bip0340,
),
wallets = listOf(
CardDTO.Wallet(
publicKey = byteArrayOf(3, 17, 59, -102, -56, 66, 10, 36, 97, -106, -92, -117, -105, -88, -2, -3, -95, -75, -54, -2, 57, 27, -90, -19, 82, 103, -12, -52, -126, -105, -120, -55, -2),
chainCode = byteArrayOf(-34, -28, -28, -12, 80, -109, -90, -31, -74, 36, -78, -21, 39, -125, -39, -91, 63, 40, -26, 3, 66, 27, -62, -55, -97, 52, -65, -11, 26, -80, 33, -45),
curve = EllipticCurve.Secp256k1,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 0,
hasBackup = true,
derivedKeys = mapOf(
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(2, 55, -114, -94, -73, -61, -50, 51, -115, 55, -79, 63, -96, -44, -64, -24, -36, -122, -123, -38, 81, -15, -127, -97, -42, 72, -85, -62, -98, 46, 119, 16, -55),
chainCode = byteArrayOf(31, 17, 71, -28, -29, 17, 72, -29, -98, 112, 31, -8, 72, -75, 4, -11, 60, -100, 9, 35, 58, -42, -38, 96, -71, -68, 24, -119, -43, -18, -122, 72),
),
DerivationPath("m/84'/0'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(2, -26, 103, 96, 112, 127, -125, 0, 7, 53, 30, -12, -82, 45, 14, 107, -9, 126, 75, 104, -67, -49, 35, -12, -82, -90, 101, -101, 125, -76, 88, -54, 99),
chainCode = byteArrayOf(-42, 36, 97, 65, -64, -113, 76, -91, -9, 11, 89, 123, -9, -3, 21, 103, -113, -60, 48, -31, -34, 108, 111, -38, -110, -80, 109, 17, -29, 2, 45, -71),
),
DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(2, -96, -3, -83, 101, 63, -25, -125, -4, -65, -42, -56, 24, -52, 118, -11, -104, -105, 40, -59, 20, -109, -97, 29, -95, -6, -80, 2, 67, 103, -80, -22, -94),
chainCode = byteArrayOf(-125, 27, -91, 38, -66, 109, -92, 16, -37, 93, 107, -29, -128, -1, 115, -64, 108, -63, 17, 27, 58, 78, -2, 39, 88, -39, 44, 89, 32, -38, -16, -38),
),
),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(-109, 42, -80, -115, -12, 44, 48, -118, -89, 10, 21, 59, 98, -110, -86, -14, 123, 105, -30, -49, -40, -4, -7, 32, 60, 67, -88, -52, -96, -10, -14, 123),
chainCode = byteArrayOf(-101, -10, -92, 72, 19, 121, -38, 105, -55, -82, -68, 13, -6, 17, 66, -10, -75, 58, 119, -127, 74, 68, 82, 25, 45, -110, 3, 3, 108, -97, -51, -127),
),
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(-109, 42, -80, -115, -12, 44, 48, -118, -89, 10, 21, 59, 98, -110, -86, -14, 123, 105, -30, -49, -40, -4, -7, 32, 60, 67, -88, -52, -96, -10, -14, 123),
chainCode = byteArrayOf(-101, -10, -92, 72, 19, 121, -38, 105, -55, -82, -68, 13, -6, 17, 66, -10, -75, 58, 119, -127, 74, 68, 82, 25, 45, -110, 3, 3, 108, -97, -51, -127),
curve = EllipticCurve.Ed25519,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 1,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(-64, 18, -128, 121, -89, -37, -99, 44, -125, -72, -111, -79, 7, 85, 40, 67, -39, 117, 123, 11, 105, -6, -5, -79, 19, -10, -29, 20, -14, -40, 5, 90),
chainCode = byteArrayOf(33, -97, 53, -112, 61, 112, -24, 74, -87, -85, -124, -4, 103, -94, -97, 76, -41, -27, 118, 33, 55, 121, -17, -52, 60, 122, 27, 25, 29, -76, 78, 11),
),
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(-118, -77, -109, 8, -119, 101, -91, 46, -44, 15, 52, -64, -92, 5, -102, 126, 52, 121, 63, -76, -54, 80, -82, -5, 31, -35, 105, -119, -96, -54, 38, 2, -6, 109, 117, -26, -47, -20, 108, 106, -69, 72, -37, -117, -30, -9, 104, -123),
chainCode = null,
curve = EllipticCurve.Bls12381G2Aug,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 1,
remainingSignatures = null,
index = 2,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = null,
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(17, -78, 53, 101, 96, -110, -10, -48, -84, 88, -64, 58, -26, -13, -2, -6, -25, 23, -79, -45, 58, 77, 52, -75, -123, 121, 32, 92, 84, -74, -111, -8),
chainCode = byteArrayOf(-110, 73, -124, -65, -1, -70, 64, -89, 95, -74, 15, 46, -110, 87, 15, -61, 120, -51, 111, 111, -45, 37, 123, -114, 65, -116, 21, 39, -77, 86, -3, -26),
curve = EllipticCurve.Bip0340,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 3,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(115, 14, 11, 0, -93, 81, -103, -95, -75, -84, 18, -120, -31, 76, -83, -81, 91, 25, -75, 36, -99, -53, -25, -15, -1, -57, 14, -39, 98, -116, -63, -123),
chainCode = byteArrayOf(23, 5, 38, -48, 67, -42, -31, -21, 89, 11, 22, -28, 44, -19, -115, -78, 123, -27, 57, 57, -24, -86, 55, 15, 104, 114, -36, 80, 81, -108, -41, 112),
),
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(-52, -92, 64, 108, -8, -100, 87, -86, -60, 18, -18, -81, 114, -84, 76, -24, 84, 52, -34, 79, -30, -66, -112, -55, -35, -119, 127, -109, 35, 18, -29, -25),
chainCode = byteArrayOf(94, -33, -94, -63, 40, -20, -26, 71, 111, 9, 87, -36, -42, -33, 40, -53, 85, 31, -36, -29, 65, -121, 2, -23, -119, -51, 114, -17, -39, -74, 23, -97),
curve = EllipticCurve.Ed25519Slip0010,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 4,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(-43, 1, -81, -47, -8, -103, -66, 42, 37, -7, 65, 54, 57, -24, 127, -89, 69, -112, 42, -46, -128, 36, -117, -28, 30, -48, 37, 52, 93, -47, 92, -47),
chainCode = byteArrayOf(4, -97, 81, -37, 76, -67, -87, -4, -82, -36, -45, -28, -117, -59, -62, 93, -73, 50, 65, -91, -83, 25, -95, 89, -64, -40, 113, 28, 59, 113, -99, 89),
),
isImported = false,
),
),
attestation = Attestation(
cardKeyAttestation = Attestation.Status.Verified,
walletKeysAttestation = Attestation.Status.Skipped,
firmwareAttestation = Attestation.Status.Skipped,
cardUniquenessAttestation = Attestation.Status.Skipped,
),
backupStatus = CardDTO.BackupStatus.Active(1),
)
override val scanResponse = ScanResponse(
card = cardDto,
productType = ProductType.Wallet2,
walletData = null,
secondTwinPublicKey = null,
derivedKeys = emptyMap(),
primaryCard = null,
)
override val derivationTaskResponse = DerivationTaskResponse(
entries = mapOf(
ByteArrayKey(
byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5),
)
to
ExtendedPublicKeysMap(
mapOf(
DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch
publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121),
chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge
publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124),
chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
),
),
),
)
override val extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(2, -93, -36, -105, 121, -52, -30, -43, -67, -7, -31, -26, -35, 25, 99, 25, -20, 118, -20, -125, -89, 12, -101, -86, 74, -91, 23, -24, 93, -86, 20, -53, -8),
chainCode = byteArrayOf(32, 60, 63, -96, 97, 58, 121, 108, 75, 59, 63, -113, 60, -49, 47, 33, 15, -65, -69, 45, -7, -26, 65, -5, -91, -55, -42, -102, -127, -104, -111, 96),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
)
override val successResponse = SuccessResponse(cardId = "AF99008500000000")
override val createProductWalletTaskResponse = CreateProductWalletTaskResponse(
card = cardDto,
derivedKeys = mapOf(
ByteArrayKey(
byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5),
)
to
ExtendedPublicKeysMap(
mapOf(
DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
),
),
),
primaryCard = primaryCard,
)
override val importWalletResponse: CreateProductWalletTaskResponse
get() = TODO("Not yet implemented")
override val createFirstTwinResponse: CreateWalletResponse
get() = error("Available only for Twin")
override val createSecondTwinResponse: CreateWalletResponse
get() = error("Available only for Twin")
override val finalizeTwinResponse: ScanResponse
get() = error("Available only for Twin")
}

View file

@ -0,0 +1,307 @@
package com.tangem.tap.domain.sdk.mocks.content
import com.tangem.common.SuccessResponse
import com.tangem.common.card.*
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ProductType
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.operations.attestation.Attestation
import com.tangem.operations.backup.PrimaryCard
import com.tangem.operations.derivation.DerivationTaskResponse
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.sdk.api.CreateProductWalletTaskResponse
import com.tangem.tap.domain.sdk.mocks.MockContent
import java.util.Date
object MetaplanetDoubleMockContent : MockContent {
private val primaryCard = PrimaryCard(
cardId = "BB00004000000000",
batchId = "BB000040",
cardPublicKey = byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5),
linkingKey = byteArrayOf( //
2, 121, 98, 127, -70, 14, 5, -23, -76, 115, -30, -26, 111, 17, 110, 34, -100, -121,
-57, -123, 74, 3, -91, 56, -20, 56, 50, -40, -101, 96, 82, 70, -91,
),
existingWalletsCount = 5, isHDWalletAllowed = true,
issuer = Card.Issuer(
name = "TANGEM SDK",
publicKey = byteArrayOf(2, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34),
),
manufacturer = Card.Manufacturer(
name = "TANGEM",
manufactureDate = Date(1743759687),
signature = byteArrayOf(),
),
walletCurves = listOf(
EllipticCurve.Secp256k1,
EllipticCurve.Ed25519,
EllipticCurve.Bls12381G2Aug,
EllipticCurve.Secp256r1,
EllipticCurve.Ed25519Slip0010,
EllipticCurve.Bls12381G2,
EllipticCurve.Bls12381G2Pop,
EllipticCurve.Bip0340,
),
firmwareVersion = FirmwareVersion(
major = 6,
minor = 33,
patch = 0,
type = FirmwareVersion.FirmwareType.Release,
),
isKeysImportAllowed = false,
certificate = null,
)
override val cardDto = CardDTO(
cardId = "BB00004000000000",
batchId = "BB000040",
cardPublicKey = byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5),
firmwareVersion = CardDTO.FirmwareVersion(
major = 6,
minor = 33,
patch = 0,
type = FirmwareVersion.FirmwareType.Release,
),
manufacturer = CardDTO.Manufacturer(
name = "TANGEM",
manufactureDate = Date(1698094800000),
signature = byteArrayOf(51, -12, 14, -56, 7, -39, 5, 63, 59, 24, 102, 99, -126, 124, -127, -108, -118, 71, -19, -71, 4, -47, -121, -46, 49, -51, -31, 100, -56, -15, 96, -37, 25, 82, 94, -88, 48, 98, -105, -97, -40, 41, 27, 116, 65, 26, 78, -85, 66, -94, -92, 15, 50, -2, 7, -69, 41, 56, -75, 59, 86, 68, -38, -3),
),
issuer = CardDTO.Issuer(
name = "TANGEM SDK",
publicKey = byteArrayOf(2, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34),
),
settings = CardDTO.Settings(
securityDelay = 15000,
maxWalletsCount = 20,
isSettingAccessCodeAllowed = true,
isSettingPasscodeAllowed = true,
isResettingUserCodesAllowed = false,
isLinkedTerminalEnabled = true,
isBackupAllowed = true,
supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None),
isFilesAllowed = true,
isHDWalletAllowed = true,
isKeysImportAllowed = true,
),
userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = true),
linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None,
isAccessCodeSet = true,
isPasscodeSet = false,
supportedCurves = listOf(
EllipticCurve.Secp256k1,
EllipticCurve.Ed25519,
EllipticCurve.Bls12381G2Aug,
EllipticCurve.Secp256r1,
EllipticCurve.Ed25519Slip0010,
EllipticCurve.Bls12381G2,
EllipticCurve.Bls12381G2Pop,
EllipticCurve.Bip0340,
),
wallets = listOf(
CardDTO.Wallet(
publicKey = byteArrayOf(3, 17, 59, -102, -56, 66, 10, 36, 97, -106, -92, -117, -105, -88, -2, -3, -95, -75, -54, -2, 57, 27, -90, -19, 82, 103, -12, -52, -126, -105, -120, -55, -2),
chainCode = byteArrayOf(-34, -28, -28, -12, 80, -109, -90, -31, -74, 36, -78, -21, 39, -125, -39, -91, 63, 40, -26, 3, 66, 27, -62, -55, -97, 52, -65, -11, 26, -80, 33, -45),
curve = EllipticCurve.Secp256k1,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 0,
hasBackup = true,
derivedKeys = mapOf(
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(2, 55, -114, -94, -73, -61, -50, 51, -115, 55, -79, 63, -96, -44, -64, -24, -36, -122, -123, -38, 81, -15, -127, -97, -42, 72, -85, -62, -98, 46, 119, 16, -55),
chainCode = byteArrayOf(31, 17, 71, -28, -29, 17, 72, -29, -98, 112, 31, -8, 72, -75, 4, -11, 60, -100, 9, 35, 58, -42, -38, 96, -71, -68, 24, -119, -43, -18, -122, 72),
),
DerivationPath("m/84'/0'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(2, -26, 103, 96, 112, 127, -125, 0, 7, 53, 30, -12, -82, 45, 14, 107, -9, 126, 75, 104, -67, -49, 35, -12, -82, -90, 101, -101, 125, -76, 88, -54, 99),
chainCode = byteArrayOf(-42, 36, 97, 65, -64, -113, 76, -91, -9, 11, 89, 123, -9, -3, 21, 103, -113, -60, 48, -31, -34, 108, 111, -38, -110, -80, 109, 17, -29, 2, 45, -71),
),
DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(2, -96, -3, -83, 101, 63, -25, -125, -4, -65, -42, -56, 24, -52, 118, -11, -104, -105, 40, -59, 20, -109, -97, 29, -95, -6, -80, 2, 67, 103, -80, -22, -94),
chainCode = byteArrayOf(-125, 27, -91, 38, -66, 109, -92, 16, -37, 93, 107, -29, -128, -1, 115, -64, 108, -63, 17, 27, 58, 78, -2, 39, 88, -39, 44, 89, 32, -38, -16, -38),
),
),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(-109, 42, -80, -115, -12, 44, 48, -118, -89, 10, 21, 59, 98, -110, -86, -14, 123, 105, -30, -49, -40, -4, -7, 32, 60, 67, -88, -52, -96, -10, -14, 123),
chainCode = byteArrayOf(-101, -10, -92, 72, 19, 121, -38, 105, -55, -82, -68, 13, -6, 17, 66, -10, -75, 58, 119, -127, 74, 68, 82, 25, 45, -110, 3, 3, 108, -97, -51, -127),
),
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(-109, 42, -80, -115, -12, 44, 48, -118, -89, 10, 21, 59, 98, -110, -86, -14, 123, 105, -30, -49, -40, -4, -7, 32, 60, 67, -88, -52, -96, -10, -14, 123),
chainCode = byteArrayOf(-101, -10, -92, 72, 19, 121, -38, 105, -55, -82, -68, 13, -6, 17, 66, -10, -75, 58, 119, -127, 74, 68, 82, 25, 45, -110, 3, 3, 108, -97, -51, -127),
curve = EllipticCurve.Ed25519,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 1,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(-64, 18, -128, 121, -89, -37, -99, 44, -125, -72, -111, -79, 7, 85, 40, 67, -39, 117, 123, 11, 105, -6, -5, -79, 19, -10, -29, 20, -14, -40, 5, 90),
chainCode = byteArrayOf(33, -97, 53, -112, 61, 112, -24, 74, -87, -85, -124, -4, 103, -94, -97, 76, -41, -27, 118, 33, 55, 121, -17, -52, 60, 122, 27, 25, 29, -76, 78, 11),
),
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(-118, -77, -109, 8, -119, 101, -91, 46, -44, 15, 52, -64, -92, 5, -102, 126, 52, 121, 63, -76, -54, 80, -82, -5, 31, -35, 105, -119, -96, -54, 38, 2, -6, 109, 117, -26, -47, -20, 108, 106, -69, 72, -37, -117, -30, -9, 104, -123),
chainCode = null,
curve = EllipticCurve.Bls12381G2Aug,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 1,
remainingSignatures = null,
index = 2,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = null,
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(17, -78, 53, 101, 96, -110, -10, -48, -84, 88, -64, 58, -26, -13, -2, -6, -25, 23, -79, -45, 58, 77, 52, -75, -123, 121, 32, 92, 84, -74, -111, -8),
chainCode = byteArrayOf(-110, 73, -124, -65, -1, -70, 64, -89, 95, -74, 15, 46, -110, 87, 15, -61, 120, -51, 111, 111, -45, 37, 123, -114, 65, -116, 21, 39, -77, 86, -3, -26),
curve = EllipticCurve.Bip0340,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 3,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(115, 14, 11, 0, -93, 81, -103, -95, -75, -84, 18, -120, -31, 76, -83, -81, 91, 25, -75, 36, -99, -53, -25, -15, -1, -57, 14, -39, 98, -116, -63, -123),
chainCode = byteArrayOf(23, 5, 38, -48, 67, -42, -31, -21, 89, 11, 22, -28, 44, -19, -115, -78, 123, -27, 57, 57, -24, -86, 55, 15, 104, 114, -36, 80, 81, -108, -41, 112),
),
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(-52, -92, 64, 108, -8, -100, 87, -86, -60, 18, -18, -81, 114, -84, 76, -24, 84, 52, -34, 79, -30, -66, -112, -55, -35, -119, 127, -109, 35, 18, -29, -25),
chainCode = byteArrayOf(94, -33, -94, -63, 40, -20, -26, 71, 111, 9, 87, -36, -42, -33, 40, -53, 85, 31, -36, -29, 65, -121, 2, -23, -119, -51, 114, -17, -39, -74, 23, -97),
curve = EllipticCurve.Ed25519Slip0010,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 4,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(-43, 1, -81, -47, -8, -103, -66, 42, 37, -7, 65, 54, 57, -24, 127, -89, 69, -112, 42, -46, -128, 36, -117, -28, 30, -48, 37, 52, 93, -47, 92, -47),
chainCode = byteArrayOf(4, -97, 81, -37, 76, -67, -87, -4, -82, -36, -45, -28, -117, -59, -62, 93, -73, 50, 65, -91, -83, 25, -95, 89, -64, -40, 113, 28, 59, 113, -99, 89),
),
isImported = false,
),
),
attestation = Attestation(
cardKeyAttestation = Attestation.Status.Verified,
walletKeysAttestation = Attestation.Status.Skipped,
firmwareAttestation = Attestation.Status.Skipped,
cardUniquenessAttestation = Attestation.Status.Skipped,
),
backupStatus = CardDTO.BackupStatus.Active(1),
)
override val scanResponse = ScanResponse(
card = cardDto,
productType = ProductType.Wallet2,
walletData = null,
secondTwinPublicKey = null,
derivedKeys = emptyMap(),
primaryCard = null,
)
override val derivationTaskResponse = DerivationTaskResponse(
entries = mapOf(
ByteArrayKey(
byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5),
)
to
ExtendedPublicKeysMap(
mapOf(
DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch
publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121),
chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge
publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124),
chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
),
),
),
)
override val extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(2, -93, -36, -105, 121, -52, -30, -43, -67, -7, -31, -26, -35, 25, 99, 25, -20, 118, -20, -125, -89, 12, -101, -86, 74, -91, 23, -24, 93, -86, 20, -53, -8),
chainCode = byteArrayOf(32, 60, 63, -96, 97, 58, 121, 108, 75, 59, 63, -113, 60, -49, 47, 33, 15, -65, -69, 45, -7, -26, 65, -5, -91, -55, -42, -102, -127, -104, -111, 96),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
)
override val successResponse = SuccessResponse(cardId = "BB00004000000000")
override val createProductWalletTaskResponse = CreateProductWalletTaskResponse(
card = cardDto,
derivedKeys = mapOf(
ByteArrayKey(
byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5),
)
to
ExtendedPublicKeysMap(
mapOf(
DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
),
),
),
primaryCard = primaryCard,
)
override val importWalletResponse: CreateProductWalletTaskResponse
get() = TODO("Not yet implemented")
override val createFirstTwinResponse: CreateWalletResponse
get() = error("Available only for Twin")
override val createSecondTwinResponse: CreateWalletResponse
get() = error("Available only for Twin")
override val finalizeTwinResponse: ScanResponse
get() = error("Available only for Twin")
}

View file

@ -0,0 +1,307 @@
package com.tangem.tap.domain.sdk.mocks.content
import com.tangem.common.SuccessResponse
import com.tangem.common.card.*
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ProductType
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.operations.attestation.Attestation
import com.tangem.operations.backup.PrimaryCard
import com.tangem.operations.derivation.DerivationTaskResponse
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.sdk.api.CreateProductWalletTaskResponse
import com.tangem.tap.domain.sdk.mocks.MockContent
import java.util.Date
object MetaplanetMockContent : MockContent {
private val primaryCard = PrimaryCard(
cardId = "BB00004000000000",
batchId = "BB000040",
cardPublicKey = byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5),
linkingKey = byteArrayOf( //
2, 121, 98, 127, -70, 14, 5, -23, -76, 115, -30, -26, 111, 17, 110, 34, -100, -121,
-57, -123, 74, 3, -91, 56, -20, 56, 50, -40, -101, 96, 82, 70, -91,
),
existingWalletsCount = 5, isHDWalletAllowed = true,
issuer = Card.Issuer(
name = "TANGEM SDK",
publicKey = byteArrayOf(2, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34),
),
manufacturer = Card.Manufacturer(
name = "TANGEM",
manufactureDate = Date(1743759687),
signature = byteArrayOf(),
),
walletCurves = listOf(
EllipticCurve.Secp256k1,
EllipticCurve.Ed25519,
EllipticCurve.Bls12381G2Aug,
EllipticCurve.Secp256r1,
EllipticCurve.Ed25519Slip0010,
EllipticCurve.Bls12381G2,
EllipticCurve.Bls12381G2Pop,
EllipticCurve.Bip0340,
),
firmwareVersion = FirmwareVersion(
major = 6,
minor = 33,
patch = 0,
type = FirmwareVersion.FirmwareType.Release,
),
isKeysImportAllowed = false,
certificate = null,
)
override val cardDto = CardDTO(
cardId = "BB00004000000000",
batchId = "BB000040",
cardPublicKey = byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5),
firmwareVersion = CardDTO.FirmwareVersion(
major = 6,
minor = 33,
patch = 0,
type = FirmwareVersion.FirmwareType.Release,
),
manufacturer = CardDTO.Manufacturer(
name = "TANGEM",
manufactureDate = Date(1698094800000),
signature = byteArrayOf(51, -12, 14, -56, 7, -39, 5, 63, 59, 24, 102, 99, -126, 124, -127, -108, -118, 71, -19, -71, 4, -47, -121, -46, 49, -51, -31, 100, -56, -15, 96, -37, 25, 82, 94, -88, 48, 98, -105, -97, -40, 41, 27, 116, 65, 26, 78, -85, 66, -94, -92, 15, 50, -2, 7, -69, 41, 56, -75, 59, 86, 68, -38, -3),
),
issuer = CardDTO.Issuer(
name = "TANGEM SDK",
publicKey = byteArrayOf(2, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34),
),
settings = CardDTO.Settings(
securityDelay = 15000,
maxWalletsCount = 20,
isSettingAccessCodeAllowed = true,
isSettingPasscodeAllowed = true,
isResettingUserCodesAllowed = false,
isLinkedTerminalEnabled = true,
isBackupAllowed = true,
supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None),
isFilesAllowed = true,
isHDWalletAllowed = true,
isKeysImportAllowed = true,
),
userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = true),
linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None,
isAccessCodeSet = true,
isPasscodeSet = false,
supportedCurves = listOf(
EllipticCurve.Secp256k1,
EllipticCurve.Ed25519,
EllipticCurve.Bls12381G2Aug,
EllipticCurve.Secp256r1,
EllipticCurve.Ed25519Slip0010,
EllipticCurve.Bls12381G2,
EllipticCurve.Bls12381G2Pop,
EllipticCurve.Bip0340,
),
wallets = listOf(
CardDTO.Wallet(
publicKey = byteArrayOf(3, 17, 59, -102, -56, 66, 10, 36, 97, -106, -92, -117, -105, -88, -2, -3, -95, -75, -54, -2, 57, 27, -90, -19, 82, 103, -12, -52, -126, -105, -120, -55, -2),
chainCode = byteArrayOf(-34, -28, -28, -12, 80, -109, -90, -31, -74, 36, -78, -21, 39, -125, -39, -91, 63, 40, -26, 3, 66, 27, -62, -55, -97, 52, -65, -11, 26, -80, 33, -45),
curve = EllipticCurve.Secp256k1,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 0,
hasBackup = true,
derivedKeys = mapOf(
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(2, 55, -114, -94, -73, -61, -50, 51, -115, 55, -79, 63, -96, -44, -64, -24, -36, -122, -123, -38, 81, -15, -127, -97, -42, 72, -85, -62, -98, 46, 119, 16, -55),
chainCode = byteArrayOf(31, 17, 71, -28, -29, 17, 72, -29, -98, 112, 31, -8, 72, -75, 4, -11, 60, -100, 9, 35, 58, -42, -38, 96, -71, -68, 24, -119, -43, -18, -122, 72),
),
DerivationPath("m/84'/0'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(2, -26, 103, 96, 112, 127, -125, 0, 7, 53, 30, -12, -82, 45, 14, 107, -9, 126, 75, 104, -67, -49, 35, -12, -82, -90, 101, -101, 125, -76, 88, -54, 99),
chainCode = byteArrayOf(-42, 36, 97, 65, -64, -113, 76, -91, -9, 11, 89, 123, -9, -3, 21, 103, -113, -60, 48, -31, -34, 108, 111, -38, -110, -80, 109, 17, -29, 2, 45, -71),
),
DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(2, -96, -3, -83, 101, 63, -25, -125, -4, -65, -42, -56, 24, -52, 118, -11, -104, -105, 40, -59, 20, -109, -97, 29, -95, -6, -80, 2, 67, 103, -80, -22, -94),
chainCode = byteArrayOf(-125, 27, -91, 38, -66, 109, -92, 16, -37, 93, 107, -29, -128, -1, 115, -64, 108, -63, 17, 27, 58, 78, -2, 39, 88, -39, 44, 89, 32, -38, -16, -38),
),
),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(-109, 42, -80, -115, -12, 44, 48, -118, -89, 10, 21, 59, 98, -110, -86, -14, 123, 105, -30, -49, -40, -4, -7, 32, 60, 67, -88, -52, -96, -10, -14, 123),
chainCode = byteArrayOf(-101, -10, -92, 72, 19, 121, -38, 105, -55, -82, -68, 13, -6, 17, 66, -10, -75, 58, 119, -127, 74, 68, 82, 25, 45, -110, 3, 3, 108, -97, -51, -127),
),
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(-109, 42, -80, -115, -12, 44, 48, -118, -89, 10, 21, 59, 98, -110, -86, -14, 123, 105, -30, -49, -40, -4, -7, 32, 60, 67, -88, -52, -96, -10, -14, 123),
chainCode = byteArrayOf(-101, -10, -92, 72, 19, 121, -38, 105, -55, -82, -68, 13, -6, 17, 66, -10, -75, 58, 119, -127, 74, 68, 82, 25, 45, -110, 3, 3, 108, -97, -51, -127),
curve = EllipticCurve.Ed25519,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 1,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(-64, 18, -128, 121, -89, -37, -99, 44, -125, -72, -111, -79, 7, 85, 40, 67, -39, 117, 123, 11, 105, -6, -5, -79, 19, -10, -29, 20, -14, -40, 5, 90),
chainCode = byteArrayOf(33, -97, 53, -112, 61, 112, -24, 74, -87, -85, -124, -4, 103, -94, -97, 76, -41, -27, 118, 33, 55, 121, -17, -52, 60, 122, 27, 25, 29, -76, 78, 11),
),
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(-118, -77, -109, 8, -119, 101, -91, 46, -44, 15, 52, -64, -92, 5, -102, 126, 52, 121, 63, -76, -54, 80, -82, -5, 31, -35, 105, -119, -96, -54, 38, 2, -6, 109, 117, -26, -47, -20, 108, 106, -69, 72, -37, -117, -30, -9, 104, -123),
chainCode = null,
curve = EllipticCurve.Bls12381G2Aug,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 1,
remainingSignatures = null,
index = 2,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = null,
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(17, -78, 53, 101, 96, -110, -10, -48, -84, 88, -64, 58, -26, -13, -2, -6, -25, 23, -79, -45, 58, 77, 52, -75, -123, 121, 32, 92, 84, -74, -111, -8),
chainCode = byteArrayOf(-110, 73, -124, -65, -1, -70, 64, -89, 95, -74, 15, 46, -110, 87, 15, -61, 120, -51, 111, 111, -45, 37, 123, -114, 65, -116, 21, 39, -77, 86, -3, -26),
curve = EllipticCurve.Bip0340,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 3,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(115, 14, 11, 0, -93, 81, -103, -95, -75, -84, 18, -120, -31, 76, -83, -81, 91, 25, -75, 36, -99, -53, -25, -15, -1, -57, 14, -39, 98, -116, -63, -123),
chainCode = byteArrayOf(23, 5, 38, -48, 67, -42, -31, -21, 89, 11, 22, -28, 44, -19, -115, -78, 123, -27, 57, 57, -24, -86, 55, 15, 104, 114, -36, 80, 81, -108, -41, 112),
),
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(-52, -92, 64, 108, -8, -100, 87, -86, -60, 18, -18, -81, 114, -84, 76, -24, 84, 52, -34, 79, -30, -66, -112, -55, -35, -119, 127, -109, 35, 18, -29, -25),
chainCode = byteArrayOf(94, -33, -94, -63, 40, -20, -26, 71, 111, 9, 87, -36, -42, -33, 40, -53, 85, 31, -36, -29, 65, -121, 2, -23, -119, -51, 114, -17, -39, -74, 23, -97),
curve = EllipticCurve.Ed25519Slip0010,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 4,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(-43, 1, -81, -47, -8, -103, -66, 42, 37, -7, 65, 54, 57, -24, 127, -89, 69, -112, 42, -46, -128, 36, -117, -28, 30, -48, 37, 52, 93, -47, 92, -47),
chainCode = byteArrayOf(4, -97, 81, -37, 76, -67, -87, -4, -82, -36, -45, -28, -117, -59, -62, 93, -73, 50, 65, -91, -83, 25, -95, 89, -64, -40, 113, 28, 59, 113, -99, 89),
),
isImported = false,
),
),
attestation = Attestation(
cardKeyAttestation = Attestation.Status.Verified,
walletKeysAttestation = Attestation.Status.Skipped,
firmwareAttestation = Attestation.Status.Skipped,
cardUniquenessAttestation = Attestation.Status.Skipped,
),
backupStatus = CardDTO.BackupStatus.Active(2),
)
override val scanResponse = ScanResponse(
card = cardDto,
productType = ProductType.Wallet2,
walletData = null,
secondTwinPublicKey = null,
derivedKeys = emptyMap(),
primaryCard = null,
)
override val derivationTaskResponse = DerivationTaskResponse(
entries = mapOf(
ByteArrayKey(
byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5),
)
to
ExtendedPublicKeysMap(
mapOf(
DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch
publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121),
chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge
publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124),
chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
),
),
),
)
override val extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(2, -93, -36, -105, 121, -52, -30, -43, -67, -7, -31, -26, -35, 25, 99, 25, -20, 118, -20, -125, -89, 12, -101, -86, 74, -91, 23, -24, 93, -86, 20, -53, -8),
chainCode = byteArrayOf(32, 60, 63, -96, 97, 58, 121, 108, 75, 59, 63, -113, 60, -49, 47, 33, 15, -65, -69, 45, -7, -26, 65, -5, -91, -55, -42, -102, -127, -104, -111, 96),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
)
override val successResponse = SuccessResponse(cardId = "BB00004000000000")
override val createProductWalletTaskResponse = CreateProductWalletTaskResponse(
card = cardDto,
derivedKeys = mapOf(
ByteArrayKey(
byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5),
)
to
ExtendedPublicKeysMap(
mapOf(
DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
),
),
),
primaryCard = primaryCard,
)
override val importWalletResponse: CreateProductWalletTaskResponse
get() = TODO("Not yet implemented")
override val createFirstTwinResponse: CreateWalletResponse
get() = error("Available only for Twin")
override val createSecondTwinResponse: CreateWalletResponse
get() = error("Available only for Twin")
override val finalizeTwinResponse: ScanResponse
get() = error("Available only for Twin")
}

View file

@ -0,0 +1,307 @@
package com.tangem.tap.domain.sdk.mocks.content
import com.tangem.common.SuccessResponse
import com.tangem.common.card.*
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ProductType
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.operations.attestation.Attestation
import com.tangem.operations.backup.PrimaryCard
import com.tangem.operations.derivation.DerivationTaskResponse
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.sdk.api.CreateProductWalletTaskResponse
import com.tangem.tap.domain.sdk.mocks.MockContent
import java.util.Date
object RedPandaDoubleMockContent : MockContent {
private val primaryCard = PrimaryCard(
cardId = "BB00003800000000",
batchId = "BB000038",
cardPublicKey = byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5),
linkingKey = byteArrayOf( //
2, 121, 98, 127, -70, 14, 5, -23, -76, 115, -30, -26, 111, 17, 110, 34, -100, -121,
-57, -123, 74, 3, -91, 56, -20, 56, 50, -40, -101, 96, 82, 70, -91,
),
existingWalletsCount = 5, isHDWalletAllowed = true,
issuer = Card.Issuer(
name = "TANGEM SDK",
publicKey = byteArrayOf(2, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34),
),
manufacturer = Card.Manufacturer(
name = "TANGEM",
manufactureDate = Date(1743759687),
signature = byteArrayOf(),
),
walletCurves = listOf(
EllipticCurve.Secp256k1,
EllipticCurve.Ed25519,
EllipticCurve.Bls12381G2Aug,
EllipticCurve.Secp256r1,
EllipticCurve.Ed25519Slip0010,
EllipticCurve.Bls12381G2,
EllipticCurve.Bls12381G2Pop,
EllipticCurve.Bip0340,
),
firmwareVersion = FirmwareVersion(
major = 6,
minor = 33,
patch = 0,
type = FirmwareVersion.FirmwareType.Release,
),
isKeysImportAllowed = false,
certificate = null,
)
override val cardDto = CardDTO(
cardId = "BB00003800000000",
batchId = "BB000038",
cardPublicKey = byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5),
firmwareVersion = CardDTO.FirmwareVersion(
major = 6,
minor = 33,
patch = 0,
type = FirmwareVersion.FirmwareType.Release,
),
manufacturer = CardDTO.Manufacturer(
name = "TANGEM",
manufactureDate = Date(1698094800000),
signature = byteArrayOf(51, -12, 14, -56, 7, -39, 5, 63, 59, 24, 102, 99, -126, 124, -127, -108, -118, 71, -19, -71, 4, -47, -121, -46, 49, -51, -31, 100, -56, -15, 96, -37, 25, 82, 94, -88, 48, 98, -105, -97, -40, 41, 27, 116, 65, 26, 78, -85, 66, -94, -92, 15, 50, -2, 7, -69, 41, 56, -75, 59, 86, 68, -38, -3),
),
issuer = CardDTO.Issuer(
name = "TANGEM SDK",
publicKey = byteArrayOf(2, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34),
),
settings = CardDTO.Settings(
securityDelay = 15000,
maxWalletsCount = 20,
isSettingAccessCodeAllowed = true,
isSettingPasscodeAllowed = true,
isResettingUserCodesAllowed = false,
isLinkedTerminalEnabled = true,
isBackupAllowed = true,
supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None),
isFilesAllowed = true,
isHDWalletAllowed = true,
isKeysImportAllowed = true,
),
userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = true),
linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None,
isAccessCodeSet = true,
isPasscodeSet = false,
supportedCurves = listOf(
EllipticCurve.Secp256k1,
EllipticCurve.Ed25519,
EllipticCurve.Bls12381G2Aug,
EllipticCurve.Secp256r1,
EllipticCurve.Ed25519Slip0010,
EllipticCurve.Bls12381G2,
EllipticCurve.Bls12381G2Pop,
EllipticCurve.Bip0340,
),
wallets = listOf(
CardDTO.Wallet(
publicKey = byteArrayOf(3, 17, 59, -102, -56, 66, 10, 36, 97, -106, -92, -117, -105, -88, -2, -3, -95, -75, -54, -2, 57, 27, -90, -19, 82, 103, -12, -52, -126, -105, -120, -55, -2),
chainCode = byteArrayOf(-34, -28, -28, -12, 80, -109, -90, -31, -74, 36, -78, -21, 39, -125, -39, -91, 63, 40, -26, 3, 66, 27, -62, -55, -97, 52, -65, -11, 26, -80, 33, -45),
curve = EllipticCurve.Secp256k1,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 0,
hasBackup = true,
derivedKeys = mapOf(
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(2, 55, -114, -94, -73, -61, -50, 51, -115, 55, -79, 63, -96, -44, -64, -24, -36, -122, -123, -38, 81, -15, -127, -97, -42, 72, -85, -62, -98, 46, 119, 16, -55),
chainCode = byteArrayOf(31, 17, 71, -28, -29, 17, 72, -29, -98, 112, 31, -8, 72, -75, 4, -11, 60, -100, 9, 35, 58, -42, -38, 96, -71, -68, 24, -119, -43, -18, -122, 72),
),
DerivationPath("m/84'/0'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(2, -26, 103, 96, 112, 127, -125, 0, 7, 53, 30, -12, -82, 45, 14, 107, -9, 126, 75, 104, -67, -49, 35, -12, -82, -90, 101, -101, 125, -76, 88, -54, 99),
chainCode = byteArrayOf(-42, 36, 97, 65, -64, -113, 76, -91, -9, 11, 89, 123, -9, -3, 21, 103, -113, -60, 48, -31, -34, 108, 111, -38, -110, -80, 109, 17, -29, 2, 45, -71),
),
DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(2, -96, -3, -83, 101, 63, -25, -125, -4, -65, -42, -56, 24, -52, 118, -11, -104, -105, 40, -59, 20, -109, -97, 29, -95, -6, -80, 2, 67, 103, -80, -22, -94),
chainCode = byteArrayOf(-125, 27, -91, 38, -66, 109, -92, 16, -37, 93, 107, -29, -128, -1, 115, -64, 108, -63, 17, 27, 58, 78, -2, 39, 88, -39, 44, 89, 32, -38, -16, -38),
),
),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(-109, 42, -80, -115, -12, 44, 48, -118, -89, 10, 21, 59, 98, -110, -86, -14, 123, 105, -30, -49, -40, -4, -7, 32, 60, 67, -88, -52, -96, -10, -14, 123),
chainCode = byteArrayOf(-101, -10, -92, 72, 19, 121, -38, 105, -55, -82, -68, 13, -6, 17, 66, -10, -75, 58, 119, -127, 74, 68, 82, 25, 45, -110, 3, 3, 108, -97, -51, -127),
),
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(-109, 42, -80, -115, -12, 44, 48, -118, -89, 10, 21, 59, 98, -110, -86, -14, 123, 105, -30, -49, -40, -4, -7, 32, 60, 67, -88, -52, -96, -10, -14, 123),
chainCode = byteArrayOf(-101, -10, -92, 72, 19, 121, -38, 105, -55, -82, -68, 13, -6, 17, 66, -10, -75, 58, 119, -127, 74, 68, 82, 25, 45, -110, 3, 3, 108, -97, -51, -127),
curve = EllipticCurve.Ed25519,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 1,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(-64, 18, -128, 121, -89, -37, -99, 44, -125, -72, -111, -79, 7, 85, 40, 67, -39, 117, 123, 11, 105, -6, -5, -79, 19, -10, -29, 20, -14, -40, 5, 90),
chainCode = byteArrayOf(33, -97, 53, -112, 61, 112, -24, 74, -87, -85, -124, -4, 103, -94, -97, 76, -41, -27, 118, 33, 55, 121, -17, -52, 60, 122, 27, 25, 29, -76, 78, 11),
),
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(-118, -77, -109, 8, -119, 101, -91, 46, -44, 15, 52, -64, -92, 5, -102, 126, 52, 121, 63, -76, -54, 80, -82, -5, 31, -35, 105, -119, -96, -54, 38, 2, -6, 109, 117, -26, -47, -20, 108, 106, -69, 72, -37, -117, -30, -9, 104, -123),
chainCode = null,
curve = EllipticCurve.Bls12381G2Aug,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 1,
remainingSignatures = null,
index = 2,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = null,
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(17, -78, 53, 101, 96, -110, -10, -48, -84, 88, -64, 58, -26, -13, -2, -6, -25, 23, -79, -45, 58, 77, 52, -75, -123, 121, 32, 92, 84, -74, -111, -8),
chainCode = byteArrayOf(-110, 73, -124, -65, -1, -70, 64, -89, 95, -74, 15, 46, -110, 87, 15, -61, 120, -51, 111, 111, -45, 37, 123, -114, 65, -116, 21, 39, -77, 86, -3, -26),
curve = EllipticCurve.Bip0340,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 3,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(115, 14, 11, 0, -93, 81, -103, -95, -75, -84, 18, -120, -31, 76, -83, -81, 91, 25, -75, 36, -99, -53, -25, -15, -1, -57, 14, -39, 98, -116, -63, -123),
chainCode = byteArrayOf(23, 5, 38, -48, 67, -42, -31, -21, 89, 11, 22, -28, 44, -19, -115, -78, 123, -27, 57, 57, -24, -86, 55, 15, 104, 114, -36, 80, 81, -108, -41, 112),
),
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(-52, -92, 64, 108, -8, -100, 87, -86, -60, 18, -18, -81, 114, -84, 76, -24, 84, 52, -34, 79, -30, -66, -112, -55, -35, -119, 127, -109, 35, 18, -29, -25),
chainCode = byteArrayOf(94, -33, -94, -63, 40, -20, -26, 71, 111, 9, 87, -36, -42, -33, 40, -53, 85, 31, -36, -29, 65, -121, 2, -23, -119, -51, 114, -17, -39, -74, 23, -97),
curve = EllipticCurve.Ed25519Slip0010,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 4,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(-43, 1, -81, -47, -8, -103, -66, 42, 37, -7, 65, 54, 57, -24, 127, -89, 69, -112, 42, -46, -128, 36, -117, -28, 30, -48, 37, 52, 93, -47, 92, -47),
chainCode = byteArrayOf(4, -97, 81, -37, 76, -67, -87, -4, -82, -36, -45, -28, -117, -59, -62, 93, -73, 50, 65, -91, -83, 25, -95, 89, -64, -40, 113, 28, 59, 113, -99, 89),
),
isImported = false,
),
),
attestation = Attestation(
cardKeyAttestation = Attestation.Status.Verified,
walletKeysAttestation = Attestation.Status.Skipped,
firmwareAttestation = Attestation.Status.Skipped,
cardUniquenessAttestation = Attestation.Status.Skipped,
),
backupStatus = CardDTO.BackupStatus.Active(1),
)
override val scanResponse = ScanResponse(
card = cardDto,
productType = ProductType.Wallet2,
walletData = null,
secondTwinPublicKey = null,
derivedKeys = emptyMap(),
primaryCard = null,
)
override val derivationTaskResponse = DerivationTaskResponse(
entries = mapOf(
ByteArrayKey(
byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5),
)
to
ExtendedPublicKeysMap(
mapOf(
DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch
publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121),
chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge
publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124),
chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
),
),
),
)
override val extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(2, -93, -36, -105, 121, -52, -30, -43, -67, -7, -31, -26, -35, 25, 99, 25, -20, 118, -20, -125, -89, 12, -101, -86, 74, -91, 23, -24, 93, -86, 20, -53, -8),
chainCode = byteArrayOf(32, 60, 63, -96, 97, 58, 121, 108, 75, 59, 63, -113, 60, -49, 47, 33, 15, -65, -69, 45, -7, -26, 65, -5, -91, -55, -42, -102, -127, -104, -111, 96),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
)
override val successResponse = SuccessResponse(cardId = "BB00003800000000")
override val createProductWalletTaskResponse = CreateProductWalletTaskResponse(
card = cardDto,
derivedKeys = mapOf(
ByteArrayKey(
byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5),
)
to
ExtendedPublicKeysMap(
mapOf(
DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
),
),
),
primaryCard = primaryCard,
)
override val importWalletResponse: CreateProductWalletTaskResponse
get() = TODO("Not yet implemented")
override val createFirstTwinResponse: CreateWalletResponse
get() = error("Available only for Twin")
override val createSecondTwinResponse: CreateWalletResponse
get() = error("Available only for Twin")
override val finalizeTwinResponse: ScanResponse
get() = error("Available only for Twin")
}

View file

@ -0,0 +1,307 @@
package com.tangem.tap.domain.sdk.mocks.content
import com.tangem.common.SuccessResponse
import com.tangem.common.card.*
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ProductType
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.operations.attestation.Attestation
import com.tangem.operations.backup.PrimaryCard
import com.tangem.operations.derivation.DerivationTaskResponse
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.sdk.api.CreateProductWalletTaskResponse
import com.tangem.tap.domain.sdk.mocks.MockContent
import java.util.Date
object RedPandaMockContent : MockContent {
private val primaryCard = PrimaryCard(
cardId = "BB00003800000000",
batchId = "BB000038",
cardPublicKey = byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5),
linkingKey = byteArrayOf( //
2, 121, 98, 127, -70, 14, 5, -23, -76, 115, -30, -26, 111, 17, 110, 34, -100, -121,
-57, -123, 74, 3, -91, 56, -20, 56, 50, -40, -101, 96, 82, 70, -91,
),
existingWalletsCount = 5, isHDWalletAllowed = true,
issuer = Card.Issuer(
name = "TANGEM SDK",
publicKey = byteArrayOf(2, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34),
),
manufacturer = Card.Manufacturer(
name = "TANGEM",
manufactureDate = Date(1743759687),
signature = byteArrayOf(),
),
walletCurves = listOf(
EllipticCurve.Secp256k1,
EllipticCurve.Ed25519,
EllipticCurve.Bls12381G2Aug,
EllipticCurve.Secp256r1,
EllipticCurve.Ed25519Slip0010,
EllipticCurve.Bls12381G2,
EllipticCurve.Bls12381G2Pop,
EllipticCurve.Bip0340,
),
firmwareVersion = FirmwareVersion(
major = 6,
minor = 33,
patch = 0,
type = FirmwareVersion.FirmwareType.Release,
),
isKeysImportAllowed = false,
certificate = null,
)
override val cardDto = CardDTO(
cardId = "BB00003800000000",
batchId = "BB000038",
cardPublicKey = byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5),
firmwareVersion = CardDTO.FirmwareVersion(
major = 6,
minor = 33,
patch = 0,
type = FirmwareVersion.FirmwareType.Release,
),
manufacturer = CardDTO.Manufacturer(
name = "TANGEM",
manufactureDate = Date(1698094800000),
signature = byteArrayOf(51, -12, 14, -56, 7, -39, 5, 63, 59, 24, 102, 99, -126, 124, -127, -108, -118, 71, -19, -71, 4, -47, -121, -46, 49, -51, -31, 100, -56, -15, 96, -37, 25, 82, 94, -88, 48, 98, -105, -97, -40, 41, 27, 116, 65, 26, 78, -85, 66, -94, -92, 15, 50, -2, 7, -69, 41, 56, -75, 59, 86, 68, -38, -3),
),
issuer = CardDTO.Issuer(
name = "TANGEM SDK",
publicKey = byteArrayOf(2, 95, 22, -67, 29, 46, -81, -28, 99, -26, 42, 51, 90, 9, -26, -78, -69, -53, -48, 68, 82, 82, 104, -123, -53, 103, -97, -60, -46, 122, -15, -67, 34),
),
settings = CardDTO.Settings(
securityDelay = 15000,
maxWalletsCount = 20,
isSettingAccessCodeAllowed = true,
isSettingPasscodeAllowed = true,
isResettingUserCodesAllowed = false,
isLinkedTerminalEnabled = true,
isBackupAllowed = true,
supportedEncryptionModes = listOf(EncryptionMode.Strong, EncryptionMode.Fast, EncryptionMode.None),
isFilesAllowed = true,
isHDWalletAllowed = true,
isKeysImportAllowed = true,
),
userSettings = CardDTO.UserSettings(isUserCodeRecoveryAllowed = true),
linkedTerminalStatus = CardDTO.LinkedTerminalStatus.None,
isAccessCodeSet = true,
isPasscodeSet = false,
supportedCurves = listOf(
EllipticCurve.Secp256k1,
EllipticCurve.Ed25519,
EllipticCurve.Bls12381G2Aug,
EllipticCurve.Secp256r1,
EllipticCurve.Ed25519Slip0010,
EllipticCurve.Bls12381G2,
EllipticCurve.Bls12381G2Pop,
EllipticCurve.Bip0340,
),
wallets = listOf(
CardDTO.Wallet(
publicKey = byteArrayOf(3, 17, 59, -102, -56, 66, 10, 36, 97, -106, -92, -117, -105, -88, -2, -3, -95, -75, -54, -2, 57, 27, -90, -19, 82, 103, -12, -52, -126, -105, -120, -55, -2),
chainCode = byteArrayOf(-34, -28, -28, -12, 80, -109, -90, -31, -74, 36, -78, -21, 39, -125, -39, -91, 63, 40, -26, 3, 66, 27, -62, -55, -97, 52, -65, -11, 26, -80, 33, -45),
curve = EllipticCurve.Secp256k1,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 0,
hasBackup = true,
derivedKeys = mapOf(
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(2, 55, -114, -94, -73, -61, -50, 51, -115, 55, -79, 63, -96, -44, -64, -24, -36, -122, -123, -38, 81, -15, -127, -97, -42, 72, -85, -62, -98, 46, 119, 16, -55),
chainCode = byteArrayOf(31, 17, 71, -28, -29, 17, 72, -29, -98, 112, 31, -8, 72, -75, 4, -11, 60, -100, 9, 35, 58, -42, -38, 96, -71, -68, 24, -119, -43, -18, -122, 72),
),
DerivationPath("m/84'/0'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(2, -26, 103, 96, 112, 127, -125, 0, 7, 53, 30, -12, -82, 45, 14, 107, -9, 126, 75, 104, -67, -49, 35, -12, -82, -90, 101, -101, 125, -76, 88, -54, 99),
chainCode = byteArrayOf(-42, 36, 97, 65, -64, -113, 76, -91, -9, 11, 89, 123, -9, -3, 21, 103, -113, -60, 48, -31, -34, 108, 111, -38, -110, -80, 109, 17, -29, 2, 45, -71),
),
DerivationPath("m/44'/1'/0'/0/0") to ExtendedPublicKey(
publicKey = byteArrayOf(2, -96, -3, -83, 101, 63, -25, -125, -4, -65, -42, -56, 24, -52, 118, -11, -104, -105, 40, -59, 20, -109, -97, 29, -95, -6, -80, 2, 67, 103, -80, -22, -94),
chainCode = byteArrayOf(-125, 27, -91, 38, -66, 109, -92, 16, -37, 93, 107, -29, -128, -1, 115, -64, 108, -63, 17, 27, 58, 78, -2, 39, 88, -39, 44, 89, 32, -38, -16, -38),
),
),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(-109, 42, -80, -115, -12, 44, 48, -118, -89, 10, 21, 59, 98, -110, -86, -14, 123, 105, -30, -49, -40, -4, -7, 32, 60, 67, -88, -52, -96, -10, -14, 123),
chainCode = byteArrayOf(-101, -10, -92, 72, 19, 121, -38, 105, -55, -82, -68, 13, -6, 17, 66, -10, -75, 58, 119, -127, 74, 68, 82, 25, 45, -110, 3, 3, 108, -97, -51, -127),
),
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(-109, 42, -80, -115, -12, 44, 48, -118, -89, 10, 21, 59, 98, -110, -86, -14, 123, 105, -30, -49, -40, -4, -7, 32, 60, 67, -88, -52, -96, -10, -14, 123),
chainCode = byteArrayOf(-101, -10, -92, 72, 19, 121, -38, 105, -55, -82, -68, 13, -6, 17, 66, -10, -75, 58, 119, -127, 74, 68, 82, 25, 45, -110, 3, 3, 108, -97, -51, -127),
curve = EllipticCurve.Ed25519,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 1,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(-64, 18, -128, 121, -89, -37, -99, 44, -125, -72, -111, -79, 7, 85, 40, 67, -39, 117, 123, 11, 105, -6, -5, -79, 19, -10, -29, 20, -14, -40, 5, 90),
chainCode = byteArrayOf(33, -97, 53, -112, 61, 112, -24, 74, -87, -85, -124, -4, 103, -94, -97, 76, -41, -27, 118, 33, 55, 121, -17, -52, 60, 122, 27, 25, 29, -76, 78, 11),
),
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(-118, -77, -109, 8, -119, 101, -91, 46, -44, 15, 52, -64, -92, 5, -102, 126, 52, 121, 63, -76, -54, 80, -82, -5, 31, -35, 105, -119, -96, -54, 38, 2, -6, 109, 117, -26, -47, -20, 108, 106, -69, 72, -37, -117, -30, -9, 104, -123),
chainCode = null,
curve = EllipticCurve.Bls12381G2Aug,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 1,
remainingSignatures = null,
index = 2,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = null,
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(17, -78, 53, 101, 96, -110, -10, -48, -84, 88, -64, 58, -26, -13, -2, -6, -25, 23, -79, -45, 58, 77, 52, -75, -123, 121, 32, 92, 84, -74, -111, -8),
chainCode = byteArrayOf(-110, 73, -124, -65, -1, -70, 64, -89, 95, -74, 15, 46, -110, 87, 15, -61, 120, -51, 111, 111, -45, 37, 123, -114, 65, -116, 21, 39, -77, 86, -3, -26),
curve = EllipticCurve.Bip0340,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 3,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(115, 14, 11, 0, -93, 81, -103, -95, -75, -84, 18, -120, -31, 76, -83, -81, 91, 25, -75, 36, -99, -53, -25, -15, -1, -57, 14, -39, 98, -116, -63, -123),
chainCode = byteArrayOf(23, 5, 38, -48, 67, -42, -31, -21, 89, 11, 22, -28, 44, -19, -115, -78, 123, -27, 57, 57, -24, -86, 55, 15, 104, 114, -36, 80, 81, -108, -41, 112),
),
isImported = false,
),
CardDTO.Wallet(
publicKey = byteArrayOf(-52, -92, 64, 108, -8, -100, 87, -86, -60, 18, -18, -81, 114, -84, 76, -24, 84, 52, -34, 79, -30, -66, -112, -55, -35, -119, 127, -109, 35, 18, -29, -25),
chainCode = byteArrayOf(94, -33, -94, -63, 40, -20, -26, 71, 111, 9, 87, -36, -42, -33, 40, -53, 85, 31, -36, -29, 65, -121, 2, -23, -119, -51, 114, -17, -39, -74, 23, -97),
curve = EllipticCurve.Ed25519Slip0010,
settings = CardWallet.Settings(isPermanent = false),
totalSignedHashes = 0,
remainingSignatures = null,
index = 4,
hasBackup = true,
derivedKeys = emptyMap(),
extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(-43, 1, -81, -47, -8, -103, -66, 42, 37, -7, 65, 54, 57, -24, 127, -89, 69, -112, 42, -46, -128, 36, -117, -28, 30, -48, 37, 52, 93, -47, 92, -47),
chainCode = byteArrayOf(4, -97, 81, -37, 76, -67, -87, -4, -82, -36, -45, -28, -117, -59, -62, 93, -73, 50, 65, -91, -83, 25, -95, 89, -64, -40, 113, 28, 59, 113, -99, 89),
),
isImported = false,
),
),
attestation = Attestation(
cardKeyAttestation = Attestation.Status.Verified,
walletKeysAttestation = Attestation.Status.Skipped,
firmwareAttestation = Attestation.Status.Skipped,
cardUniquenessAttestation = Attestation.Status.Skipped,
),
backupStatus = CardDTO.BackupStatus.Active(2),
)
override val scanResponse = ScanResponse(
card = cardDto,
productType = ProductType.Wallet2,
walletData = null,
secondTwinPublicKey = null,
derivedKeys = emptyMap(),
primaryCard = null,
)
override val derivationTaskResponse = DerivationTaskResponse(
entries = mapOf(
ByteArrayKey(
byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5),
)
to
ExtendedPublicKeysMap(
mapOf(
DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/145'/0'/0/0") to ExtendedPublicKey( // bch
publicKey = byteArrayOf(2, 38, -6, 92, -37, -91, -59, -108, -18, -119, -55, 41, 38, -33, 44, 59, 24, -79, -14, -38, -10, -123, 106, 56, 39, 8, 112, 29, -41, 99, 70, -104, -121),
chainCode = byteArrayOf(105, -21, -61, -50, 68, -89, 119, 53, -96, -40, 119, 77, -122, 121, 16, 40, -50, -48, -105, -101, -74, -7, -94, -59, -90, 96, 59, 99, 43, -91, 115, -29),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/3'/0'/0/0") to ExtendedPublicKey( // doge
publicKey = byteArrayOf(3, -25, -24, -97, -124, 24, -89, 44, 75, 123, 92, -86, -73, -93, 25, -90, -89, -95, 88, 3, 107, 37, -1, -85, -32, -57, -123, -41, 108, -9, -96, 77, -124),
chainCode = byteArrayOf(119, 3, 41, 112, 71, 54, 72, 30, 39, 25, 25, -104, 92, 46, -109, 63, 93, 67, 43, -102, -87, 39, -95, 106, 45, 67, 109, -29, -35, 10, -107, 104),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
),
),
),
)
override val extendedPublicKey = ExtendedPublicKey(
publicKey = byteArrayOf(2, -93, -36, -105, 121, -52, -30, -43, -67, -7, -31, -26, -35, 25, 99, 25, -20, 118, -20, -125, -89, 12, -101, -86, 74, -91, 23, -24, 93, -86, 20, -53, -8),
chainCode = byteArrayOf(32, 60, 63, -96, 97, 58, 121, 108, 75, 59, 63, -113, 60, -49, 47, 33, 15, -65, -69, 45, -7, -26, 65, -5, -91, -55, -42, -102, -127, -104, -111, 96),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
)
override val successResponse = SuccessResponse(cardId = "BB00003800000000")
override val createProductWalletTaskResponse = CreateProductWalletTaskResponse(
card = cardDto,
derivedKeys = mapOf(
ByteArrayKey(
byteArrayOf(3, 23, -112, -57, 109, 60, -82, -36, 45, -14, -34, -12, 10, -89, 14, 37, 38, 36, -102, 37, 93, 90, 69, -113, -117, 120, -29, 12, -125, 43, -40, -31, 5),
)
to
ExtendedPublicKeysMap(
mapOf(
DerivationPath("m/44'/0'/0'/0/0") to ExtendedPublicKey( // btc
publicKey = byteArrayOf(3, 45, 58, -110, -52, -51, -83, -4, -45, -118, 119, 37, 123, -17, 66, -83, 61, -106, 115, 47, 121, 66, 84, -122, -57, -45, 7, -79, 70, -13, 28, -125, -52),
chainCode = byteArrayOf(93, 51, 52, -66, -39, -38, 34, -84, 50, 1, -127, -20, 80, -20, -30, -72, 2, 1, -78, -81, -17, 51, -52, -25, 12, 108, 50, 89, -66, 18, 65, 70),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // eth
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
depth = 0,
parentFingerprint = byteArrayOf(0, 0, 0, 0),
childNumber = 0,
),
),
),
),
primaryCard = primaryCard,
)
override val importWalletResponse: CreateProductWalletTaskResponse
get() = TODO("Not yet implemented")
override val createFirstTwinResponse: CreateWalletResponse
get() = error("Available only for Twin")
override val createSecondTwinResponse: CreateWalletResponse
get() = error("Available only for Twin")
override val finalizeTwinResponse: ScanResponse
get() = error("Available only for Twin")
}

View file

@ -33,7 +33,7 @@ class FinalizeTwinTask(
visaCardScanHandler = null,
visaCoroutineScope = null,
shouldCheckIsAlreadyActivated = false,
isDynamicAddressesEnabled = false,
isDynamicAddressesEnabled = isDynamicAddressesEnabled,
onboardingV2FeatureToggles = null,
).run(session, callback)
is CompletionResult.Failure ->

View file

@ -1,55 +0,0 @@
package com.tangem.tap.features.details.redux
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.models.scan.ScanResponse
import kotlinx.coroutines.CoroutineScope
import org.rekotlin.Action
@Suppress("BooleanPropertyNaming")
sealed class DetailsAction : Action {
data class PrepareScreen(
val scanResponse: ScanResponse?,
val initializedAppSettingsState: AppSettingsState,
) : DetailsAction()
sealed class AppSettings : DetailsAction() {
data class SwitchPrivacySetting(
val enable: Boolean,
val setting: AppSetting,
) : AppSettings() {
data object Success : AppSettings()
data class Failure(
val prevState: Boolean,
val setting: AppSetting,
) : AppSettings()
}
data class CheckBiometricsStatus(
val coroutineScope: CoroutineScope,
) : AppSettings()
data object EnrollBiometrics : AppSettings()
data class BiometricsStatusChanged(
val isEnrollBiometricsNeeded: Boolean,
) : AppSettings()
data class ChangeAppThemeMode(
val appThemeMode: AppThemeMode,
) : AppSettings()
data class ChangeBalanceHiding(
val shouldHideBalance: Boolean,
) : AppSettings()
data class ChangeAppCurrency(
val currency: AppCurrency,
) : AppSettings()
data class Prepare(val state: AppSettingsState) : AppSettings()
}
data class ChangeAppCurrency(val currency: AppCurrency) : DetailsAction()
}

View file

@ -1,235 +0,0 @@
package com.tangem.tap.features.details.redux
import com.tangem.common.CompletionResult
import com.tangem.common.doOnFailure
import com.tangem.common.doOnSuccess
import com.tangem.core.analytics.Analytics
import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.common.wallets.UserWalletsListRepository.LockMethod
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import com.tangem.utils.logging.TangemLogger
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import org.rekotlin.Action
import org.rekotlin.Middleware
@Suppress("MemberNameEqualsClassName")
class DetailsMiddleware {
private val appSettingsMiddleware = AppSettingsMiddleware()
val detailsMiddleware: Middleware<AppState> = { _, stateProvider ->
{ next ->
{ action ->
if (!DemoHelper.tryHandle(stateProvider)) {
val detailsState = stateProvider()?.detailsState
if (detailsState != null) {
handleAction(action)
}
}
next(action)
}
}
}
private fun handleAction(action: Action) {
when (action) {
is DetailsAction.AppSettings -> appSettingsMiddleware.handle(action)
}
}
class AppSettingsMiddleware {
private val checkBiometricsStatusJobHolder = JobHolder()
fun handle(action: DetailsAction.AppSettings) {
when (action) {
is DetailsAction.AppSettings.SwitchPrivacySetting -> {
when (action.setting) {
AppSetting.RequireAccessCode -> toggleRequireAccessCode(enable = action.enable)
AppSetting.BiometricAuthentication -> toggleBiometricsAuthentication(enable = action.enable)
}
}
is DetailsAction.AppSettings.CheckBiometricsStatus -> {
observeBiometricsStatusChanges(action.coroutineScope)
}
is DetailsAction.AppSettings.EnrollBiometrics -> {
enrollBiometrics()
}
is DetailsAction.AppSettings.ChangeAppThemeMode -> {
changeAppThemeMode(action.appThemeMode)
}
is DetailsAction.AppSettings.ChangeBalanceHiding -> {
changeBalanceHiding(action.shouldHideBalance)
}
is DetailsAction.AppSettings.ChangeAppCurrency -> {
store.dispatch(GlobalAction.ChangeAppCurrency(action.currency))
store.dispatch(DetailsAction.ChangeAppCurrency(action.currency))
}
is DetailsAction.AppSettings.SwitchPrivacySetting.Success,
is DetailsAction.AppSettings.SwitchPrivacySetting.Failure,
is DetailsAction.AppSettings.BiometricsStatusChanged,
is DetailsAction.AppSettings.Prepare,
-> Unit
}
}
private fun toggleBiometricsAuthentication(enable: Boolean) {
scope.launch {
val walletsRepository = store.inject(DaggerGraphState::walletsRepository)
// Nothing to change
if (walletsRepository.useBiometricAuthentication() == enable) {
store.dispatchWithMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success)
return@launch
}
if (enable) {
setBiometricLockForAllWallets()
} else {
// Remove all biometric-related data
removeAllBiometricData()
walletsRepository.setRequireAccessCode(value = true)
}
walletsRepository.setUseBiometricAuthentication(value = enable)
store.dispatchWithMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success)
}
}
private fun toggleRequireAccessCode(enable: Boolean) {
scope.launch {
val walletsRepository = store.inject(DaggerGraphState::walletsRepository)
// Nothing to change
if (walletsRepository.requireAccessCode() == enable) {
store.dispatchWithMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success)
return@launch
}
if (enable) {
// Remove all saved access codes
removeAllBiometricSingData()
}
walletsRepository.setRequireAccessCode(value = enable)
store.dispatchWithMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success)
}
}
private suspend fun setBiometricLockForAllWallets() {
val userWalletsListRepository = store.inject(DaggerGraphState::userWalletsListRepository)
val userWallets = userWalletsListRepository.userWalletsSync()
userWallets.forEach { wallet ->
userWalletsListRepository.setLock(
userWalletId = wallet.walletId,
lockMethod = LockMethod.Biometric,
changeUnsecured = false,
)
}
}
private suspend fun removeAllBiometricData() {
val userWalletsListRepository = store.inject(DaggerGraphState::userWalletsListRepository)
userWalletsListRepository.userWalletsSync().forEach {
userWalletsListRepository.removeBiometricLock(it.walletId)
}
removeAllBiometricSingData()
}
private suspend fun removeAllBiometricSingData() {
deleteSavedAccessCodes()
val userWalletsListRepository = store.inject(DaggerGraphState::userWalletsListRepository)
val tangemHotSdk = store.inject(DaggerGraphState::tangemHotSdk)
userWalletsListRepository.userWalletsSync().forEach { wallet ->
if (wallet is UserWallet.Hot) {
userWalletsListRepository.saveWithoutLock(
userWallet = wallet.copy(
hotWalletId = tangemHotSdk.removeBiometryAuthIfPresented(wallet.hotWalletId),
),
)
}
}
}
private fun observeBiometricsStatusChanges(scope: CoroutineScope) {
val needEnrollBiometricsFlow = flow {
do {
val isEnrollBiometricsNeeded = runCatching(tangemSdkManager::needEnrollBiometrics).getOrNull()
if (isEnrollBiometricsNeeded != null) {
emit(isEnrollBiometricsNeeded)
}
delay(timeMillis = 200)
} while (true)
}
needEnrollBiometricsFlow
.distinctUntilChanged()
.onEach { needEnrollBiometrics ->
store.dispatchWithMain(DetailsAction.AppSettings.BiometricsStatusChanged(needEnrollBiometrics))
}
.launchIn(scope)
.saveIn(checkBiometricsStatusJobHolder)
}
private fun enrollBiometrics() {
Analytics.send(Settings.AppSettings.ButtonEnableBiometricAuthentication())
store.inject(DaggerGraphState::settingsManager).openBiometricSettings()
}
private fun changeAppThemeMode(appThemeMode: AppThemeMode) {
val repository = store.inject(DaggerGraphState::appThemeModeRepository)
scope.launch {
repository.changeAppThemeMode(appThemeMode)
}
}
private fun changeBalanceHiding(hideBalance: Boolean) {
val repository = store.inject(DaggerGraphState::balanceHidingRepository)
scope.launch {
val newState = repository.getBalanceHidingSettings().copy(
isHidingEnabledInSettings = hideBalance,
isBalanceHidden = false,
)
repository.storeBalanceHidingSettings(newState)
}
}
private suspend fun deleteSavedAccessCodes(): CompletionResult<Unit> {
return tangemSdkManager.clearSavedUserCodes()
.doOnSuccess {
Analytics.send(Settings.AppSettings.SaveAccessCodeSwitcherChanged(AnalyticsParam.OnOffState.Off))
store.inject(DaggerGraphState::settingsRepository).setShouldSaveAccessCodes(value = false)
store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = false,
)
}
.doOnFailure { error ->
TangemLogger.e("Unable to delete saved access codes", error)
}
}
}
}

View file

@ -1,103 +0,0 @@
package com.tangem.tap.features.details.redux
import com.tangem.tap.common.redux.AppState
import org.rekotlin.Action
object DetailsReducer {
fun reduce(action: Action, state: AppState): DetailsState = internalReduce(action, state)
}
@Suppress("CyclomaticComplexMethod")
private fun internalReduce(action: Action, state: AppState): DetailsState {
if (action !is DetailsAction) return state.detailsState
val detailsState = state.detailsState
return when (action) {
is DetailsAction.PrepareScreen -> {
handlePrepareScreen(action)
}
is DetailsAction.AppSettings -> {
handlePrivacyAction(action, detailsState)
}
is DetailsAction.ChangeAppCurrency -> detailsState.copy(
appSettingsState = detailsState.appSettingsState.copy(
selectedAppCurrency = action.currency,
),
)
}
}
private fun handlePrepareScreen(action: DetailsAction.PrepareScreen): DetailsState {
return DetailsState(
scanResponse = action.scanResponse,
appSettingsState = action.initializedAppSettingsState,
)
}
@Suppress("LongMethod", "CyclomaticComplexMethod")
private fun handlePrivacyAction(action: DetailsAction.AppSettings, state: DetailsState): DetailsState {
return when (action) {
is DetailsAction.AppSettings.SwitchPrivacySetting -> state.copy(
appSettingsState = when (action.setting) {
AppSetting.RequireAccessCode -> state.appSettingsState.copy(
isInProgress = true,
requireAccessCode = action.enable,
)
AppSetting.BiometricAuthentication -> state.appSettingsState.copy(
isInProgress = true,
useBiometricAuthentication = action.enable,
)
},
)
is DetailsAction.AppSettings.SwitchPrivacySetting.Success -> state.copy(
appSettingsState = state.appSettingsState.copy(
isInProgress = false,
),
)
is DetailsAction.AppSettings.SwitchPrivacySetting.Failure -> state.copy(
appSettingsState = when (action.setting) {
AppSetting.RequireAccessCode -> state.appSettingsState.copy(
isInProgress = false,
requireAccessCode = action.prevState,
)
AppSetting.BiometricAuthentication -> state.appSettingsState.copy(
isInProgress = false,
needEnrollBiometrics = action.prevState,
)
},
)
is DetailsAction.AppSettings.BiometricsStatusChanged -> state.copy(
appSettingsState = state.appSettingsState.copy(
needEnrollBiometrics = action.isEnrollBiometricsNeeded,
),
)
is DetailsAction.AppSettings.ChangeAppThemeMode -> state.copy(
appSettingsState = state.appSettingsState.copy(
selectedThemeMode = action.appThemeMode,
),
)
is DetailsAction.AppSettings.ChangeAppCurrency -> state.copy(
appSettingsState = state.appSettingsState.copy(
selectedAppCurrency = action.currency,
),
)
is DetailsAction.AppSettings.ChangeBalanceHiding -> state.copy(
appSettingsState = state.appSettingsState.copy(
isHidingEnabled = action.shouldHideBalance,
),
)
// state should be copied to avoid concurrent modifications from different sources
is DetailsAction.AppSettings.Prepare -> state.copy(
appSettingsState = state.appSettingsState.copy(
isHidingEnabled = action.state.isHidingEnabled,
selectedAppCurrency = action.state.selectedAppCurrency,
selectedThemeMode = action.state.selectedThemeMode,
useBiometricAuthentication = action.state.useBiometricAuthentication,
requireAccessCode = action.state.requireAccessCode,
hasSecuredWallets = action.state.hasSecuredWallets,
),
)
is DetailsAction.AppSettings.EnrollBiometrics,
is DetailsAction.AppSettings.CheckBiometricsStatus,
-> state
}
}

View file

@ -1,30 +0,0 @@
package com.tangem.tap.features.details.redux
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.models.scan.ScanResponse
import org.rekotlin.StateType
data class DetailsState(
@Deprecated("Delete after onboarding refactoring")
val scanResponse: ScanResponse? = null,
val appSettingsState: AppSettingsState = AppSettingsState(),
) : StateType
@Suppress("BooleanPropertyNaming")
data class AppSettingsState(
val requireAccessCode: Boolean = false,
val useBiometricAuthentication: Boolean = false,
val needEnrollBiometrics: Boolean = false,
val hasSecuredWallets: Boolean = false,
val isHidingEnabled: Boolean = false,
val isInProgress: Boolean = false,
val selectedAppCurrency: AppCurrency = AppCurrency.Default,
val selectedThemeMode: AppThemeMode = AppThemeMode.DEFAULT,
)
enum class SecurityOption { LongTap, PassCode, AccessCode }
enum class AppSetting {
RequireAccessCode, BiometricAuthentication,
}

View file

@ -0,0 +1,3 @@
package com.tangem.tap.features.details.redux
enum class SecurityOption { LongTap, PassCode, AccessCode }

View file

@ -2,71 +2,59 @@ package com.tangem.tap.features.details.ui.appsettings
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState.Dialog
import com.tangem.core.ui.message.DialogMessage
import com.tangem.core.ui.message.EventMessageAction
import com.tangem.wallet.R
import kotlinx.collections.immutable.toImmutableList
internal class AppSettingsDialogsFactory {
fun createThemeModeSelectorDialog(
selectedModeIndex: Int,
onSelect: (AppThemeMode) -> Unit,
onDismiss: () -> Unit,
): Dialog.Selector {
val modes = AppThemeMode.available
return Dialog.Selector(
title = resourceReference(R.string.app_settings_theme_selector_title),
selectedItemIndex = selectedModeIndex,
items = modes.map { mode ->
resourceReference(
id = when (mode) {
AppThemeMode.FORCE_DARK -> R.string.app_settings_theme_mode_dark
AppThemeMode.FORCE_LIGHT -> R.string.app_settings_theme_mode_light
AppThemeMode.FOLLOW_SYSTEM -> R.string.app_settings_theme_mode_system
},
)
}.toImmutableList(),
onSelect = { index ->
val mode = AppThemeMode.available[index]
onSelect(mode)
},
onDismiss = onDismiss,
)
}
fun createDisableBiometricAuthenticationAlert(onDisable: () -> Unit, onDismiss: () -> Unit): Dialog.Alert {
return Dialog.Alert(
fun createDisableBiometricAuthenticationAlert(onDisable: () -> Unit): DialogMessage {
return DialogMessage(
title = resourceReference(R.string.common_attention),
description = resourceReference(
message = resourceReference(
R.string.app_settings_off_biometrics_alert_message,
wrappedList(resourceReference(R.string.common_biometrics)),
),
confirmText = resourceReference(R.string.common_disable),
onConfirm = onDisable,
onDismiss = onDismiss,
isDismissable = false,
firstActionBuilder = {
EventMessageAction(
title = resourceReference(R.string.common_disable),
isWarning = true,
onClick = onDisable,
)
},
secondActionBuilder = { cancelAction() },
)
}
fun createEnableRequireAccessCodeAlert(onEnable: () -> Unit, onDismiss: () -> Unit): Dialog.Alert {
return Dialog.Alert(
fun createEnableRequireAccessCodeAlert(onEnable: () -> Unit): DialogMessage {
return DialogMessage(
title = resourceReference(R.string.common_attention),
description = resourceReference(R.string.app_settings_on_require_access_code_alert_message),
confirmText = resourceReference(R.string.common_enable),
onConfirm = { onEnable() },
onDismiss = onDismiss,
message = resourceReference(R.string.app_settings_on_require_access_code_alert_message),
isDismissable = false,
firstActionBuilder = {
EventMessageAction(
title = resourceReference(R.string.common_enable),
onClick = onEnable,
)
},
secondActionBuilder = { cancelAction() },
)
}
fun createDisableRequireAccessCodeAlert(onDisable: () -> Unit, onDismiss: () -> Unit): Dialog.Alert {
return Dialog.Alert(
fun createDisableRequireAccessCodeAlert(onDisable: () -> Unit): DialogMessage {
return DialogMessage(
title = resourceReference(R.string.common_attention),
description = resourceReference(R.string.app_settings_off_require_access_code_alert_message),
confirmText = resourceReference(R.string.common_disable),
onConfirm = { onDisable() },
onDismiss = onDismiss,
message = resourceReference(R.string.app_settings_off_require_access_code_alert_message),
isDismissable = false,
firstActionBuilder = {
EventMessageAction(
title = resourceReference(R.string.common_disable),
isWarning = true,
onClick = onDisable,
)
},
secondActionBuilder = { cancelAction() },
)
}
}

View file

@ -8,8 +8,6 @@ import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
@ -42,13 +40,6 @@ internal fun AppSettingsScreen(state: AppSettingsScreenState, onBackClick: () ->
@Composable
private fun AppSettings(state: AppSettingsScreenState.Content) {
val dialog by rememberUpdatedState(newValue = state.dialog)
when (val safeDialog = dialog) {
is AppSettingsScreenState.Dialog.Alert -> SettingsAlertDialog(dialog = safeDialog)
is AppSettingsScreenState.Dialog.Selector -> SettingsSelectorDialog(dialog = safeDialog)
null -> Unit
}
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
LazyColumn(
@ -102,12 +93,7 @@ private class AppSettingsScreenStateProvider : CollectionPreviewParameterProvide
itemsFactory.createSelectThemeModeButton(AppThemeMode.DEFAULT, {}),
)
add(
AppSettingsScreenState.Content(
items = items,
dialog = null,
),
)
add(AppSettingsScreenState.Content(items = items))
},
)
// endregion Preview

View file

@ -10,10 +10,7 @@ internal sealed class AppSettingsScreenState {
object Loading : AppSettingsScreenState()
data class Content(
val items: ImmutableList<Item>,
val dialog: Dialog?,
) : AppSettingsScreenState()
data class Content(val items: ImmutableList<Item>) : AppSettingsScreenState()
@Immutable
sealed class Item {
@ -46,26 +43,4 @@ internal sealed class AppSettingsScreenState {
val onClick: () -> Unit,
) : Item()
}
@Immutable
sealed class Dialog {
abstract val onDismiss: () -> Unit
data class Alert(
val title: TextReference,
val description: TextReference,
val confirmText: TextReference,
val onConfirm: () -> Unit,
override val onDismiss: () -> Unit,
) : Dialog()
data class Selector(
val title: TextReference,
val selectedItemIndex: Int,
val items: ImmutableList<TextReference>,
val onSelect: (Int) -> Unit,
override val onDismiss: () -> Unit,
) : Dialog()
}
}

View file

@ -4,42 +4,56 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.slot.childSlot
import com.arkivanov.essenty.lifecycle.doOnResume
import com.tangem.common.routing.AppRouter
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.details.ui.appsettings.api.AppSettingsComponent
import com.tangem.tap.features.details.ui.appsettings.model.AppSettingsDialogConfig
import com.tangem.tap.features.details.ui.appsettings.model.AppSettingsModel
import com.tangem.tap.store
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@Suppress("UnusedPrivateMember")
internal class DefaultAppSettingsComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted params: Unit,
@Suppress("UnusedPrivateMember") @Assisted params: Unit,
) : AppSettingsComponent, AppComponentContext by appComponentContext {
private val model: AppSettingsModel = getOrCreateModel()
init {
private val dialogSlot = childSlot(
source = model.dialogNavigation,
serializer = AppSettingsDialogConfig.serializer(),
handleBackButton = true,
childFactory = { config, _ -> config },
)
init {
doOnResume { model.onResume() }
}
@Composable
override fun Content(modifier: Modifier) {
val state by model.uiState.collectAsStateWithLifecycle()
val dialog by dialogSlot.subscribeAsState()
AppSettingsScreen(
modifier = modifier,
state = state,
onBackClick = {
store.dispatchNavigationAction(AppRouter::pop)
},
onBackClick = model::onBackClick,
)
dialog.child?.instance?.let { config ->
when (config) {
is AppSettingsDialogConfig.ThemeModeSelector -> SettingsSelectorDialog(
config = config,
onSelect = model::onThemeModeSelected,
onDismiss = model::dismissDialog,
)
}
}
}
@AssistedFactory

View file

@ -0,0 +1,38 @@
package com.tangem.tap.features.details.ui.appsettings
import androidx.compose.runtime.Composable
import com.tangem.core.ui.components.DialogButtonUM
import com.tangem.core.ui.components.SelectorDialog
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.tap.features.details.ui.appsettings.model.AppSettingsDialogConfig
import com.tangem.wallet.R
import kotlinx.collections.immutable.toImmutableList
@Composable
internal fun SettingsSelectorDialog(
config: AppSettingsDialogConfig.ThemeModeSelector,
onSelect: (Int) -> Unit,
onDismiss: () -> Unit,
) {
val modes = AppThemeMode.available
SelectorDialog(
title = stringResourceSafe(R.string.app_settings_theme_selector_title),
selectedItemIndex = config.selectedModeIndex,
items = modes.map { mode ->
stringResourceSafe(
id = when (mode) {
AppThemeMode.FORCE_DARK -> R.string.app_settings_theme_mode_dark
AppThemeMode.FORCE_LIGHT -> R.string.app_settings_theme_mode_light
AppThemeMode.FOLLOW_SYSTEM -> R.string.app_settings_theme_mode_system
},
)
}.toImmutableList(),
confirmButton = DialogButtonUM(
title = stringResourceSafe(R.string.common_cancel),
onClick = onDismiss,
),
onSelect = onSelect,
onDismissDialog = onDismiss,
)
}

View file

@ -1,54 +0,0 @@
package com.tangem.tap.features.details.ui.appsettings.components
import android.content.res.Configuration
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.components.BasicDialog
import com.tangem.core.ui.components.DialogButtonUM
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.tap.features.details.ui.appsettings.AppSettingsDialogsFactory
import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState.Dialog
import com.tangem.wallet.R
@Composable
internal fun SettingsAlertDialog(dialog: Dialog.Alert) {
BasicDialog(
title = dialog.title.resolveReference(),
message = dialog.description.resolveReference(),
isDismissable = false,
confirmButton = DialogButtonUM(
title = dialog.confirmText.resolveReference(),
isWarning = true,
onClick = dialog.onConfirm,
),
dismissButton = DialogButtonUM(
title = stringResourceSafe(id = R.string.common_cancel),
onClick = dialog.onDismiss,
),
onDismissDialog = dialog.onDismiss,
)
}
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun AlertDialogPreview(@PreviewParameter(AlertDialogProvider::class) dialog: Dialog.Alert) {
TangemThemePreview {
SettingsAlertDialog(dialog = dialog)
}
}
private class AlertDialogProvider : CollectionPreviewParameterProvider<Dialog>(
collection = buildList {
val dialogsFactory = AppSettingsDialogsFactory()
add(dialogsFactory.createThemeModeSelectorDialog(selectedModeIndex = 0, onSelect = {}, onDismiss = {}))
add(dialogsFactory.createDisableBiometricAuthenticationAlert(onDisable = {}, onDismiss = {}))
},
)
// endregion Preview

View file

@ -1,52 +0,0 @@
package com.tangem.tap.features.details.ui.appsettings.components
import android.content.res.Configuration
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.components.DialogButtonUM
import com.tangem.core.ui.components.SelectorDialog
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.tap.features.details.ui.appsettings.AppSettingsDialogsFactory
import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState.Dialog
import com.tangem.wallet.R
import kotlinx.collections.immutable.toImmutableList
@Composable
internal fun SettingsSelectorDialog(dialog: Dialog.Selector) {
SelectorDialog(
title = dialog.title.resolveReference(),
selectedItemIndex = dialog.selectedItemIndex,
items = dialog.items.map { it.resolveReference() }.toImmutableList(),
confirmButton = DialogButtonUM(
title = stringResourceSafe(R.string.common_cancel),
onClick = dialog.onDismiss,
),
onSelect = dialog.onSelect,
onDismissDialog = dialog.onDismiss,
)
}
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun SettingsSelectorDialogPreview(@PreviewParameter(DialogProvider::class) param: Dialog.Selector) {
TangemThemePreview {
SettingsSelectorDialog(param)
}
}
private class DialogProvider : CollectionPreviewParameterProvider<Dialog.Selector>(
collection = listOf(
AppSettingsDialogsFactory().createThemeModeSelectorDialog(
selectedModeIndex = 0,
onSelect = {},
onDismiss = {},
),
),
)
// endregion Preview

View file

@ -0,0 +1,10 @@
package com.tangem.tap.features.details.ui.appsettings.model
import kotlinx.serialization.Serializable
@Serializable
internal sealed interface AppSettingsDialogConfig {
@Serializable
data class ThemeModeSelector(val selectedModeIndex: Int) : AppSettingsDialogConfig
}

View file

@ -1,11 +1,18 @@
package com.tangem.tap.features.details.ui.appsettings.model
import androidx.compose.runtime.Stable
import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate
import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.common.doOnFailure
import com.tangem.common.doOnSuccess
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.navigation.settings.SettingsManager
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.DialogMessage
import com.tangem.domain.appcurrency.model.AppCurrency
@ -13,113 +20,138 @@ import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.hot.sdk.TangemHotSdk
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.features.details.redux.AppSetting
import com.tangem.tap.features.details.redux.AppSettingsState
import com.tangem.tap.features.details.redux.DetailsAction
import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.features.details.ui.appsettings.AppSettingsDialogsFactory
import com.tangem.tap.features.details.ui.appsettings.AppSettingsItemsFactory
import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState
import com.tangem.tap.features.details.ui.appsettings.analytics.AppSettingsItemsAnalyticsSender
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import com.tangem.utils.extensions.addIf
import com.tangem.utils.logging.TangemLogger
import com.tangem.wallet.R
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import org.rekotlin.StoreSubscriber
import javax.inject.Inject
@Suppress("LongParameterList")
@Suppress("LongParameterList", "LargeClass")
@Stable
@ModelScoped
internal class AppSettingsModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val appCurrencyRepository: AppCurrencyRepository,
appCurrencyRepository: AppCurrencyRepository,
private val walletsRepository: WalletsRepository,
private val userWalletsListRepository: UserWalletsListRepository,
private val balanceHidingRepository: BalanceHidingRepository,
private val analyticsEventHandler: AnalyticsEventHandler,
private val appThemeModeRepository: AppThemeModeRepository,
private val appSettingsItemsAnalyticsSender: AppSettingsItemsAnalyticsSender,
private val tangemSdkManager: TangemSdkManager,
private val settingsManager: SettingsManager,
private val settingsRepository: SettingsRepository,
private val cardSdkConfigRepository: CardSdkConfigRepository,
private val tangemHotSdk: TangemHotSdk,
private val router: Router,
private val uiMessageSender: UiMessageSender,
) : Model(), StoreSubscriber<DetailsState> {
) : Model() {
private val itemsFactory = AppSettingsItemsFactory()
private val dialogsFactory = AppSettingsDialogsFactory()
private val appCurrencyUpdatesJobHolder = JobHolder()
val dialogNavigation: SlotNavigation<AppSettingsDialogConfig> = SlotNavigation()
private val _uiState: MutableStateFlow<AppSettingsScreenState> = MutableStateFlow(
value = AppSettingsScreenState.Loading,
)
val uiState: StateFlow<AppSettingsScreenState> = _uiState
private val localState = MutableStateFlow(LocalState())
private val biometricsStatusJobHolder = JobHolder()
val uiState: StateFlow<AppSettingsScreenState>
field = MutableStateFlow<AppSettingsScreenState>(value = AppSettingsScreenState.Loading)
init {
bootstrapAppCurrencyUpdates()
bootstrapBiometricsUpdates()
bootstrapLocalState()
combine(
flow = appCurrencyRepository.getSelectedAppCurrency().distinctUntilChanged(),
flow2 = appThemeModeRepository.getAppThemeMode(),
flow3 = balanceHidingRepository.getBalanceHidingSettingsFlow(),
flow4 = localState,
) { currency, themeMode, hidingSettings, local ->
AppSettingsState(
appCurrency = currency,
themeMode = themeMode,
isHidingEnabled = hidingSettings.isHidingEnabledInSettings,
local = local,
)
}
.onEach { state ->
val items = buildItems(state)
uiState.update { prevState ->
when (prevState) {
is AppSettingsScreenState.Content -> prevState.copy(items = items)
is AppSettingsScreenState.Loading -> AppSettingsScreenState.Content(items = items)
}
}
}
.launchIn(modelScope)
subscribeToStoreChanges()
sendItemsAnalytics()
}
override fun newState(state: DetailsState) {
val items = buildItems(state.appSettingsState)
_uiState.update { prevState ->
when (prevState) {
is AppSettingsScreenState.Content -> prevState.copy(
items = items,
)
is AppSettingsScreenState.Loading -> AppSettingsScreenState.Content(
items = items,
dialog = null,
)
}
}
}
fun onResume() {
store.dispatch(DetailsAction.AppSettings.CheckBiometricsStatus(modelScope))
observeBiometricsStatusChanges()
}
override fun onDestroy() {
super.onDestroy()
store.unsubscribe(subscriber = this)
private fun observeBiometricsStatusChanges() {
flow {
do {
val isEnrollBiometricsNeeded = runCatching(tangemSdkManager::needEnrollBiometrics).getOrNull()
if (isEnrollBiometricsNeeded != null) {
emit(isEnrollBiometricsNeeded)
}
delay(timeMillis = 200)
} while (true)
}
.flowOn(dispatchers.default)
.distinctUntilChanged()
.onEach { isEnrollBiometricsNeeded ->
localState.update { it.copy(isEnrollBiometricsNeeded = isEnrollBiometricsNeeded) }
}
.launchIn(modelScope)
.saveIn(biometricsStatusJobHolder)
}
private fun buildItems(state: AppSettingsState): ImmutableList<AppSettingsScreenState.Item> {
val items = buildList {
addIf(
condition = state.needEnrollBiometrics,
condition = state.local.isEnrollBiometricsNeeded,
element = itemsFactory.createEnrollBiometricsCard(onClick = ::enrollBiometrics),
)
add(
itemsFactory.createSelectAppCurrencyButton(
currentAppCurrencyName = state.selectedAppCurrency.name,
currentAppCurrencyName = state.appCurrency.name,
onClick = ::showAppCurrencySelector,
),
)
val canUseBiometrics =
!state.needEnrollBiometrics && !state.isInProgress && state.hasSecuredWallets
val canUseBiometrics = with(state.local) {
!isEnrollBiometricsNeeded && !isInProgress && hasSecuredWallets
}
add(
itemsFactory.createUseBiometricsSwitch(
isChecked = state.useBiometricAuthentication,
isChecked = state.local.isBiometricAuthenticationUsed,
isEnabled = canUseBiometrics,
onCheckedChange = ::onBiometricAuthenticationToggled,
onDisabledClick = ::onBiometricAuthenticationDisabledClicked,
@ -128,8 +160,8 @@ internal class AppSettingsModel @Inject constructor(
add(
itemsFactory.createRequireAccessCodeSwitch(
isChecked = state.requireAccessCode || !state.useBiometricAuthentication,
isEnabled = canUseBiometrics && state.useBiometricAuthentication,
isChecked = state.local.isAccessCodeRequired || !state.local.isBiometricAuthenticationUsed,
isEnabled = canUseBiometrics && state.local.isBiometricAuthenticationUsed,
onCheckedChange = ::onRequireAccessCodeToggled,
),
)
@ -144,8 +176,8 @@ internal class AppSettingsModel @Inject constructor(
add(
itemsFactory.createSelectThemeModeButton(
currentThemeMode = state.selectedThemeMode,
onClick = { showThemeModeSelector(state.selectedThemeMode) },
currentThemeMode = state.themeMode,
onClick = { showThemeModeSelector(state.themeMode) },
),
)
}
@ -154,31 +186,35 @@ internal class AppSettingsModel @Inject constructor(
}
private fun enrollBiometrics() {
store.dispatchOnMain(DetailsAction.AppSettings.EnrollBiometrics)
analyticsEventHandler.send(Settings.AppSettings.ButtonEnableBiometricAuthentication())
settingsManager.openBiometricSettings()
}
fun onBackClick() {
router.pop()
}
private fun showAppCurrencySelector() {
store.dispatchNavigationAction { push(AppRoute.AppCurrencySelector) }
router.push(AppRoute.AppCurrencySelector)
}
private fun showThemeModeSelector(selectedMode: AppThemeMode) {
updateContentState {
copy(
dialog = dialogsFactory.createThemeModeSelectorDialog(
selectedModeIndex = selectedMode.ordinal,
onSelect = { mode ->
dialogNavigation.activate(AppSettingsDialogConfig.ThemeModeSelector(selectedMode.ordinal))
}
fun onThemeModeSelected(index: Int) {
val mode = AppThemeMode.available[index]
analyticsEventHandler.send(
event = Settings.AppSettings.ThemeSwitched(
theme = AnalyticsParam.AppTheme.fromAppThemeMode(mode),
),
)
store.dispatchOnMain(DetailsAction.AppSettings.ChangeAppThemeMode(mode))
dismissDialog()
},
onDismiss = ::dismissDialog,
),
)
changeAppThemeMode(mode)
dialogNavigation.dismiss()
}
fun dismissDialog() {
dialogNavigation.dismiss()
}
private fun onBiometricAuthenticationToggled(isChecked: Boolean) {
@ -186,21 +222,15 @@ internal class AppSettingsModel @Inject constructor(
// val param = AnalyticsParam.OnOffState(isChecked)
// analyticsEventHandler.send(Settings.AppSettings.BiometricAuthenticationChanged(param))
if (isChecked) {
onSettingsToggled(AppSetting.BiometricAuthentication, enable = true)
toggleBiometricsAuthentication(enable = true)
} else {
updateContentState {
copy(
dialog = dialogsFactory.createDisableBiometricAuthenticationAlert(
onDisable = {
onSettingsToggled(AppSetting.BiometricAuthentication, enable = false)
dismissDialog()
},
onDismiss = ::dismissDialog,
uiMessageSender.send(
dialogsFactory.createDisableBiometricAuthenticationAlert(
onDisable = { toggleBiometricsAuthentication(enable = false) },
),
)
}
}
}
private fun onBiometricAuthenticationDisabledClicked() {
uiMessageSender.send(DialogMessage(message = resourceReference(R.string.app_settings_access_code_warning)))
@ -210,74 +240,136 @@ internal class AppSettingsModel @Inject constructor(
// TODO : Uncomment and implement analytics event when ready
// val param = AnalyticsParam.OnOffState(isChecked)
// analyticsEventHandler.send(Settings.AppSettings.RequireAccessCodeChanged(param))
updateContentState {
copy(
dialog = if (isChecked) {
if (isChecked) {
uiMessageSender.send(
dialogsFactory.createEnableRequireAccessCodeAlert(
onEnable = {
onSettingsToggled(AppSetting.RequireAccessCode, enable = true)
dismissDialog()
},
onDismiss = ::dismissDialog,
onEnable = { toggleRequireAccessCode(enable = true) },
),
)
} else {
uiMessageSender.send(
dialogsFactory.createDisableRequireAccessCodeAlert(
onDisable = {
onSettingsToggled(AppSetting.RequireAccessCode, enable = false)
dismissDialog()
},
onDismiss = ::dismissDialog,
)
},
onDisable = { toggleRequireAccessCode(enable = false) },
),
)
}
}
private fun onSettingsToggled(setting: AppSetting, enable: Boolean) {
store.dispatch(DetailsAction.AppSettings.SwitchPrivacySetting(enable = enable, setting = setting))
private fun toggleBiometricsAuthentication(enable: Boolean) {
localState.update { it.copy(isBiometricAuthenticationUsed = enable, isInProgress = true) }
modelScope.launch {
// Nothing to change
if (walletsRepository.useBiometricAuthentication() == enable) {
localState.update { it.copy(isInProgress = false) }
return@launch
}
if (enable) {
setBiometricLockForAllWallets()
} else {
removeAllBiometricData()
walletsRepository.setRequireAccessCode(value = true)
localState.update { it.copy(isAccessCodeRequired = true) }
}
walletsRepository.setUseBiometricAuthentication(value = enable)
localState.update { it.copy(isInProgress = false) }
}
}
private fun toggleRequireAccessCode(enable: Boolean) {
localState.update { it.copy(isAccessCodeRequired = enable, isInProgress = true) }
modelScope.launch {
// Nothing to change
if (walletsRepository.requireAccessCode() == enable) {
localState.update { it.copy(isInProgress = false) }
return@launch
}
if (enable) {
removeAllBiometricSingData(userWalletsListRepository.userWalletsSync())
}
walletsRepository.setRequireAccessCode(value = enable)
localState.update { it.copy(isInProgress = false) }
}
}
private suspend fun setBiometricLockForAllWallets() {
val userWallets = userWalletsListRepository.userWalletsSync()
userWallets.forEach { wallet ->
userWalletsListRepository.setLock(
userWalletId = wallet.walletId,
lockMethod = UserWalletsListRepository.LockMethod.Biometric,
changeUnsecured = false,
)
}
}
private suspend fun removeAllBiometricData() {
val userWallets = userWalletsListRepository.userWalletsSync()
userWallets.forEach {
userWalletsListRepository.removeBiometricLock(it.walletId)
}
removeAllBiometricSingData(userWallets)
}
private suspend fun removeAllBiometricSingData(userWallets: List<UserWallet>) {
deleteSavedAccessCodes()
userWallets.forEach { wallet ->
if (wallet is UserWallet.Hot) {
userWalletsListRepository.saveWithoutLock(
userWallet = wallet.copy(
hotWalletId = tangemHotSdk.removeBiometryAuthIfPresented(wallet.hotWalletId),
),
)
}
}
}
private suspend fun deleteSavedAccessCodes() {
tangemSdkManager.clearSavedUserCodes()
.doOnSuccess {
analyticsEventHandler.send(
Settings.AppSettings.SaveAccessCodeSwitcherChanged(AnalyticsParam.OnOffState.Off),
)
settingsRepository.setShouldSaveAccessCodes(value = false)
cardSdkConfigRepository.setAccessCodeRequestPolicy(isBiometricsRequestPolicy = false)
}
.doOnFailure { error ->
TangemLogger.e("Unable to delete saved access codes", error)
}
}
private fun onFlipToHideBalanceToggled(enable: Boolean) {
val param = AnalyticsParam.OnOffState(enable)
analyticsEventHandler.send(Settings.AppSettings.HideBalanceChanged(param))
store.dispatch(DetailsAction.AppSettings.ChangeBalanceHiding(shouldHideBalance = enable))
}
private fun dismissDialog() {
updateContentState { copy(dialog = null) }
}
private fun bootstrapAppCurrencyUpdates() {
appCurrencyRepository
.getSelectedAppCurrency()
.onEach { appCurrency ->
if (appCurrency.code == store.state.globalState.appCurrency.code) return@onEach
store.dispatchWithMain(DetailsAction.AppSettings.ChangeAppCurrency(appCurrency))
}
.launchIn(scope)
.saveIn(appCurrencyUpdatesJobHolder)
}
private fun bootstrapBiometricsUpdates() = modelScope.launch {
val state = AppSettingsState(
useBiometricAuthentication = walletsRepository.useBiometricAuthentication(),
requireAccessCode = walletsRepository.requireAccessCode(),
isHidingEnabled = balanceHidingRepository.getBalanceHidingSettings().isHidingEnabledInSettings,
selectedAppCurrency = appCurrencyRepository.getSelectedAppCurrency().firstOrNull() ?: AppCurrency.Default,
selectedThemeMode = appThemeModeRepository.getAppThemeMode().firstOrNull() ?: AppThemeMode.DEFAULT,
hasSecuredWallets = userWalletsListRepository.hasSecuredWallets(),
modelScope.launch {
val settings = balanceHidingRepository.getBalanceHidingSettings().copy(
isHidingEnabledInSettings = enable,
isBalanceHidden = false,
)
store.dispatchWithMain(DetailsAction.AppSettings.Prepare(state))
balanceHidingRepository.storeBalanceHidingSettings(settings)
}
}
private fun subscribeToStoreChanges() {
store.subscribe(subscriber = this) { state ->
state.skipRepeats { oldState, newState ->
oldState.detailsState == newState.detailsState
}.select { it.detailsState }
private fun changeAppThemeMode(mode: AppThemeMode) {
modelScope.launch {
appThemeModeRepository.changeAppThemeMode(mode)
}
}
private fun bootstrapLocalState() = modelScope.launch {
localState.update { state ->
state.copy(
hasSecuredWallets = userWalletsListRepository.hasSecuredWallets(),
isEnrollBiometricsNeeded = runCatching(tangemSdkManager::needEnrollBiometrics).getOrNull() == true,
isBiometricAuthenticationUsed = walletsRepository.useBiometricAuthentication(),
isAccessCodeRequired = walletsRepository.requireAccessCode(),
)
}
}
@ -286,15 +378,21 @@ internal class AppSettingsModel @Inject constructor(
.filterIsInstance<AppSettingsScreenState.Content>()
.distinctUntilChangedBy(AppSettingsScreenState.Content::items)
.onEach { appSettingsItemsAnalyticsSender.send(it.items) }
.launchIn(scope)
.launchIn(modelScope)
}
private fun updateContentState(block: AppSettingsScreenState.Content.() -> AppSettingsScreenState.Content) {
_uiState.update { prevState ->
when (prevState) {
is AppSettingsScreenState.Content -> block(prevState)
is AppSettingsScreenState.Loading -> prevState
}
}
}
private data class LocalState(
val hasSecuredWallets: Boolean = false,
val isEnrollBiometricsNeeded: Boolean = false,
val isBiometricAuthenticationUsed: Boolean = false,
val isAccessCodeRequired: Boolean = false,
val isInProgress: Boolean = false,
)
private data class AppSettingsState(
val themeMode: AppThemeMode = AppThemeMode.DEFAULT,
val isHidingEnabled: Boolean = false,
val appCurrency: AppCurrency = AppCurrency.Default,
val local: LocalState = LocalState(),
)
}

View file

@ -188,7 +188,7 @@ internal class DefaultRampManager(
private fun CryptoCurrency.findAssetPredicate(assetId: ExpressAsset.ID): Boolean {
val currencyAssedId = ExpressAsset.ID(
networkId = this.network.backendId,
networkId = this.network.rawId,
contractAddress = (this as? CryptoCurrency.Token)?.contractAddress,
)

View file

@ -138,6 +138,7 @@ internal class ChildFactory @Inject constructor(
AppRoute.ManageTokens.Source.SETTINGS -> ManageTokensSource.SETTINGS
AppRoute.ManageTokens.Source.STORIES -> ManageTokensSource.STORIES
AppRoute.ManageTokens.Source.ACCOUNT -> ManageTokensSource.ACCOUNT
AppRoute.ManageTokens.Source.WALLET -> ManageTokensSource.WALLET
}
val mode = route.accountId?.let { ManageTokensMode.Account(it) } ?: ManageTokensMode.None
@ -194,6 +195,9 @@ internal class ChildFactory @Inject constructor(
source = params.source,
)
},
preselectedSection = route.preselectedSection,
shouldOpenExchanges = route.shouldOpenExchanges,
exchangesCount = route.exchangesCount,
),
componentFactory = feedEntryComponentFactory,
)
@ -457,7 +461,10 @@ internal class ChildFactory @Inject constructor(
is AppRoute.Markets -> {
createComponentChild(
context = context,
params = FeedEntryRoute.MarketTokenList,
params = FeedEntryRoute.MarketTokenList(
preselectedOrder = route.preselectedOrder,
preselectedInterval = route.preselectedInterval,
),
componentFactory = feedEntryComponentFactory,
)
}

View file

@ -8,6 +8,7 @@ import com.tangem.data.card.sdk.CardSdkProvider
import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler
import com.tangem.features.feed.entry.deeplink.MarketsDeepLinkHandler
import com.tangem.features.feed.entry.deeplink.MarketsTokenDetailDeepLinkHandler
import com.tangem.features.feed.entry.deeplink.MarketsTokenExchangesDeepLinkHandler
import com.tangem.features.feed.entry.deeplink.NewsDetailsDeepLinkHandler
import com.tangem.features.onramp.deeplink.BuyDeepLinkHandler
import com.tangem.features.onramp.deeplink.OnrampDeepLinkHandler
@ -51,6 +52,7 @@ internal class DeepLinkFactory @Inject constructor(
private val swapDeepLink: SwapDeepLinkHandler.Factory,
private val promoDeepLink: PromoDeeplinkHandler.Factory,
private val onboardVisaDeepLink: OnboardVisaDeepLinkHandler.Factory,
private val marketsTokenExchangesDeepLink: MarketsTokenExchangesDeepLinkHandler.Factory,
private val newsDetailsDeepLink: NewsDetailsDeepLinkHandler.Factory,
) {
private val permittedAppRoute = MutableStateFlow(false)
@ -148,8 +150,9 @@ internal class DeepLinkFactory @Inject constructor(
isFromOnNewIntent = isFromOnNewIntent,
)
DeepLinkRoute.Staking.host -> stakingDeepLink.create(coroutineScope, queryParams)
DeepLinkRoute.Markets.host -> marketsDeepLink.create()
DeepLinkRoute.Markets.host -> marketsDeepLink.create(queryParams)
DeepLinkRoute.MarketTokenDetail.host -> marketsTokenDetailDeepLink.create(coroutineScope, queryParams)
DeepLinkRoute.TokenExchanges.host -> marketsTokenExchangesDeepLink.create(coroutineScope, queryParams)
DeepLinkRoute.Buy.host -> buyDeepLink.create()
DeepLinkRoute.Sell.host -> sellDeepLink.create()
DeepLinkRoute.Swap.host -> swapDeepLink.create()

View file

@ -2,5 +2,6 @@
<resources>
<string name="tangem_app_name" translatable="false">Tangem</string>
<string name="mock_card_picker_title" translatable="false">Select Mock Card</string>
</resources>

View file

@ -6,6 +6,7 @@ import com.tangem.data.card.sdk.CardSdkProvider
import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler
import com.tangem.features.feed.entry.deeplink.MarketsDeepLinkHandler
import com.tangem.features.feed.entry.deeplink.MarketsTokenDetailDeepLinkHandler
import com.tangem.features.feed.entry.deeplink.MarketsTokenExchangesDeepLinkHandler
import com.tangem.features.feed.entry.deeplink.NewsDetailsDeepLinkHandler
import com.tangem.features.onramp.deeplink.BuyDeepLinkHandler
import com.tangem.features.onramp.deeplink.OnrampDeepLinkHandler
@ -55,7 +56,7 @@ class DeepLinkFactoryTest {
every { create(any(), any()) } returns mockk()
}
private val marketsDeepLinkFactory = mockk<MarketsDeepLinkHandler.Factory>(relaxed = true) {
every { create() } returns mockk()
every { create(any()) } returns mockk()
}
private val marketsTokenDetailDeepLinkFactory = mockk<MarketsTokenDetailDeepLinkHandler.Factory>(relaxed = true) {
every { create(any(), any()) } returns mockk()
@ -86,6 +87,11 @@ class DeepLinkFactoryTest {
every { create(any(), any()) } returns mockk()
}
private val marketsTokenExchangesDeepLinkFactory =
mockk<MarketsTokenExchangesDeepLinkHandler.Factory>(relaxed = true) {
every { create(any(), any()) } returns mockk()
}
private val mockedUri = mockk<Uri>(relaxed = true)
private val isFromOnNewIntent: Boolean = false
@ -103,6 +109,7 @@ class DeepLinkFactoryTest {
stakingDeepLink = stakingDeepLinkFactory,
marketsDeepLink = marketsDeepLinkFactory,
marketsTokenDetailDeepLink = marketsTokenDetailDeepLinkFactory,
marketsTokenExchangesDeepLink = marketsTokenExchangesDeepLinkFactory,
buyDeepLink = buyDeepLinkFactory,
sellDeepLink = sellDeepLinkFactory,
swapDeepLink = swapDeepLinkFactory,
@ -308,7 +315,7 @@ class DeepLinkFactoryTest {
every { mockedUri.host } returns "markets"
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
advanceUntilIdle()
verify { marketsDeepLinkFactory.create() }
verify { marketsDeepLinkFactory.create(any()) }
// Test Sell
every { mockedUri.host } returns "sell"

View file

@ -10,6 +10,9 @@ import com.tangem.common.routing.entity.InitScreenLaunchMode
import com.tangem.core.decompose.navigation.Route
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.feedback.models.WalletMetaInfo
import com.tangem.domain.markets.PreselectedMarketsInterval
import com.tangem.domain.markets.PreselectedMarketsOrder
import com.tangem.domain.markets.PreselectedTokenDetailsSection
import com.tangem.domain.markets.TokenMarketParams
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountId
@ -146,6 +149,7 @@ sealed class AppRoute(val path: String) : Route {
STORIES,
SETTINGS,
ACCOUNT,
WALLET,
}
}
@ -252,7 +256,10 @@ sealed class AppRoute(val path: String) : Route {
) : AppRoute(path = "/wallet_hardware_backup/${userWalletId.stringValue}")
@Serializable
data object Markets : AppRoute(path = "/markets")
data class Markets(
val preselectedOrder: PreselectedMarketsOrder? = null,
val preselectedInterval: PreselectedMarketsInterval? = null,
) : AppRoute(path = "/markets")
@Serializable
data class MarketsTokenDetails(
@ -260,6 +267,9 @@ sealed class AppRoute(val path: String) : Route {
val appCurrency: AppCurrency,
val shouldShowPortfolio: Boolean,
val analyticsParams: AnalyticsParams? = null,
val preselectedSection: PreselectedTokenDetailsSection? = null,
val shouldOpenExchanges: Boolean = false,
val exchangesCount: Int? = null,
) : AppRoute(path = "/markets_token_details/${token.id}/$shouldShowPortfolio") {
@Serializable

View file

@ -60,6 +60,10 @@ sealed class DeepLinkRoute {
override val host: String = "promo"
}
data object TokenExchanges : DeepLinkRoute() {
override val host: String = "token_exchanges"
}
data object OnboardVisa : DeepLinkRoute() {
override val host: String = "onboard-visa"
}

View file

@ -15,4 +15,7 @@ object DeeplinkConst {
const val REF_KEY = "ref"
const val CAMPAIGN_KEY = "campaign"
const val NAME_KEY = "name"
const val ORDER_KEY = "order"
const val INTERVAL_KEY = "interval"
const val SECTION_KEY = "section"
}

View file

@ -47,7 +47,7 @@ fun StakingBalance.Data.getTotalStakingBalance(blockchainId: String): BigDecimal
* StakeKit-specific extension to get total balance including rewards.
*/
private fun StakingBalance.Data.StakeKit.getTotalWithRewardsStakingBalanceStakeKit(blockchainId: String): BigDecimal {
return if (BlockchainUtils.isIncludeStakingTotalBalance(blockchainId = blockchainId)) {
return if (BlockchainUtils.isIncludeStakingTotalBalance(networkId = blockchainId)) {
balance.items.sumOf { it.amount }
} else {
getRewardStakingBalance()
@ -58,7 +58,7 @@ private fun StakingBalance.Data.StakeKit.getTotalWithRewardsStakingBalanceStakeK
* StakeKit-specific extension to get total staked balance excluding rewards.
*/
private fun StakingBalance.Data.StakeKit.getTotalStakingBalanceStakeKit(blockchainId: String): BigDecimal {
return if (BlockchainUtils.isIncludeStakingTotalBalance(blockchainId = blockchainId)) {
return if (BlockchainUtils.isIncludeStakingTotalBalance(networkId = blockchainId)) {
balance.items
.filterNot { it.type == BalanceType.REWARDS }
.sumOf { it.amount }

View file

@ -55,8 +55,7 @@ class MockCryptoCurrencyFactory(private val userWallet: UserWallet.Cold = defaul
)
val network = Network(
id = Network.ID(blockchain.id, derivationPath),
backendId = blockchain.toNetworkId(),
id = Network.ID(value = blockchain.toNetworkId(), derivationPath = derivationPath),
name = blockchain.fullName,
isTestnet = blockchain.isTestnet(),
derivationPath = derivationPath,
@ -85,12 +84,11 @@ class MockCryptoCurrencyFactory(private val userWallet: UserWallet.Cold = defaul
return CryptoCurrency.Token(
id = CryptoCurrency.ID(
prefix = CryptoCurrency.ID.Prefix.TOKEN_PREFIX,
body = CryptoCurrency.ID.Body.NetworkId(blockchain.id),
body = CryptoCurrency.ID.Body.NetworkId(blockchain.toNetworkId()),
suffix = CryptoCurrency.ID.Suffix.RawID(blockchain.id),
),
network = Network(
id = Network.ID(value = blockchain.id, derivationPath),
backendId = "NEVER-MIND",
id = Network.ID(value = blockchain.toNetworkId(), derivationPath = derivationPath),
name = blockchain.fullName,
currencySymbol = "NEVER-MIND",
derivationPath = derivationPath,

View file

@ -1,6 +1,8 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
alias(deps.plugins.kotlin.kapt)
alias(deps.plugins.hilt.android)
id("configuration")
}
@ -12,13 +14,23 @@ dependencies {
/** Project - Core */
implementation(projects.core.ui)
implementation(projects.core.utils)
implementation(projects.core.navigation)
implementation(projects.core.analytics)
/** Project - Common */
implementation(projects.common.uiCharts)
implementation(projects.common.ui)
implementation(projects.common.routing)
/** Project - Domain */
implementation(projects.domain.models)
implementation(projects.domain.tokens)
implementation(projects.domain.tokens.models)
implementation(projects.domain.staking)
implementation(projects.domain.staking.models)
implementation(projects.domain.onramp.models)
implementation(projects.domain.offramp)
implementation(projects.domain.demo)
implementation(deps.lifecycle.compose)
implementation(deps.compose.foundation)
@ -26,4 +38,8 @@ dependencies {
implementation(deps.compose.ui.tooling)
implementation(deps.compose.ui.utils)
implementation(deps.kotlin.immutable.collections)
/** DI */
implementation(deps.hilt.android)
kapt(deps.hilt.kapt)
}

View file

@ -0,0 +1,11 @@
package com.tangem.common.ui.markets.action
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.tokens.model.TokenActionsState
data class CryptoCurrencyData(
val userWallet: UserWallet,
val status: CryptoCurrencyStatus,
val actions: List<TokenActionsState.ActionState>,
)

View file

@ -1,14 +1,14 @@
package com.tangem.features.feed.components.market.details.portfolio.impl.ui.state
package com.tangem.common.ui.markets.action
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Immutable
import com.tangem.common.ui.markets.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.features.feed.impl.R
@Immutable
internal sealed class QuickActionUM(
sealed class QuickActionUM(
val title: TextReference,
val description: TextReference,
@DrawableRes val icon: Int,

View file

@ -0,0 +1,9 @@
package com.tangem.common.ui.markets.action
import kotlinx.collections.immutable.ImmutableList
data class QuickActions(
val actions: ImmutableList<QuickActionUM>,
val onQuickActionClick: (QuickActionUM) -> Unit,
val onQuickActionLongClick: (QuickActionUM) -> Unit,
)

View file

@ -0,0 +1,61 @@
package com.tangem.common.ui.markets.action
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
import com.tangem.domain.tokens.model.TokenActionsState
import kotlinx.collections.immutable.toImmutableList
object QuickActionsConverter {
fun quickActions(cryptoData: CryptoCurrencyData, tokenActionsHandler: TokenActionsHandler): QuickActions {
return QuickActions(
actions = toQuickActions(cryptoData.actions),
onQuickActionClick = { quickActionUM ->
when (quickActionUM) {
QuickActionUM.Buy -> tokenActionsHandler.handle(
action = TokenActionsBSContentUM.Action.Buy,
cryptoCurrencyData = cryptoData,
)
is QuickActionUM.Exchange -> tokenActionsHandler.handle(
action = TokenActionsBSContentUM.Action.Exchange,
cryptoCurrencyData = cryptoData,
)
QuickActionUM.Receive -> tokenActionsHandler.handle(
action = TokenActionsBSContentUM.Action.Receive,
cryptoCurrencyData = cryptoData,
)
QuickActionUM.Stake -> tokenActionsHandler.handle(
action = TokenActionsBSContentUM.Action.Stake,
cryptoCurrencyData = cryptoData,
)
is QuickActionUM.YieldMode -> tokenActionsHandler.handle(
action = TokenActionsBSContentUM.Action.YieldMode,
cryptoCurrencyData = cryptoData,
)
}
},
onQuickActionLongClick = { actionUM ->
if (actionUM == QuickActionUM.Receive) {
tokenActionsHandler.handle(
action = TokenActionsBSContentUM.Action.CopyAddress,
cryptoCurrencyData = cryptoData,
)
}
},
)
}
fun toQuickActions(actions: List<TokenActionsState.ActionState>) = buildList {
actions.forEach { action ->
if (action.unavailabilityReason == ScenarioUnavailabilityReason.None) {
when (action) {
is TokenActionsState.ActionState.Buy -> QuickActionUM.Buy
is TokenActionsState.ActionState.Swap -> QuickActionUM.Exchange(action.shouldShowBadge)
is TokenActionsState.ActionState.Receive -> QuickActionUM.Receive
is TokenActionsState.ActionState.Stake -> QuickActionUM.Stake
is TokenActionsState.ActionState.YieldMode -> QuickActionUM.YieldMode(action.apy)
else -> null
}?.let(::add)
}
}
}.toImmutableList()
}

View file

@ -1,14 +1,14 @@
package com.tangem.features.feed.components.market.details.portfolio.impl.ui.state
package com.tangem.common.ui.markets.action
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Immutable
import com.tangem.common.ui.markets.R
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.features.feed.impl.R
import kotlinx.collections.immutable.ImmutableList
internal data class TokenActionsBSContentUM(
data class TokenActionsBSContentUM(
val title: String,
val actions: ImmutableList<Action>,
val onActionClick: (Action) -> Unit,

View file

@ -1,7 +1,8 @@
package com.tangem.features.feed.components.market.details.portfolio.impl.model
package com.tangem.common.ui.markets.action
import com.tangem.common.routing.AppRoute
import com.tangem.common.ui.bottomsheet.receive.mapToAddressModels
import com.tangem.common.ui.markets.R
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.event.OfframpAnalyticsEvent
@ -18,9 +19,6 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.offramp.GetOfframpUrlUseCase
import com.tangem.domain.onramp.model.OnrampSource
import com.tangem.domain.tokens.model.TokenActionsState
import com.tangem.features.feed.components.market.details.portfolio.impl.loader.PortfolioData
import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.TokenActionsBSContentUM
import com.tangem.features.feed.impl.R
import com.tangem.utils.Provider
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@ -28,7 +26,7 @@ import dagger.assisted.AssistedInject
import kotlinx.collections.immutable.toImmutableList
@Suppress("LongParameterList")
internal class TokenActionsHandler @AssistedInject constructor(
class TokenActionsHandler @AssistedInject constructor(
private val router: Router,
private val clipboardManager: ClipboardManager,
private val uiMessageSender: UiMessageSender,
@ -45,7 +43,7 @@ internal class TokenActionsHandler @AssistedInject constructor(
add(TokenActionsBSContentUM.Action.Sell)
}
fun handle(action: TokenActionsBSContentUM.Action, cryptoCurrencyData: PortfolioData.CryptoCurrencyData) {
fun handle(action: TokenActionsBSContentUM.Action, cryptoCurrencyData: CryptoCurrencyData) {
onHandleQuickAction(
HandledQuickAction(
action = action,
@ -79,13 +77,13 @@ internal class TokenActionsHandler @AssistedInject constructor(
}
private fun showDemoModeWarning() {
val message = DialogMessage(
val message = DialogMessage.Companion(
message = resourceReference(R.string.alert_demo_feature_disabled),
)
messageSender.send(message)
}
private fun onCopyAddress(cryptoCurrencyData: PortfolioData.CryptoCurrencyData) {
private fun onCopyAddress(cryptoCurrencyData: CryptoCurrencyData) {
val cryptoCurrencyStatus = cryptoCurrencyData.status
val networkAddress = cryptoCurrencyStatus.value.networkAddress ?: return
val addresses = networkAddress.availableAddresses
@ -97,7 +95,7 @@ internal class TokenActionsHandler @AssistedInject constructor(
uiMessageSender.send(SnackbarMessage(resourceReference(R.string.wallet_notification_address_copied)))
}
private fun onBuyClick(cryptoCurrencyData: PortfolioData.CryptoCurrencyData) {
private fun onBuyClick(cryptoCurrencyData: CryptoCurrencyData) {
router.push(
AppRoute.Onramp(
userWalletId = cryptoCurrencyData.userWallet.walletId,
@ -107,7 +105,7 @@ internal class TokenActionsHandler @AssistedInject constructor(
)
}
private fun onSellClick(cryptoCurrencyData: PortfolioData.CryptoCurrencyData) {
private fun onSellClick(cryptoCurrencyData: CryptoCurrencyData) {
getOfframpUrlUseCase(
cryptoCurrencyStatus = cryptoCurrencyData.status,
appCurrencyCode = currentAppCurrency().code,
@ -117,7 +115,7 @@ internal class TokenActionsHandler @AssistedInject constructor(
}
}
private fun onExchangeClick(cryptoCurrencyData: PortfolioData.CryptoCurrencyData) {
private fun onExchangeClick(cryptoCurrencyData: CryptoCurrencyData) {
router.push(
AppRoute.Swap(
currencyFrom = cryptoCurrencyData.status.currency,
@ -128,7 +126,7 @@ internal class TokenActionsHandler @AssistedInject constructor(
)
}
private fun onSendClick(cryptoCurrencyData: PortfolioData.CryptoCurrencyData) {
private fun onSendClick(cryptoCurrencyData: CryptoCurrencyData) {
val route = AppRoute.SendEntryPoint(
userWalletId = cryptoCurrencyData.userWallet.walletId,
currency = cryptoCurrencyData.status.currency,
@ -136,7 +134,7 @@ internal class TokenActionsHandler @AssistedInject constructor(
router.push(route)
}
private fun onStakeClick(cryptoCurrencyData: PortfolioData.CryptoCurrencyData) {
private fun onStakeClick(cryptoCurrencyData: CryptoCurrencyData) {
val option = cryptoCurrencyData.actions.firstOrNull { it is TokenActionsState.ActionState.Stake }
?.let { it as TokenActionsState.ActionState.Stake }
?.option ?: return
@ -150,7 +148,7 @@ internal class TokenActionsHandler @AssistedInject constructor(
)
}
private fun onYieldModeClick(cryptoCurrencyData: PortfolioData.CryptoCurrencyData) {
private fun onYieldModeClick(cryptoCurrencyData: CryptoCurrencyData) {
val yieldSupplyApy = cryptoCurrencyData.actions.filterIsInstance<TokenActionsState.ActionState.YieldMode>()
.firstOrNull()?.apy ?: return
@ -173,6 +171,6 @@ internal class TokenActionsHandler @AssistedInject constructor(
data class HandledQuickAction(
val action: TokenActionsBSContentUM.Action,
val cryptoCurrencyData: PortfolioData.CryptoCurrencyData,
val cryptoCurrencyData: CryptoCurrencyData,
)
}

View file

@ -8,6 +8,10 @@ android {
namespace = "com.tangem.common.ui"
}
tasks.withType<Test>().configureEach {
useJUnitPlatform()
}
dependencies {
api(projects.common)
@ -48,4 +52,8 @@ dependencies {
implementation(tangemDeps.blockchain) {
exclude(module = "joda-time")
}
/** Tests */
testImplementation(projects.test.core)
testRuntimeOnly(deps.test.junit5.engine)
}

View file

@ -27,6 +27,8 @@ class AccountCryptoPortfolioItemStateConverter(
private val priceChangeLce: Lce<Unit, PriceChange>? = null,
private val onItemClick: ((Account.CryptoPortfolio) -> Unit)? = null,
private val onItemLongClick: ((Account.CryptoPortfolio) -> Unit)? = null,
private val fiatAmountStateProvider: ((TotalFiatBalance) -> FiatAmountState?)? = null,
private val subtitle2StateProvider: ((Lce<Unit, PriceChange>) -> Subtitle2State?)? = null,
) : Converter<TotalFiatBalance, TokenItemState> {
override fun convert(value: TotalFiatBalance): TokenItemState {
@ -40,11 +42,11 @@ class AccountCryptoPortfolioItemStateConverter(
private fun Account.CryptoPortfolio.mapToContentState(
fiatBalance: TotalFiatBalance.Loaded,
): TokenItemState.Content {
val subtitle2State = priceChangeLce?.fold(
ifLoading = { priceChange -> priceChange?.toSubtitle2State() ?: Subtitle2State.Loading },
ifError = { null },
ifContent = { priceChange -> priceChange.toSubtitle2State() },
)
val subtitle2State = if (priceChangeLce != null && subtitle2StateProvider != null) {
subtitle2StateProvider(priceChangeLce)
} else {
createSubtitle2State(priceChangeLce)
}
return TokenItemState.Content(
id = account.accountId.toItemId(),
iconState = AccountIconItemStateConverter().convert(this),
@ -59,11 +61,8 @@ class AccountCryptoPortfolioItemStateConverter(
),
isAvailable = false,
),
fiatAmountState = FiatAmountState.Content(
text = fiatBalance.amount
.format { fiat(fiatCurrencyCode = appCurrency.code, fiatCurrencySymbol = appCurrency.symbol) },
isFlickering = fiatBalance.source == StatusSource.CACHE,
),
fiatAmountState = fiatAmountStateProvider?.invoke(fiatBalance)
?: createFiatAmountState(fiatBalance, appCurrency),
subtitle2State = subtitle2State,
onItemClick = onItemClick?.let { onItemClick -> { onItemClick(account) } },
onItemLongClick = onItemLongClick?.let { onItemLongClick -> { onItemLongClick(account) } },
@ -127,4 +126,32 @@ class AccountCryptoPortfolioItemStateConverter(
type = this.value.getPriceChangeType(),
isFlickering = this.source.isFlickering(),
)
private fun createSubtitle2State(priceChangeLce: Lce<Unit, PriceChange>?): Subtitle2State? {
return priceChangeLce?.fold(
ifLoading = { priceChange -> priceChange?.toSubtitle2State() ?: Subtitle2State.Loading },
ifError = { null },
ifContent = { priceChange -> priceChange.toSubtitle2State() },
)
}
companion object {
fun createFiatAmountState(fiatBalance: TotalFiatBalance, appCurrency: AppCurrency): FiatAmountState {
return when (fiatBalance) {
TotalFiatBalance.Failed,
TotalFiatBalance.Loading,
-> FiatAmountState.Empty
is TotalFiatBalance.Loaded -> FiatAmountState.Content(
text = fiatBalance.amount.format {
fiat(
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
)
},
isFlickering = fiatBalance.source == StatusSource.CACHE,
)
}
}
}
}

View file

@ -0,0 +1,311 @@
package com.tangem.common.ui.extensions
import androidx.annotation.DrawableRes
import com.tangem.blockchain.common.Blockchain
import com.tangem.core.ui.R
/**
* Holds icon resources for a single [Blockchain].
*
* @property active drawable for the active state
* @property greyedOut drawable for the disabled / greyed-out state
*/
private data class IconSet(
@DrawableRes val active: Int,
@DrawableRes val greyedOut: Int,
)
/**
* Returns the [IconSet] for the given [blockchain], or `null` if icons are not yet available.
*
* The `when` is exhaustive adding a new [Blockchain] entry in the SDK without handling it here
* will cause a compile-time error.
*/
@Suppress("CyclomaticComplexMethod", "LongMethod")
private fun iconSetOf(blockchain: Blockchain): IconSet? = when (blockchain) {
Blockchain.Alephium,
Blockchain.AlephiumTestnet,
-> IconSet(active = R.drawable.img_alephium_22, greyedOut = R.drawable.ic_alephium_22)
Blockchain.AlephZero,
Blockchain.AlephZeroTestnet,
-> IconSet(active = R.drawable.img_azero_22, greyedOut = R.drawable.ic_azero_22)
Blockchain.Algorand,
Blockchain.AlgorandTestnet,
-> IconSet(active = R.drawable.img_algorand_22, greyedOut = R.drawable.ic_algorand_22)
Blockchain.ApeChain,
Blockchain.ApeChainTestnet,
-> IconSet(active = R.drawable.img_apecoin_22, greyedOut = R.drawable.ic_apecoin_22)
Blockchain.Aptos,
Blockchain.AptosTestnet,
-> IconSet(active = R.drawable.img_aptos_22, greyedOut = R.drawable.ic_aptos_22)
Blockchain.Arbitrum,
Blockchain.ArbitrumTestnet,
-> IconSet(active = R.drawable.img_arbitrum_22, greyedOut = R.drawable.ic_arbitrum_22)
Blockchain.ArbitrumNova,
-> IconSet(active = R.drawable.img_arbitrum_nova_22, greyedOut = R.drawable.ic_arbitrum_nova_22)
Blockchain.Areon,
Blockchain.AreonTestnet,
-> IconSet(active = R.drawable.img_areon_22, greyedOut = R.drawable.ic_areon_22)
Blockchain.Aurora,
Blockchain.AuroraTestnet,
-> IconSet(active = R.drawable.img_aurora_22, greyedOut = R.drawable.ic_aurora_22)
Blockchain.Avalanche,
Blockchain.AvalancheTestnet,
-> IconSet(active = R.drawable.img_avalanche_22, greyedOut = R.drawable.ic_avalanche_22)
Blockchain.BSC,
Blockchain.BSCTestnet,
Blockchain.Binance,
Blockchain.BinanceTestnet,
-> IconSet(active = R.drawable.img_bsc_22, greyedOut = R.drawable.ic_bsc_16)
Blockchain.Base,
Blockchain.BaseTestnet,
-> IconSet(active = R.drawable.img_base_22, greyedOut = R.drawable.ic_base_22)
Blockchain.Bitcoin,
Blockchain.BitcoinTestnet,
-> IconSet(active = R.drawable.img_btc_22, greyedOut = R.drawable.ic_bitcoin_16)
Blockchain.BitcoinCash,
Blockchain.BitcoinCashTestnet,
-> IconSet(active = R.drawable.img_btc_cash_22, greyedOut = R.drawable.ic_bitcoin_cash_16)
Blockchain.Bitrock,
Blockchain.BitrockTestnet,
-> IconSet(active = R.drawable.img_bitrock_22, greyedOut = R.drawable.ic_bitrock_22)
Blockchain.Bittensor,
-> IconSet(active = R.drawable.img_bittensor_22, greyedOut = R.drawable.ic_bittensor_22)
Blockchain.Blast,
Blockchain.BlastTestnet,
-> IconSet(active = R.drawable.img_blast_22, greyedOut = R.drawable.ic_blast_22)
Blockchain.Canxium,
-> IconSet(active = R.drawable.img_canxium_22, greyedOut = R.drawable.ic_canxium_22)
Blockchain.Cardano,
-> IconSet(active = R.drawable.img_cardano_22, greyedOut = R.drawable.ic_cardano_16)
Blockchain.Casper,
Blockchain.CasperTestnet,
-> IconSet(active = R.drawable.img_casper_22, greyedOut = R.drawable.ic_casper_22)
Blockchain.Chia,
Blockchain.ChiaTestnet,
-> IconSet(active = R.drawable.img_chia_22, greyedOut = R.drawable.ic_chia_22)
Blockchain.Chiliz,
Blockchain.ChilizTestnet,
-> IconSet(active = R.drawable.img_chiliz_22, greyedOut = R.drawable.ic_chiliz_22)
Blockchain.Clore,
-> IconSet(active = R.drawable.img_clore_22, greyedOut = R.drawable.ic_clore_22)
Blockchain.Core,
Blockchain.CoreTestnet,
-> IconSet(active = R.drawable.img_core_22, greyedOut = R.drawable.ic_core_22)
Blockchain.Cosmos,
Blockchain.CosmosTestnet,
-> IconSet(active = R.drawable.img_cosmos_22, greyedOut = R.drawable.ic_cosmos_22)
Blockchain.Cronos,
-> IconSet(active = R.drawable.img_cronos_22, greyedOut = R.drawable.ic_cronos_22)
Blockchain.Cyber,
Blockchain.CyberTestnet,
-> IconSet(active = R.drawable.img_cyber_22, greyedOut = R.drawable.ic_cyber_22)
Blockchain.Dash,
-> IconSet(active = R.drawable.img_dash_22, greyedOut = R.drawable.ic_dash_22)
Blockchain.Decimal,
Blockchain.DecimalTestnet,
-> IconSet(active = R.drawable.img_decimal_22, greyedOut = R.drawable.ic_decimal_22)
Blockchain.Dischain,
-> IconSet(active = R.drawable.img_dischain_22, greyedOut = R.drawable.ic_dischain_22)
Blockchain.Dogecoin,
-> IconSet(active = R.drawable.img_dogecoin_22, greyedOut = R.drawable.ic_dogecoin_16)
Blockchain.Ducatus,
-> IconSet(active = R.drawable.img_ducatus_22, greyedOut = R.drawable.ic_ducatus_22)
Blockchain.EnergyWebChain,
Blockchain.EnergyWebChainTestnet,
Blockchain.EnergyWebX,
Blockchain.EnergyWebXTestnet,
-> IconSet(active = R.drawable.img_energy_web_22, greyedOut = R.drawable.ic_energy_web_22)
Blockchain.Ethereum,
Blockchain.EthereumTestnet,
-> IconSet(active = R.drawable.img_eth_22, greyedOut = R.drawable.ic_eth_16)
Blockchain.EthereumClassic,
Blockchain.EthereumClassicTestnet,
-> IconSet(active = R.drawable.img_eth_classic_22, greyedOut = R.drawable.ic_eth_16)
Blockchain.EthereumPow,
Blockchain.EthereumPowTestnet,
-> IconSet(active = R.drawable.img_eth_pow_22, greyedOut = R.drawable.ic_ethereumpow_22)
Blockchain.Fact0rn,
-> IconSet(active = R.drawable.img_fact0rn_22, greyedOut = R.drawable.ic_fact0rn_22)
Blockchain.Fantom,
Blockchain.FantomTestnet,
-> IconSet(active = R.drawable.img_fantom_22, greyedOut = R.drawable.ic_fantom_22)
Blockchain.Filecoin,
-> IconSet(active = R.drawable.img_filecoin_22, greyedOut = R.drawable.ic_filecoin_22)
Blockchain.Flare,
Blockchain.FlareTestnet,
-> IconSet(active = R.drawable.img_flare_22, greyedOut = R.drawable.ic_flare_22)
Blockchain.Gnosis,
-> IconSet(active = R.drawable.img_gnosis_22, greyedOut = R.drawable.ic_gnosis_22)
Blockchain.Hedera,
Blockchain.HederaTestnet,
-> IconSet(active = R.drawable.img_hedera_22, greyedOut = R.drawable.ic_hedera_22)
Blockchain.Hyperliquid,
Blockchain.HyperliquidTestnet,
-> IconSet(active = R.drawable.img_hyperliquid_22, greyedOut = R.drawable.ic_hyperliquid_22)
Blockchain.InternetComputer,
-> IconSet(active = R.drawable.img_icp_22, greyedOut = R.drawable.ic_icp_22)
Blockchain.Joystream,
-> IconSet(active = R.drawable.img_joystream_22, greyedOut = R.drawable.ic_joystream_22)
Blockchain.Kaspa,
Blockchain.KaspaTestnet,
-> IconSet(active = R.drawable.img_kaspa_22, greyedOut = R.drawable.ic_kaspa_22)
Blockchain.Kava,
Blockchain.KavaTestnet,
-> IconSet(active = R.drawable.img_kava_22, greyedOut = R.drawable.ic_kava_22)
Blockchain.Koinos,
Blockchain.KoinosTestnet,
-> IconSet(active = R.drawable.img_koinos_22, greyedOut = R.drawable.ic_koinos_22)
Blockchain.Kusama,
-> IconSet(active = R.drawable.img_kusama_22, greyedOut = R.drawable.ic_kusama_16)
Blockchain.Linea,
Blockchain.LineaTestnet,
-> IconSet(active = R.drawable.img_linea_22, greyedOut = R.drawable.ic_linea_22)
Blockchain.Litecoin,
-> IconSet(active = R.drawable.img_litecoin_22, greyedOut = R.drawable.ic_litecoin_22)
Blockchain.Manta,
Blockchain.MantaTestnet,
-> IconSet(active = R.drawable.img_manta_22, greyedOut = R.drawable.ic_manta_22)
Blockchain.Mantle,
Blockchain.MantleTestnet,
-> IconSet(active = R.drawable.img_mantle_22, greyedOut = R.drawable.ic_mantle_22)
Blockchain.Monad,
Blockchain.MonadTestnet,
-> IconSet(active = R.drawable.img_monad_22, greyedOut = R.drawable.ic_monad_22)
Blockchain.Moonbeam,
Blockchain.MoonbeamTestnet,
-> IconSet(active = R.drawable.img_moonbeam_22, greyedOut = R.drawable.ic_moonbeam_22)
Blockchain.Moonriver,
Blockchain.MoonriverTestnet,
-> IconSet(active = R.drawable.img_moonriver_22, greyedOut = R.drawable.ic_moonriver_22)
Blockchain.Near,
Blockchain.NearTestnet,
-> IconSet(active = R.drawable.img_near_22, greyedOut = R.drawable.ic_near_22)
Blockchain.OctaSpace,
Blockchain.OctaSpaceTestnet,
-> IconSet(active = R.drawable.img_octaspace_22, greyedOut = R.drawable.ic_octaspace_22)
Blockchain.OdysseyChain,
Blockchain.OdysseyChainTestnet,
-> IconSet(active = R.drawable.img_odyssey_chain_22, greyedOut = R.drawable.ic_odyssey_chain_22)
Blockchain.Optimism,
Blockchain.OptimismTestnet,
-> IconSet(active = R.drawable.img_optimism_22, greyedOut = R.drawable.ic_optimism_22)
Blockchain.Pepecoin,
Blockchain.PepecoinTestnet,
-> IconSet(active = R.drawable.img_pepecoin_22, greyedOut = R.drawable.ic_pepecoin_22)
Blockchain.Plasma,
Blockchain.PlasmaTestnet,
-> IconSet(active = R.drawable.img_plasma_22, greyedOut = R.drawable.ic_plasma_22)
Blockchain.Playa3ull,
-> IconSet(active = R.drawable.img_playa3ull_22, greyedOut = R.drawable.ic_playa3ull_22)
Blockchain.Polkadot,
Blockchain.PolkadotTestnet,
-> IconSet(active = R.drawable.img_polkadot_22, greyedOut = R.drawable.ic_polkadot_16)
Blockchain.Polygon,
Blockchain.PolygonTestnet,
-> IconSet(active = R.drawable.img_polygon_22, greyedOut = R.drawable.ic_polygon_22)
Blockchain.PolygonZkEVM,
Blockchain.PolygonZkEVMTestnet,
-> IconSet(active = R.drawable.img_polygon_22, greyedOut = R.drawable.ic_polygon_22)
Blockchain.PulseChain,
Blockchain.PulseChainTestnet,
-> IconSet(active = R.drawable.img_pls_22, greyedOut = R.drawable.ic_pls_22)
Blockchain.Quai,
Blockchain.QuaiTestnet,
-> IconSet(active = R.drawable.img_quai_22, greyedOut = R.drawable.ic_quai_22)
Blockchain.RSK,
-> IconSet(active = R.drawable.img_rsk_22, greyedOut = R.drawable.ic_rsk_16)
Blockchain.Radiant,
-> IconSet(active = R.drawable.img_radiant_22, greyedOut = R.drawable.ic_radiant_22)
Blockchain.Ravencoin,
Blockchain.RavencoinTestnet,
-> IconSet(active = R.drawable.img_ravencoin_22, greyedOut = R.drawable.ic_ravencoin_22)
Blockchain.Scroll,
Blockchain.ScrollTestnet,
-> IconSet(active = R.drawable.img_scroll_22, greyedOut = R.drawable.ic_scroll_22)
Blockchain.Sei,
Blockchain.SeiTestnet,
-> IconSet(active = R.drawable.img_sei_22, greyedOut = R.drawable.ic_sei_22)
Blockchain.Shibarium,
Blockchain.ShibariumTestnet,
-> IconSet(active = R.drawable.img_shibarium_22, greyedOut = R.drawable.ic_shibarium_22)
Blockchain.Solana,
Blockchain.SolanaTestnet,
-> IconSet(active = R.drawable.img_solana_22, greyedOut = R.drawable.ic_solana_16)
Blockchain.Sonic,
Blockchain.SonicTestnet,
-> IconSet(active = R.drawable.img_sonic_22, greyedOut = R.drawable.ic_sonic_22)
Blockchain.Stellar,
Blockchain.StellarTestnet,
-> IconSet(active = R.drawable.img_stellar_22, greyedOut = R.drawable.ic_stellar_16)
Blockchain.Sui,
Blockchain.SuiTestnet,
-> IconSet(active = R.drawable.img_sui_22, greyedOut = R.drawable.ic_sui_22)
Blockchain.TON,
Blockchain.TONTestnet,
-> IconSet(active = R.drawable.img_ton_22, greyedOut = R.drawable.ic_ton_22)
Blockchain.Taraxa,
Blockchain.TaraxaTestnet,
-> IconSet(active = R.drawable.img_taraxa_22, greyedOut = R.drawable.ic_taraxa_22)
Blockchain.Telos,
Blockchain.TelosTestnet,
-> IconSet(active = R.drawable.img_telos_22, greyedOut = R.drawable.ic_telos_22)
Blockchain.TerraV1,
-> IconSet(active = R.drawable.img_terra_22, greyedOut = R.drawable.ic_terra_22)
Blockchain.TerraV2,
-> IconSet(active = R.drawable.img_terra2_22, greyedOut = R.drawable.ic_terra2_22)
Blockchain.Tezos,
-> IconSet(active = R.drawable.img_tezos_22, greyedOut = R.drawable.ic_tezos_16)
Blockchain.Tron,
Blockchain.TronTestnet,
-> IconSet(active = R.drawable.img_tron_22, greyedOut = R.drawable.ic_tron_22)
Blockchain.VanarChain,
Blockchain.VanarChainTestnet,
-> IconSet(active = R.drawable.img_vanar_22, greyedOut = R.drawable.ic_vanar_22)
Blockchain.VeChain,
Blockchain.VeChainTestnet,
-> IconSet(active = R.drawable.img_vechain_22, greyedOut = R.drawable.ic_vechain_22)
Blockchain.XDC,
Blockchain.XDCTestnet,
-> IconSet(active = R.drawable.img_xdc_22, greyedOut = R.drawable.ic_xdc_22)
Blockchain.XRP,
-> IconSet(active = R.drawable.img_xrp_22, greyedOut = R.drawable.ic_xrp_22)
Blockchain.Xodex,
-> IconSet(active = R.drawable.img_xodex_22, greyedOut = R.drawable.ic_xodex_22)
Blockchain.ZkLinkNova,
Blockchain.ZkLinkNovaTestnet,
-> IconSet(active = R.drawable.img_zklink_22, greyedOut = R.drawable.ic_zklink_22)
Blockchain.ZkSyncEra,
Blockchain.ZkSyncEraTestnet,
-> IconSet(active = R.drawable.img_zksync_22, greyedOut = R.drawable.ic_zksync_22)
Blockchain.Nexa,
Blockchain.NexaTestnet,
Blockchain.Unknown,
-> null
}
private val ICONS_BY_BLOCKCHAIN: Map<Blockchain, IconSet> = Blockchain.entries
.mapNotNull { blockchain -> iconSetOf(blockchain)?.let { blockchain to it } }
.toMap()
/**
* Returns the active (colored) icon drawable resource for the given [blockchain].
*
* @param blockchain the blockchain to look up
* @param fallback drawable returned when [blockchain] has no icon defined
*/
@DrawableRes
fun getActiveIconRes(blockchain: Blockchain, @DrawableRes fallback: Int = R.drawable.ic_alert_24): Int {
return ICONS_BY_BLOCKCHAIN[blockchain]?.active ?: fallback
}
/**
* Returns the greyed-out (disabled) icon drawable resource for the given [blockchain].
*
* @param blockchain the blockchain to look up
* @param fallback drawable returned when [blockchain] has no icon defined
*/
@DrawableRes
fun getGreyedOutIconRes(blockchain: Blockchain, @DrawableRes fallback: Int = R.drawable.ic_alert_24): Int {
return ICONS_BY_BLOCKCHAIN[blockchain]?.greyedOut ?: fallback
}

View file

@ -1,8 +1,10 @@
package com.tangem.common.ui.navigationButtons
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.extensions.TextReference
@Immutable
sealed class NavigationButtonsState {
data object Empty : NavigationButtonsState()

View file

@ -8,7 +8,7 @@ import com.tangem.domain.models.tokenlist.TokenList
sealed interface TokenConverterParams {
/** Wallet mode; list of tokens for main account */
data class Wallet(
val mainAccount: AccountStatus,
val mainAccount: AccountStatus.CryptoPortfolio,
val tokenList: TokenList,
) : TokenConverterParams

View file

@ -0,0 +1,256 @@
package com.tangem.common.ui.extensions
import com.google.common.truth.Truth.assertThat
import com.tangem.blockchain.common.Blockchain
import com.tangem.core.ui.R
import com.tangem.test.core.ProvideTestModels
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.params.ParameterizedTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class BlockchainIconsTest {
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
inner class GetActiveIconRes {
@ParameterizedTest
@ProvideTestModels
fun returnsCorrectDrawable(model: TestModel) {
assertThat(getActiveIconRes(model.input)).isEqualTo(model.expected)
}
@Test
fun returnsAlertDrawableForUnknownId() {
assertThat(getActiveIconRes(Blockchain.Unknown)).isEqualTo(R.drawable.ic_alert_24)
}
@Suppress("CyclomaticComplexMethod", "LongMethod")
private fun provideTestModels() = Blockchain.entries.map { blockchain ->
val expected = when (blockchain) {
Blockchain.Alephium, Blockchain.AlephiumTestnet -> R.drawable.img_alephium_22
Blockchain.AlephZero, Blockchain.AlephZeroTestnet -> R.drawable.img_azero_22
Blockchain.Algorand, Blockchain.AlgorandTestnet -> R.drawable.img_algorand_22
Blockchain.ApeChain, Blockchain.ApeChainTestnet -> R.drawable.img_apecoin_22
Blockchain.Aptos, Blockchain.AptosTestnet -> R.drawable.img_aptos_22
Blockchain.Arbitrum, Blockchain.ArbitrumTestnet -> R.drawable.img_arbitrum_22
Blockchain.ArbitrumNova -> R.drawable.img_arbitrum_nova_22
Blockchain.Areon, Blockchain.AreonTestnet -> R.drawable.img_areon_22
Blockchain.Aurora, Blockchain.AuroraTestnet -> R.drawable.img_aurora_22
Blockchain.Avalanche, Blockchain.AvalancheTestnet -> R.drawable.img_avalanche_22
Blockchain.BSC, Blockchain.BSCTestnet,
Blockchain.Binance, Blockchain.BinanceTestnet,
-> R.drawable.img_bsc_22
Blockchain.Base, Blockchain.BaseTestnet -> R.drawable.img_base_22
Blockchain.Bitcoin, Blockchain.BitcoinTestnet -> R.drawable.img_btc_22
Blockchain.BitcoinCash, Blockchain.BitcoinCashTestnet -> R.drawable.img_btc_cash_22
Blockchain.Bitrock, Blockchain.BitrockTestnet -> R.drawable.img_bitrock_22
Blockchain.Bittensor -> R.drawable.img_bittensor_22
Blockchain.Blast, Blockchain.BlastTestnet -> R.drawable.img_blast_22
Blockchain.Canxium -> R.drawable.img_canxium_22
Blockchain.Cardano -> R.drawable.img_cardano_22
Blockchain.Casper, Blockchain.CasperTestnet -> R.drawable.img_casper_22
Blockchain.Chia, Blockchain.ChiaTestnet -> R.drawable.img_chia_22
Blockchain.Chiliz, Blockchain.ChilizTestnet -> R.drawable.img_chiliz_22
Blockchain.Clore -> R.drawable.img_clore_22
Blockchain.Core, Blockchain.CoreTestnet -> R.drawable.img_core_22
Blockchain.Cosmos, Blockchain.CosmosTestnet -> R.drawable.img_cosmos_22
Blockchain.Cronos -> R.drawable.img_cronos_22
Blockchain.Cyber, Blockchain.CyberTestnet -> R.drawable.img_cyber_22
Blockchain.Dash -> R.drawable.img_dash_22
Blockchain.Decimal, Blockchain.DecimalTestnet -> R.drawable.img_decimal_22
Blockchain.Dischain -> R.drawable.img_dischain_22
Blockchain.Dogecoin -> R.drawable.img_dogecoin_22
Blockchain.Ducatus -> R.drawable.img_ducatus_22
Blockchain.EnergyWebChain, Blockchain.EnergyWebChainTestnet,
Blockchain.EnergyWebX, Blockchain.EnergyWebXTestnet,
-> R.drawable.img_energy_web_22
Blockchain.Ethereum, Blockchain.EthereumTestnet -> R.drawable.img_eth_22
Blockchain.EthereumClassic, Blockchain.EthereumClassicTestnet -> R.drawable.img_eth_classic_22
Blockchain.EthereumPow, Blockchain.EthereumPowTestnet -> R.drawable.img_eth_pow_22
Blockchain.Fact0rn -> R.drawable.img_fact0rn_22
Blockchain.Fantom, Blockchain.FantomTestnet -> R.drawable.img_fantom_22
Blockchain.Filecoin -> R.drawable.img_filecoin_22
Blockchain.Flare, Blockchain.FlareTestnet -> R.drawable.img_flare_22
Blockchain.Gnosis -> R.drawable.img_gnosis_22
Blockchain.Hedera, Blockchain.HederaTestnet -> R.drawable.img_hedera_22
Blockchain.Hyperliquid, Blockchain.HyperliquidTestnet -> R.drawable.img_hyperliquid_22
Blockchain.InternetComputer -> R.drawable.img_icp_22
Blockchain.Joystream -> R.drawable.img_joystream_22
Blockchain.Kaspa, Blockchain.KaspaTestnet -> R.drawable.img_kaspa_22
Blockchain.Kava, Blockchain.KavaTestnet -> R.drawable.img_kava_22
Blockchain.Koinos, Blockchain.KoinosTestnet -> R.drawable.img_koinos_22
Blockchain.Kusama -> R.drawable.img_kusama_22
Blockchain.Linea, Blockchain.LineaTestnet -> R.drawable.img_linea_22
Blockchain.Litecoin -> R.drawable.img_litecoin_22
Blockchain.Manta, Blockchain.MantaTestnet -> R.drawable.img_manta_22
Blockchain.Mantle, Blockchain.MantleTestnet -> R.drawable.img_mantle_22
Blockchain.Monad, Blockchain.MonadTestnet -> R.drawable.img_monad_22
Blockchain.Moonbeam, Blockchain.MoonbeamTestnet -> R.drawable.img_moonbeam_22
Blockchain.Moonriver, Blockchain.MoonriverTestnet -> R.drawable.img_moonriver_22
Blockchain.Near, Blockchain.NearTestnet -> R.drawable.img_near_22
Blockchain.OctaSpace, Blockchain.OctaSpaceTestnet -> R.drawable.img_octaspace_22
Blockchain.OdysseyChain, Blockchain.OdysseyChainTestnet -> R.drawable.img_odyssey_chain_22
Blockchain.Optimism, Blockchain.OptimismTestnet -> R.drawable.img_optimism_22
Blockchain.Pepecoin, Blockchain.PepecoinTestnet -> R.drawable.img_pepecoin_22
Blockchain.Plasma, Blockchain.PlasmaTestnet -> R.drawable.img_plasma_22
Blockchain.Playa3ull -> R.drawable.img_playa3ull_22
Blockchain.Polkadot, Blockchain.PolkadotTestnet -> R.drawable.img_polkadot_22
Blockchain.Polygon, Blockchain.PolygonTestnet -> R.drawable.img_polygon_22
Blockchain.PolygonZkEVM, Blockchain.PolygonZkEVMTestnet -> R.drawable.img_polygon_22
Blockchain.PulseChain, Blockchain.PulseChainTestnet -> R.drawable.img_pls_22
Blockchain.Quai, Blockchain.QuaiTestnet -> R.drawable.img_quai_22
Blockchain.RSK -> R.drawable.img_rsk_22
Blockchain.Radiant -> R.drawable.img_radiant_22
Blockchain.Ravencoin, Blockchain.RavencoinTestnet -> R.drawable.img_ravencoin_22
Blockchain.Scroll, Blockchain.ScrollTestnet -> R.drawable.img_scroll_22
Blockchain.Sei, Blockchain.SeiTestnet -> R.drawable.img_sei_22
Blockchain.Shibarium, Blockchain.ShibariumTestnet -> R.drawable.img_shibarium_22
Blockchain.Solana, Blockchain.SolanaTestnet -> R.drawable.img_solana_22
Blockchain.Sonic, Blockchain.SonicTestnet -> R.drawable.img_sonic_22
Blockchain.Stellar, Blockchain.StellarTestnet -> R.drawable.img_stellar_22
Blockchain.Sui, Blockchain.SuiTestnet -> R.drawable.img_sui_22
Blockchain.TON, Blockchain.TONTestnet -> R.drawable.img_ton_22
Blockchain.Taraxa, Blockchain.TaraxaTestnet -> R.drawable.img_taraxa_22
Blockchain.Telos, Blockchain.TelosTestnet -> R.drawable.img_telos_22
Blockchain.TerraV1 -> R.drawable.img_terra_22
Blockchain.TerraV2 -> R.drawable.img_terra2_22
Blockchain.Tezos -> R.drawable.img_tezos_22
Blockchain.Tron, Blockchain.TronTestnet -> R.drawable.img_tron_22
Blockchain.VanarChain, Blockchain.VanarChainTestnet -> R.drawable.img_vanar_22
Blockchain.VeChain, Blockchain.VeChainTestnet -> R.drawable.img_vechain_22
Blockchain.XDC, Blockchain.XDCTestnet -> R.drawable.img_xdc_22
Blockchain.XRP -> R.drawable.img_xrp_22
Blockchain.Xodex -> R.drawable.img_xodex_22
Blockchain.ZkLinkNova, Blockchain.ZkLinkNovaTestnet -> R.drawable.img_zklink_22
Blockchain.ZkSyncEra, Blockchain.ZkSyncEraTestnet -> R.drawable.img_zksync_22
Blockchain.Nexa, Blockchain.NexaTestnet, Blockchain.Unknown -> R.drawable.ic_alert_24
}
TestModel(blockchain, expected)
}
}
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
inner class GetGreyedOutIconRes {
@ParameterizedTest
@ProvideTestModels
fun returnsCorrectDrawable(model: TestModel) {
assertThat(getGreyedOutIconRes(model.input)).isEqualTo(model.expected)
}
@Test
fun returnsAlertDrawableForUnknownId() {
assertThat(getGreyedOutIconRes(Blockchain.Unknown)).isEqualTo(R.drawable.ic_alert_24)
}
@Suppress("CyclomaticComplexMethod", "LongMethod")
private fun provideTestModels() = Blockchain.entries.map { blockchain ->
val expected = when (blockchain) {
Blockchain.Alephium, Blockchain.AlephiumTestnet -> R.drawable.ic_alephium_22
Blockchain.AlephZero, Blockchain.AlephZeroTestnet -> R.drawable.ic_azero_22
Blockchain.Algorand, Blockchain.AlgorandTestnet -> R.drawable.ic_algorand_22
Blockchain.ApeChain, Blockchain.ApeChainTestnet -> R.drawable.ic_apecoin_22
Blockchain.Aptos, Blockchain.AptosTestnet -> R.drawable.ic_aptos_22
Blockchain.Arbitrum, Blockchain.ArbitrumTestnet -> R.drawable.ic_arbitrum_22
Blockchain.ArbitrumNova -> R.drawable.ic_arbitrum_nova_22
Blockchain.Areon, Blockchain.AreonTestnet -> R.drawable.ic_areon_22
Blockchain.Aurora, Blockchain.AuroraTestnet -> R.drawable.ic_aurora_22
Blockchain.Avalanche, Blockchain.AvalancheTestnet -> R.drawable.ic_avalanche_22
Blockchain.BSC, Blockchain.BSCTestnet,
Blockchain.Binance, Blockchain.BinanceTestnet,
-> R.drawable.ic_bsc_16
Blockchain.Base, Blockchain.BaseTestnet -> R.drawable.ic_base_22
Blockchain.Bitcoin, Blockchain.BitcoinTestnet -> R.drawable.ic_bitcoin_16
Blockchain.BitcoinCash, Blockchain.BitcoinCashTestnet -> R.drawable.ic_bitcoin_cash_16
Blockchain.Bitrock, Blockchain.BitrockTestnet -> R.drawable.ic_bitrock_22
Blockchain.Bittensor -> R.drawable.ic_bittensor_22
Blockchain.Blast, Blockchain.BlastTestnet -> R.drawable.ic_blast_22
Blockchain.Canxium -> R.drawable.ic_canxium_22
Blockchain.Cardano -> R.drawable.ic_cardano_16
Blockchain.Casper, Blockchain.CasperTestnet -> R.drawable.ic_casper_22
Blockchain.Chia, Blockchain.ChiaTestnet -> R.drawable.ic_chia_22
Blockchain.Chiliz, Blockchain.ChilizTestnet -> R.drawable.ic_chiliz_22
Blockchain.Clore -> R.drawable.ic_clore_22
Blockchain.Core, Blockchain.CoreTestnet -> R.drawable.ic_core_22
Blockchain.Cosmos, Blockchain.CosmosTestnet -> R.drawable.ic_cosmos_22
Blockchain.Cronos -> R.drawable.ic_cronos_22
Blockchain.Cyber, Blockchain.CyberTestnet -> R.drawable.ic_cyber_22
Blockchain.Dash -> R.drawable.ic_dash_22
Blockchain.Decimal, Blockchain.DecimalTestnet -> R.drawable.ic_decimal_22
Blockchain.Dischain -> R.drawable.ic_dischain_22
Blockchain.Dogecoin -> R.drawable.ic_dogecoin_16
Blockchain.Ducatus -> R.drawable.ic_ducatus_22
Blockchain.EnergyWebChain, Blockchain.EnergyWebChainTestnet,
Blockchain.EnergyWebX, Blockchain.EnergyWebXTestnet,
-> R.drawable.ic_energy_web_22
Blockchain.Ethereum, Blockchain.EthereumTestnet -> R.drawable.ic_eth_16
Blockchain.EthereumClassic, Blockchain.EthereumClassicTestnet -> R.drawable.ic_eth_16
Blockchain.EthereumPow, Blockchain.EthereumPowTestnet -> R.drawable.ic_ethereumpow_22
Blockchain.Fact0rn -> R.drawable.ic_fact0rn_22
Blockchain.Fantom, Blockchain.FantomTestnet -> R.drawable.ic_fantom_22
Blockchain.Filecoin -> R.drawable.ic_filecoin_22
Blockchain.Flare, Blockchain.FlareTestnet -> R.drawable.ic_flare_22
Blockchain.Gnosis -> R.drawable.ic_gnosis_22
Blockchain.Hedera, Blockchain.HederaTestnet -> R.drawable.ic_hedera_22
Blockchain.Hyperliquid, Blockchain.HyperliquidTestnet -> R.drawable.ic_hyperliquid_22
Blockchain.InternetComputer -> R.drawable.ic_icp_22
Blockchain.Joystream -> R.drawable.ic_joystream_22
Blockchain.Kaspa, Blockchain.KaspaTestnet -> R.drawable.ic_kaspa_22
Blockchain.Kava, Blockchain.KavaTestnet -> R.drawable.ic_kava_22
Blockchain.Koinos, Blockchain.KoinosTestnet -> R.drawable.ic_koinos_22
Blockchain.Kusama -> R.drawable.ic_kusama_16
Blockchain.Linea, Blockchain.LineaTestnet -> R.drawable.ic_linea_22
Blockchain.Litecoin -> R.drawable.ic_litecoin_22
Blockchain.Manta, Blockchain.MantaTestnet -> R.drawable.ic_manta_22
Blockchain.Mantle, Blockchain.MantleTestnet -> R.drawable.ic_mantle_22
Blockchain.Monad, Blockchain.MonadTestnet -> R.drawable.ic_monad_22
Blockchain.Moonbeam, Blockchain.MoonbeamTestnet -> R.drawable.ic_moonbeam_22
Blockchain.Moonriver, Blockchain.MoonriverTestnet -> R.drawable.ic_moonriver_22
Blockchain.Near, Blockchain.NearTestnet -> R.drawable.ic_near_22
Blockchain.OctaSpace, Blockchain.OctaSpaceTestnet -> R.drawable.ic_octaspace_22
Blockchain.OdysseyChain, Blockchain.OdysseyChainTestnet -> R.drawable.ic_odyssey_chain_22
Blockchain.Optimism, Blockchain.OptimismTestnet -> R.drawable.ic_optimism_22
Blockchain.Pepecoin, Blockchain.PepecoinTestnet -> R.drawable.ic_pepecoin_22
Blockchain.Plasma, Blockchain.PlasmaTestnet -> R.drawable.ic_plasma_22
Blockchain.Playa3ull -> R.drawable.ic_playa3ull_22
Blockchain.Polkadot, Blockchain.PolkadotTestnet -> R.drawable.ic_polkadot_16
Blockchain.Polygon, Blockchain.PolygonTestnet -> R.drawable.ic_polygon_22
Blockchain.PolygonZkEVM, Blockchain.PolygonZkEVMTestnet -> R.drawable.ic_polygon_22
Blockchain.PulseChain, Blockchain.PulseChainTestnet -> R.drawable.ic_pls_22
Blockchain.Quai, Blockchain.QuaiTestnet -> R.drawable.ic_quai_22
Blockchain.RSK -> R.drawable.ic_rsk_16
Blockchain.Radiant -> R.drawable.ic_radiant_22
Blockchain.Ravencoin, Blockchain.RavencoinTestnet -> R.drawable.ic_ravencoin_22
Blockchain.Scroll, Blockchain.ScrollTestnet -> R.drawable.ic_scroll_22
Blockchain.Sei, Blockchain.SeiTestnet -> R.drawable.ic_sei_22
Blockchain.Shibarium, Blockchain.ShibariumTestnet -> R.drawable.ic_shibarium_22
Blockchain.Solana, Blockchain.SolanaTestnet -> R.drawable.ic_solana_16
Blockchain.Sonic, Blockchain.SonicTestnet -> R.drawable.ic_sonic_22
Blockchain.Stellar, Blockchain.StellarTestnet -> R.drawable.ic_stellar_16
Blockchain.Sui, Blockchain.SuiTestnet -> R.drawable.ic_sui_22
Blockchain.TON, Blockchain.TONTestnet -> R.drawable.ic_ton_22
Blockchain.Taraxa, Blockchain.TaraxaTestnet -> R.drawable.ic_taraxa_22
Blockchain.Telos, Blockchain.TelosTestnet -> R.drawable.ic_telos_22
Blockchain.TerraV1 -> R.drawable.ic_terra_22
Blockchain.TerraV2 -> R.drawable.ic_terra2_22
Blockchain.Tezos -> R.drawable.ic_tezos_16
Blockchain.Tron, Blockchain.TronTestnet -> R.drawable.ic_tron_22
Blockchain.VanarChain, Blockchain.VanarChainTestnet -> R.drawable.ic_vanar_22
Blockchain.VeChain, Blockchain.VeChainTestnet -> R.drawable.ic_vechain_22
Blockchain.XDC, Blockchain.XDCTestnet -> R.drawable.ic_xdc_22
Blockchain.XRP -> R.drawable.ic_xrp_22
Blockchain.Xodex -> R.drawable.ic_xodex_22
Blockchain.ZkLinkNova, Blockchain.ZkLinkNovaTestnet -> R.drawable.ic_zklink_22
Blockchain.ZkSyncEra, Blockchain.ZkSyncEraTestnet -> R.drawable.ic_zksync_22
Blockchain.Nexa, Blockchain.NexaTestnet, Blockchain.Unknown -> R.drawable.ic_alert_24
}
TestModel(blockchain, expected)
}
}
data class TestModel(val input: Blockchain, val expected: Int)
}

View file

@ -99,6 +99,7 @@ sealed class AnalyticsParam {
data object NewsPage : ScreensSources("News Page")
data object Portfolio : ScreensSources("Portfolio")
data object Staking : ScreensSources("Staking")
data object Earn : ScreensSources("Earn")
}
sealed class TxSentFrom(val value: String) {

View file

@ -15,24 +15,8 @@
"name": "vanar-chain",
"version": "undefined"
},
{
"name": "sonic",
"version": "5.21.0"
},
{
"name": "apechain",
"version": "5.21.0"
},
{
"name": "alephium",
"version": "5.21.0"
},
{
"name": "zklink",
"version": "undefined"
},
{
"name": "plasma",
"version": "5.31"
}
]

View file

@ -20,30 +20,10 @@
"name": "SWAP_REDESIGN_ENABLED",
"version": "undefined"
},
{
"name": "HOT_WALLET_CREATION_RESTRICTION_ENABLED",
"version": "5.32.0"
},
{
"name": "APP_REDESIGN_ENABLED",
"version": "undefined"
},
{
"name": "SWAP_MARKET_LIST_ENABLED",
"version": "5.34"
},
{
"name": "EARN_BLOCK_ENABLED",
"version": "5.35"
},
{
"name": "HOLD_TO_CONFIRM_BUTTON_ENABLED",
"version": "5.35"
},
{
"name": "WALLET_REORDER_FEATURE_ENABLED",
"version": "5.34"
},
{
"name": "GASLESS_APPROVAL_ENABLED",
"version": "5.37"
@ -56,14 +36,6 @@
"name": "DYNAMIC_ADDRESSES_ENABLED",
"version": "undefined"
},
{
"name": "CUSTOMER_IO_ENABLED",
"version": "5.35"
},
{
"name": "MAIN_SCREEN_QR_SCANNING_ENABLED",
"version": "5.36"
},
{
"name": "NEW_PROMO_BANNERS_ENABLED",
"version": "5.37"
@ -73,7 +45,7 @@
"version": "undefined"
},
{
"name": "TOKEN_SYNC_ENABLED",
"name": "ASSETS_DISCOVERY_ENABLED",
"version": "undefined"
},
{
@ -87,5 +59,13 @@
{
"name": "HEDERA_ERC20_ENABLED",
"version": "5.37"
},
{
"name": "ADD_AND_MANAGE_TOKENS_ENABLED",
"version": "undefined"
},
{
"name": "WALLET_CONNECT_BITCOIN_ENABLED",
"version": "undefined"
}
]

View file

@ -85,6 +85,18 @@ interface TangemPayApi {
@Body body: FreezeUnfreezeCardRequest,
): ApiResponse<FreezeUnfreezeCardResponse>
@GET("v1/fees/{type}")
suspend fun getFee(
@Header("Authorization") authHeader: String,
@Path("type") type: String,
): ApiResponse<FeeResponse>
@POST("v1/customer/card/reissue")
suspend fun reissueCard(
@Header("Authorization") authHeader: String,
@Body body: ReissueCardRequest,
): ApiResponse<ReissueCardResponse>
@POST("v1/customer/card/withdraw/data")
suspend fun getWithdrawData(
@Header("Authorization") authHeader: String,
@ -96,4 +108,10 @@ interface TangemPayApi {
@Header("Authorization") authHeader: String,
@Body body: WithdrawRequest,
): ApiResponse<WithdrawResponse>
@PATCH("v1/card")
suspend fun updateCardDisplayName(
@Header("Authorization") authHeader: String,
@Body body: UpdateCardDisplayNameRequest,
): ApiResponse<UpdateCardDisplayNameResponse>
}

View file

@ -0,0 +1,9 @@
package com.tangem.datasource.api.pay.models.request
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class ReissueCardRequest(
@Json(name = "card_id") val cardId: String,
)

View file

@ -0,0 +1,9 @@
package com.tangem.datasource.api.pay.models.request
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class UpdateCardDisplayNameRequest(
@Json(name = "display_name") val displayName: String,
)

View file

@ -29,6 +29,7 @@ data class CustomerMeResponse(
@Json(name = "status") val status: Status,
@Json(name = "updated_at") val updatedAt: String,
@Json(name = "payment_account_id") val paymentAccountId: String,
@Json(name = "display_name") val displayName: String?,
) {
@JsonClass(generateAdapter = false)
enum class Status {

View file

@ -0,0 +1,17 @@
package com.tangem.datasource.api.pay.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class FeeResponse(
@Json(name = "result") val result: Result,
) {
@JsonClass(generateAdapter = true)
data class Result(
@Json(name = "type") val type: String,
@Json(name = "amount") val amount: String,
@Json(name = "currency") val currency: String,
@Json(name = "description") val description: String,
)
}

View file

@ -0,0 +1,15 @@
package com.tangem.datasource.api.pay.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class ReissueCardResponse(
@Json(name = "result") val result: Result,
) {
@JsonClass(generateAdapter = true)
data class Result(
@Json(name = "order_id") val orderId: String,
@Json(name = "status") val status: String,
)
}

View file

@ -0,0 +1,14 @@
package com.tangem.datasource.api.pay.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class UpdateCardDisplayNameResponse(
@Json(name = "result") val result: Result?,
) {
@JsonClass(generateAdapter = true)
data class Result(
@Json(name = "display_name") val displayName: String?,
)
}

View file

@ -42,7 +42,8 @@ data class UserTokensResponse(
return otherToken.contractAddress == this.contractAddress &&
otherToken.networkId == this.networkId &&
otherToken.derivationPath == this.derivationPath &&
otherToken.decimals == this.decimals
otherToken.decimals == this.decimals &&
otherToken.dynamicAddressesEnabled == this.dynamicAddressesEnabled
}
override fun hashCode(): Int = calculateHashCode(
@ -50,6 +51,7 @@ data class UserTokensResponse(
networkId.hashCode(),
derivationPath.hashCode(),
decimals.hashCode(),
dynamicAddressesEnabled.hashCode(),
)
}

View file

@ -2,7 +2,9 @@ package com.tangem.datasource.di
import com.tangem.datasource.local.datastore.RuntimeDataStore
import com.tangem.datasource.local.visa.DefaultTangemPayCardFrozenStateStore
import com.tangem.datasource.local.visa.DefaultTangemPayReissueCardStore
import com.tangem.datasource.local.visa.TangemPayCardFrozenStateStore
import com.tangem.datasource.local.visa.TangemPayReissueCardStore
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -20,4 +22,12 @@ internal object TangemPayStoresModule {
dataStore = RuntimeDataStore(),
)
}
@Provides
@Singleton
fun provideTangemPayReissueCardStore(): TangemPayReissueCardStore {
return DefaultTangemPayReissueCardStore(
feeStore = RuntimeDataStore(),
)
}
}

View file

@ -34,6 +34,7 @@ sealed interface NetworkStatusDM {
* @property selectedAddress selected address
* @property availableAddresses available addresses
* @property amounts amounts
* @property yieldSupplyStatuses yield supply statuses
*/
@NameLabel("amounts")
data class Verified(
@ -65,6 +66,11 @@ sealed interface NetworkStatusDM {
@Json(name = "error_message") val errorMessage: String,
) : NetworkStatusDM
/**
* Id
*
* @property value blockchain id [com.tangem.blockchain.common.Blockchain.id]
*/
@JsonClass(generateAdapter = true)
data class ID(
@Json(name = "value") val value: String,

View file

@ -16,6 +16,7 @@ import com.tangem.datasource.local.preferences.PreferencesKeys.IS_WALLET_SWAP_PR
import com.tangem.datasource.local.preferences.PreferencesKeys.SHOULD_SHOW_RING_PROMO_KEY
import com.tangem.datasource.local.preferences.utils.CleanupKeyMigration
import com.tangem.datasource.local.preferences.utils.SharedPreferencesKeyMigration
import com.tangem.datasource.local.preferences.utils.SwapCurrencyIdMigration
import com.tangem.utils.coroutines.AppCoroutineScope
import com.tangem.utils.logging.TangemLogger
@ -80,6 +81,7 @@ internal object PreferencesDataStore {
legacyKeyName = LEGACY_DEFAULT_KEY_NAME,
keyName = PreferencesKeys.BALANCE_HIDING_SETTINGS_KEY.name,
),
SwapCurrencyIdMigration(),
CleanupKeyMigration(key = APP_LOGS_KEY),
CleanupKeyMigration(key = IS_WALLET_SWAP_PROMO_OKX_SHOW_KEY),
CleanupKeyMigration(key = IS_TOKEN_SWAP_PROMO_OKX_SHOW_KEY),

Some files were not shown because too many files have changed in this diff Show more