From 48b6dff5bd065cf829d1ef74b6e7c03ca1e7ff03 Mon Sep 17 00:00:00 2001 From: 0xh3rman <119309671+0xh3rman@users.noreply.github.com> Date: Fri, 14 Aug 2026 19:29:32 +0900 Subject: [PATCH 1/4] Disable Sei Cosmos chain --- .../chains/icons/{sei.svg => seievm.svg} | 0 .../com/gemwallet/android/MainViewModel.kt | 8 +- .../android/services/CheckAccountsService.kt | 3 +- .../android/MainViewModelAuthStateTest.kt | 2 + .../services/CheckAccountsServiceTest.kt | 11 +- .../operators/TestCreateAccountOperator.kt | 4 +- .../operators/gemstone/GemMappers.kt | 3 +- .../data/coordinators/di/WalletModule.kt | 14 +- .../coordinators/wallet/DeleteWalletImpl.kt | 40 +- .../wallet/DeleteWalletImplTest.kt | 29 +- .../repositories/assets/AssetsRepository.kt | 3 +- .../chains/ChainInfoRepository.kt | 3 +- .../repositories/tokens/TokensRepository.kt | 2 +- .../repositories/tokens/WalletSearchTokens.kt | 2 +- .../repositories/wallets/WalletsRepository.kt | 2 + .../wallets/WalletsRepositoryImpl.kt | 7 +- .../assets/AssetsRepositoryTest.kt | 17 +- .../86.json | 2702 +++++++++++++++++ .../android/data/service/store}/LocalStore.kt | 14 +- .../service/store/database/ChainConverters.kt | 3 +- .../service/store/database/GemDatabase.kt | 2 +- .../data/service/store/database/WalletsDao.kt | 9 +- .../store/database/di/DatabaseModule.kt | 6 +- .../store/database/di/Migration_63_64.kt | 5 +- .../store/database/di/Migration_85_86.kt | 76 + .../store/database/di/Migration_85_86Test.kt | 32 + .../viewmodels/NetworkAssetsViewModel.kt | 2 +- .../presents/ContactChainSelectScene.kt | 2 +- .../presents/ValidatorsScene.kt | 8 +- .../viewmodels/build.gradle.kts | 3 +- .../wallet/viewmodels/WalletAvatarService.kt | 3 +- .../wallets/viewmodels/WalletsViewModel.kt | 2 +- .../wallet/coordinators/DeleteWallet.kt | 8 +- .../domains/asset/IconUrlGeneration.kt | 10 +- .../com/gemwallet/android/ext/AssetId.kt | 4 +- .../kotlin/com/gemwallet/android/ext/Chain.kt | 43 +- .../com/gemwallet/android/ext/StakeChain.kt | 4 +- .../wallet/core/primitives/TransactionId.kt | 3 +- .../wallet/core/primitives/generated/Chain.kt | 2 - .../core/primitives/generated/ChainCosmos.kt | 2 - .../core/primitives/generated/ChainStake.kt | 2 - .../com/gemwallet/android/ext/ChainExtTest.kt | 2 +- .../android/testkit/GemstoneTestKeys.kt | 3 +- .../ui/components/list_item/ValidatorItem.kt | 4 +- core/apps/daemon/src/parser/mod.rs | 11 +- core/apps/dynode/src/config/mod.rs | 1 + .../src/mnemonic/tests/derivation.rs | 2 +- .../gem_evm/src/uniswap/deployment/v3.rs | 2 +- .../gem_evm/src/uniswap/deployment/v4.rs | 2 +- core/crates/primitives/src/chain.rs | 19 +- core/crates/primitives/src/chain_config.rs | 8 + core/crates/primitives/src/chain_cosmos.rs | 1 + core/crates/primitives/src/chain_stake.rs | 1 + core/crates/settings_chain/src/node_check.rs | 2 +- .../AppService/OnstartService.swift | 6 + .../Tests/BalanceServiceTests.swift | 24 +- .../Tests/WalletServiceTests.swift | 17 + .../WalletService/WalletService.swift | 6 + .../GemStakeChain+GemstonePrimitives.swift | 1 - .../Keystore/Tests/LocalKeystoreTests.swift | 2 - .../Extensions/CosmosChain+Primitives.swift | 1 - .../Primitives/Sources/Generated/Chain.swift | 1 - .../Sources/Generated/ChainCosmos.swift | 1 - .../Sources/Generated/ChainStake.swift | 1 - .../Sources/Types/ChainImage.swift | 2 +- .../Sources/ViewModels/BalanceViewModel.swift | 6 +- ios/Packages/Store/Sources/Migrations.swift | 62 + .../Tests/StoreTests/MigrationsTests.swift | 50 + ios/Packages/Style/Sources/Images.swift | 4 +- .../Contents.json | 2 +- .../sei.svg => seievm.imageset/seievm.svg} | 0 71 files changed, 3182 insertions(+), 159 deletions(-) rename android/app/src/main/assets/chains/icons/{sei.svg => seievm.svg} (100%) create mode 100644 android/data/services/store/schemas/com.gemwallet.android.data.service.store.database.GemDatabase/86.json rename android/{features/wallet-details/viewmodels/src/main/kotlin/com/gemwallet/android/features/wallet/viewmodels => data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store}/LocalStore.kt (57%) create mode 100644 android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_85_86.kt create mode 100644 android/data/services/store/src/test/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_85_86Test.kt rename ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/{sei.imageset => seievm.imageset}/Contents.json (85%) rename ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/{sei.imageset/sei.svg => seievm.imageset/seievm.svg} (100%) diff --git a/android/app/src/main/assets/chains/icons/sei.svg b/android/app/src/main/assets/chains/icons/seievm.svg similarity index 100% rename from android/app/src/main/assets/chains/icons/sei.svg rename to android/app/src/main/assets/chains/icons/seievm.svg diff --git a/android/app/src/main/kotlin/com/gemwallet/android/MainViewModel.kt b/android/app/src/main/kotlin/com/gemwallet/android/MainViewModel.kt index afbd71f0a0..3981998626 100644 --- a/android/app/src/main/kotlin/com/gemwallet/android/MainViewModel.kt +++ b/android/app/src/main/kotlin/com/gemwallet/android/MainViewModel.kt @@ -3,6 +3,7 @@ package com.gemwallet.android import android.content.Intent import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.gemwallet.android.application.wallet.coordinators.DeleteWallet import com.gemwallet.android.data.repositories.bridge.BridgesRepository import com.gemwallet.android.data.repositories.config.UserConfig import com.gemwallet.android.model.AuthState @@ -29,6 +30,7 @@ class MainViewModel @Inject constructor( private val bridgesRepository: BridgesRepository, private val syncService: SyncService, private val migrateV3KeystoreService: MigrateV3KeystoreService, + private val deleteWallet: DeleteWallet, private val checkAccountsService: CheckAccountsService, private val lockTimer: LockTimer, private val pendingNavigationCoordinator: PendingNavigationCoordinator, @@ -78,10 +80,12 @@ class MainViewModel @Inject constructor( fun isAuthRequired(): Boolean = userConfig.authRequired() internal fun maintain() { - viewModelScope.launch(Dispatchers.IO) { syncService.sync() } viewModelScope.launch(Dispatchers.IO) { migrateV3KeystoreService() - checkAccountsService() + if (deleteWallet.deleteEmptyWallets()) { + checkAccountsService() + syncService.sync() + } } } diff --git a/android/app/src/main/kotlin/com/gemwallet/android/services/CheckAccountsService.kt b/android/app/src/main/kotlin/com/gemwallet/android/services/CheckAccountsService.kt index 5c84da7a3c..4cbe15e32a 100644 --- a/android/app/src/main/kotlin/com/gemwallet/android/services/CheckAccountsService.kt +++ b/android/app/src/main/kotlin/com/gemwallet/android/services/CheckAccountsService.kt @@ -4,7 +4,6 @@ import com.gemwallet.android.application.PasswordStore import com.gemwallet.android.blockchain.operators.AddAccountsOperator import com.gemwallet.android.data.repositories.assets.AssetsRepository import com.gemwallet.android.data.repositories.wallets.WalletsRepository -import com.gemwallet.android.ext.available import com.wallet.core.primitives.AssetId import com.wallet.core.primitives.Chain import com.wallet.core.primitives.WalletType @@ -39,7 +38,7 @@ class CheckAccountsService @Inject constructor( } val accountChains = wallet.accounts.map { it.chain }.toSet() - val newChains = Chain.available().filterNot(accountChains::contains) + val newChains = Chain.entries.filterNot(accountChains::contains) // Backfill new chains via add_accounts, skips gracefully if the v4 keystore isn't ready (e.g. migration pending) and retries next launch. val newAccounts = if (newChains.isNotEmpty()) { diff --git a/android/app/src/test/kotlin/com/gemwallet/android/MainViewModelAuthStateTest.kt b/android/app/src/test/kotlin/com/gemwallet/android/MainViewModelAuthStateTest.kt index c925b15ec3..1265e4fa09 100644 --- a/android/app/src/test/kotlin/com/gemwallet/android/MainViewModelAuthStateTest.kt +++ b/android/app/src/test/kotlin/com/gemwallet/android/MainViewModelAuthStateTest.kt @@ -1,5 +1,6 @@ package com.gemwallet.android +import com.gemwallet.android.application.wallet.coordinators.DeleteWallet import com.gemwallet.android.data.repositories.bridge.BridgesRepository import com.gemwallet.android.data.repositories.config.UserConfig import com.gemwallet.android.model.AuthState @@ -92,6 +93,7 @@ class MainViewModelAuthStateTest { bridgesRepository = mockk(relaxed = true), syncService = mockk(relaxed = true), migrateV3KeystoreService = mockk(relaxed = true), + deleteWallet = mockk(relaxed = true), checkAccountsService = mockk(relaxed = true), lockTimer = mockk(relaxed = true), pendingNavigationCoordinator = mockk(relaxed = true), diff --git a/android/app/src/test/kotlin/com/gemwallet/android/services/CheckAccountsServiceTest.kt b/android/app/src/test/kotlin/com/gemwallet/android/services/CheckAccountsServiceTest.kt index 276326ec98..5ac061e5af 100644 --- a/android/app/src/test/kotlin/com/gemwallet/android/services/CheckAccountsServiceTest.kt +++ b/android/app/src/test/kotlin/com/gemwallet/android/services/CheckAccountsServiceTest.kt @@ -4,17 +4,16 @@ import com.gemwallet.android.application.PasswordStore import com.gemwallet.android.blockchain.operators.AddAccountsOperator import com.gemwallet.android.data.repositories.assets.AssetsRepository import com.gemwallet.android.data.repositories.wallets.WalletsRepository -import com.gemwallet.android.ext.available import com.gemwallet.android.testkit.mockAccount import com.gemwallet.android.testkit.mockAsset import com.gemwallet.android.testkit.mockWallet import com.wallet.core.primitives.Chain +import com.wallet.core.primitives.WalletType import io.mockk.coEvery import io.mockk.coJustRun import io.mockk.coVerify import io.mockk.every import io.mockk.mockk -import io.mockk.mockkStatic import io.mockk.unmockkAll import io.mockk.verify import kotlinx.coroutines.flow.flowOf @@ -43,14 +42,14 @@ class CheckAccountsServiceTest { @Test fun invoke_repairsMissingNativeAssetsWithoutCreatingNewAccounts() = runBlocking { - val accounts = Chain.available().map { chain -> + val accounts = Chain.entries.map { chain -> mockAccount(chain = chain) } val wallet = mockWallet( id = "wallet-1", accounts = accounts, ) - val nativeAssets = Chain.available() + val nativeAssets = Chain.entries .filterNot { it == Chain.Ethereum } .map { chain -> mockAsset(chain = chain) } @@ -76,11 +75,9 @@ class CheckAccountsServiceTest { @Test fun invoke_doesNotRepairWhenExpectedNativeAssetsExist() = runBlocking { - mockkStatic("com.gemwallet.android.ext.ChainKt") - every { Chain.available() } returns setOf(Chain.Solana) - val wallet = mockWallet( id = "wallet-1", + type = WalletType.Single, accounts = listOf(mockAccount(chain = Chain.Solana)), ) val nativeAssets = listOf( diff --git a/android/blockchain/src/androidTest/kotlin/com/gemwallet/android/blockchain/operators/TestCreateAccountOperator.kt b/android/blockchain/src/androidTest/kotlin/com/gemwallet/android/blockchain/operators/TestCreateAccountOperator.kt index 1b1f128b0d..db49d909d0 100644 --- a/android/blockchain/src/androidTest/kotlin/com/gemwallet/android/blockchain/operators/TestCreateAccountOperator.kt +++ b/android/blockchain/src/androidTest/kotlin/com/gemwallet/android/blockchain/operators/TestCreateAccountOperator.kt @@ -1,7 +1,6 @@ package com.gemwallet.android.blockchain.operators import androidx.test.core.app.ApplicationProvider -import com.gemwallet.android.ext.available import com.gemwallet.android.testkit.LOCAL_KEYSTORE_TEST_PHRASE import com.gemwallet.android.testkit.TEST_PHRASE import com.gemwallet.android.testkit.gemstoneTestAccount @@ -46,7 +45,7 @@ class TestCreateAccountOperator { @Test fun testCreate_account_derive_address_matches_ios_local_keystore() { - Chain.available().forEach { chain -> + Chain.entries.forEach { chain -> val account = gemstoneTestAccount(context, chain = chain, phrase = LOCAL_KEYSTORE_TEST_PHRASE) assertEquals("Unexpected derived address for $chain", expectedAddress(chain), account.address) @@ -101,7 +100,6 @@ class TestCreateAccountOperator { Chain.Xrp -> "rPwE3gChNKtZ1mhH3Ko8YFGqKmGRWLWXV3" Chain.Celestia -> "celestia142j9u5eaduzd7faumygud6ruhdwme98qpwmfv7" Chain.Injective -> "inj13u6g7vqgw074mgmf2ze2cadzvkz9snlwcrtq8a" - Chain.Sei -> "sei142j9u5eaduzd7faumygud6ruhdwme98qagm0sj" Chain.Noble -> "noble142j9u5eaduzd7faumygud6ruhdwme98qc8l3wa" Chain.Near -> "0c91f6106ff835c0195d5388565a2d69e25038a7e23d26198f85caf6594117ec" Chain.Stellar -> "GA3H6I4C5XUBYGVB66KXR27JV5KS3APSTKRUWOIXZ5MVWZKVTLXWKZ2P" diff --git a/android/blockchain/src/main/kotlin/com/gemwallet/android/blockchain/operators/gemstone/GemMappers.kt b/android/blockchain/src/main/kotlin/com/gemwallet/android/blockchain/operators/gemstone/GemMappers.kt index 747c9e5ffd..2b7fd076c8 100644 --- a/android/blockchain/src/main/kotlin/com/gemwallet/android/blockchain/operators/gemstone/GemMappers.kt +++ b/android/blockchain/src/main/kotlin/com/gemwallet/android/blockchain/operators/gemstone/GemMappers.kt @@ -1,5 +1,6 @@ package com.gemwallet.android.blockchain.operators.gemstone +import com.gemwallet.android.ext.find import com.wallet.core.primitives.Account import com.wallet.core.primitives.Chain import uniffi.gemstone.GemKeystoreAccount @@ -12,5 +13,5 @@ internal fun GemKeystoreAccount.toAccount(): Account = Account( ) internal fun String.toPrimitiveChain(): Chain = - Chain.entries.firstOrNull { it.string == this } + Chain.find(this) ?: throw IllegalArgumentException("Unsupported chain: $this") diff --git a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/di/WalletModule.kt b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/di/WalletModule.kt index 1ad5be003c..accf9eae6c 100644 --- a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/di/WalletModule.kt +++ b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/di/WalletModule.kt @@ -22,9 +22,10 @@ import com.gemwallet.android.data.coordinators.wallet.SetWalletAvatarImpl import com.gemwallet.android.data.coordinators.wallet.SetWalletNameImpl import com.gemwallet.android.data.coordinators.wallet.ToggleWalletPinImpl import com.gemwallet.android.data.coordinators.wallet.WalletIdGeneratorImpl -import com.gemwallet.android.data.service.store.WalletPreferencesFactory import com.gemwallet.android.data.repositories.session.SessionRepository import com.gemwallet.android.data.repositories.wallets.WalletsRepository +import com.gemwallet.android.data.service.store.LocalStore +import com.gemwallet.android.data.service.store.WalletPreferencesFactory import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -94,8 +95,15 @@ object WalletModule { walletsRepository: WalletsRepository, deleteKeyStoreOperator: DeleteKeyStoreOperator, walletPreferencesFactory: WalletPreferencesFactory, + localStore: LocalStore, ): DeleteWallet { - return DeleteWalletImpl(sessionRepository, walletsRepository, deleteKeyStoreOperator, walletPreferencesFactory) + return DeleteWalletImpl( + sessionRepository, + walletsRepository, + deleteKeyStoreOperator, + walletPreferencesFactory, + localStore, + ) } @Provides @@ -112,4 +120,4 @@ object WalletModule { ): SetCurrentWallet { return SetCurrentWalletImpl(sessionRepository, walletsRepository) } -} \ No newline at end of file +} diff --git a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/wallet/DeleteWalletImpl.kt b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/wallet/DeleteWalletImpl.kt index 49684cc3ef..e4b129d56d 100644 --- a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/wallet/DeleteWalletImpl.kt +++ b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/wallet/DeleteWalletImpl.kt @@ -3,44 +3,63 @@ package com.gemwallet.android.data.coordinators.wallet import android.util.Log import com.gemwallet.android.application.wallet.coordinators.DeleteWallet import com.gemwallet.android.blockchain.operators.DeleteKeyStoreOperator -import com.gemwallet.android.data.service.store.WalletPreferencesFactory import com.gemwallet.android.data.repositories.session.SessionRepository import com.gemwallet.android.data.repositories.wallets.WalletsRepository +import com.gemwallet.android.data.service.store.LocalStore +import com.gemwallet.android.data.service.store.WalletPreferencesFactory +import com.wallet.core.primitives.Wallet import com.wallet.core.primitives.WalletId import com.wallet.core.primitives.WalletType import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.withContext -import javax.inject.Inject -import javax.inject.Singleton -@Singleton -class DeleteWalletImpl @Inject constructor( +class DeleteWalletImpl( private val sessionRepository: SessionRepository, private val walletsRepository: WalletsRepository, private val deleteKeyStoreOperator: DeleteKeyStoreOperator, private val walletPreferencesFactory: WalletPreferencesFactory, + private val localStore: LocalStore, ) : DeleteWallet { + override suspend fun deleteEmptyWallets(): Boolean = withContext(Dispatchers.IO) { + for (wallet in walletsRepository.getEmptyWallets()) { + if (!deleteWallet(wallet)) return@withContext false + } + true + } + override suspend fun deleteWallet( walletId: WalletId, onBoard: () -> Unit, - onComplete: () -> Unit - ) = withContext(Dispatchers.IO) { - val wallet = walletsRepository.getWallet(walletId).firstOrNull() ?: return@withContext + onComplete: () -> Unit, + ): Boolean = withContext(Dispatchers.IO) { + val wallet = walletsRepository.getWallet(walletId).firstOrNull() ?: return@withContext false + deleteWallet(wallet, onBoard, onComplete) + } + + private suspend fun deleteWallet( + wallet: Wallet, + onBoard: () -> Unit = {}, + onComplete: () -> Unit = {}, + ): Boolean { + val walletId = wallet.id val currentWalletId = sessionRepository.session().firstOrNull()?.wallet?.id // Delete the keystore before the DB row; if it fails, keep the wallet so the secret is never orphaned silently. if (wallet.type != WalletType.View && !deleteKeyStoreOperator(wallet)) { Log.e(TAG, "keystore delete failed for ${walletId.id}; keeping the wallet") - return@withContext + return false } if (!walletsRepository.removeWallet(walletId = walletId)) { Log.e(TAG, "wallet row removal failed for ${walletId.id}; retry delete to finish") - return@withContext + return false } walletPreferencesFactory.create(walletId.id).clear() + if (!localStore.remove(wallet.imageUrl)) { + Log.e(TAG, "wallet avatar delete failed for ${walletId.id}") + } val callback: () -> Unit = if (currentWalletId == walletId) { val nextWallet = walletsRepository.getAll().firstOrNull() @@ -62,6 +81,7 @@ class DeleteWalletImpl @Inject constructor( withContext(Dispatchers.Main) { callback() } + return true } private companion object { diff --git a/android/data/coordinators/src/test/kotlin/com/gemwallet/android/data/coordinators/wallet/DeleteWalletImplTest.kt b/android/data/coordinators/src/test/kotlin/com/gemwallet/android/data/coordinators/wallet/DeleteWalletImplTest.kt index a8e8bb0129..9928bbbfb0 100644 --- a/android/data/coordinators/src/test/kotlin/com/gemwallet/android/data/coordinators/wallet/DeleteWalletImplTest.kt +++ b/android/data/coordinators/src/test/kotlin/com/gemwallet/android/data/coordinators/wallet/DeleteWalletImplTest.kt @@ -3,6 +3,7 @@ package com.gemwallet.android.data.coordinators.wallet import com.gemwallet.android.blockchain.operators.DeleteKeyStoreOperator import com.gemwallet.android.data.repositories.session.SessionRepository import com.gemwallet.android.data.repositories.wallets.WalletsRepository +import com.gemwallet.android.data.service.store.LocalStore import com.gemwallet.android.data.service.store.WalletPreferences import com.gemwallet.android.data.service.store.WalletPreferencesFactory import com.gemwallet.android.model.Session @@ -22,6 +23,8 @@ import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import org.junit.After +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test @@ -32,6 +35,7 @@ class DeleteWalletImplTest { private val walletsRepository = mockk(relaxed = true) private val deleteKeyStoreOperator = mockk() private val walletPreferences = mockk(relaxed = true) + private val localStore = mockk(relaxed = true) private val walletPreferencesFactory = mockk { every { create(any()) } returns walletPreferences } @@ -41,6 +45,7 @@ class DeleteWalletImplTest { walletsRepository, deleteKeyStoreOperator, walletPreferencesFactory, + localStore, ) @Before @@ -60,26 +65,42 @@ class DeleteWalletImplTest { every { sessionRepository.session() } returns MutableStateFlow(null) every { deleteKeyStoreOperator(wallet) } returns false - delete.deleteWallet(wallet.id, onBoard = {}, onComplete = {}) + val deleted = delete.deleteWallet(wallet.id) + assertFalse(deleted) verify { deleteKeyStoreOperator(wallet) } coVerify(exactly = 0) { walletsRepository.removeWallet(any()) } verify(exactly = 0) { walletPreferences.clear() } } @Test - fun clearsWalletPreferencesWhenWalletDeleted() = runTest { - val wallet = mockWallet(id = "wallet-1", type = WalletType.Multicoin) + fun cleansWalletDataWhenDeleted() = runTest { + val wallet = mockWallet(id = "wallet-1", type = WalletType.Multicoin).copy(imageUrl = "avatar.png") every { walletsRepository.getWallet(wallet.id) } returns flowOf(wallet) every { walletsRepository.getAll() } returns flowOf(emptyList()) every { sessionRepository.session() } returns MutableStateFlow(null) every { deleteKeyStoreOperator(wallet) } returns true coEvery { walletsRepository.removeWallet(wallet.id) } returns true - delete.deleteWallet(wallet.id, onBoard = {}, onComplete = {}) + val deleted = delete.deleteWallet(wallet.id) + assertTrue(deleted) coVerify { walletsRepository.removeWallet(wallet.id) } verify { walletPreferencesFactory.create(wallet.id.id) } verify { walletPreferences.clear() } + verify { localStore.remove("avatar.png") } + } + + @Test + fun deletesEmptyWallets() = runTest { + val emptyWallet = mockWallet(id = "empty-wallet", accounts = emptyList()) + coEvery { walletsRepository.getEmptyWallets() } returns listOf(emptyWallet) + every { sessionRepository.session() } returns MutableStateFlow(null) + every { deleteKeyStoreOperator(emptyWallet) } returns true + coEvery { walletsRepository.removeWallet(emptyWallet.id) } returns true + + assertTrue(delete.deleteEmptyWallets()) + + coVerify { walletsRepository.removeWallet(emptyWallet.id) } } } diff --git a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/assets/AssetsRepository.kt b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/assets/AssetsRepository.kt index ac37e8b7c8..ad9cc32710 100644 --- a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/assets/AssetsRepository.kt +++ b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/assets/AssetsRepository.kt @@ -21,7 +21,6 @@ import com.gemwallet.android.data.service.store.database.entities.toUpdateRecord import com.gemwallet.android.domains.asset.chain import com.gemwallet.android.domains.asset.defaultBasic import com.gemwallet.android.ext.asset -import com.gemwallet.android.ext.available import com.gemwallet.android.ext.toAssetId import com.gemwallet.android.ext.toIdentifier import com.gemwallet.android.model.AssetBalance @@ -361,7 +360,7 @@ class AssetsRepository @Inject constructor( } suspend fun updateNativeAssetRanks() = withContext(Dispatchers.IO) { - val basics = Chain.available().map { it.asset().defaultBasic } + val basics = Chain.entries.map { it.asset().defaultBasic } assetsDao.insert(basics.map { it.toRecord() }) for (basic in basics) { runCatching { assetsDao.updateAssetRank(basic.asset.id.toIdentifier(), basic.score.rank) } diff --git a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/chains/ChainInfoRepository.kt b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/chains/ChainInfoRepository.kt index 9e0765e793..5dbcf5138b 100644 --- a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/chains/ChainInfoRepository.kt +++ b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/chains/ChainInfoRepository.kt @@ -1,10 +1,9 @@ package com.gemwallet.android.data.repositories.chains import com.gemwallet.android.domains.asset.defaultAssetRank -import com.gemwallet.android.ext.available import com.wallet.core.primitives.Chain import javax.inject.Inject class ChainInfoRepository @Inject constructor() { - fun getAll() = Chain.available().sortedByDescending { it.defaultAssetRank } + fun getAll() = Chain.entries.sortedByDescending { it.defaultAssetRank } } diff --git a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/tokens/TokensRepository.kt b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/tokens/TokensRepository.kt index e76ca7d25d..3b77403dba 100644 --- a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/tokens/TokensRepository.kt +++ b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/tokens/TokensRepository.kt @@ -36,7 +36,7 @@ class TokensRepository ( if (query.isEmpty() && tags.isEmpty()) { return@withContext false } - val networkAssets = async { tokenService.search(query, chains.ifEmpty { Chain.entries.toList() }) } + val networkAssets = async { tokenService.search(query, chains.ifEmpty { Chain.entries }) } val tokens = runCatchingCancellable { searchAssets.searchAssets( query = query, diff --git a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/tokens/WalletSearchTokens.kt b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/tokens/WalletSearchTokens.kt index 16469c09a4..97fca25799 100644 --- a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/tokens/WalletSearchTokens.kt +++ b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/tokens/WalletSearchTokens.kt @@ -44,7 +44,7 @@ class WalletSearchTokens( if (scope.isAll && query.isEmpty()) { return@withContext false } - val chains = if (scope.isAll) scopeChains.ifEmpty { Chain.entries.toList() } else emptyList() + val chains = if (scope.isAll) scopeChains.ifEmpty { Chain.entries } else emptyList() val networkAssets = async { tokenService.search(query, chains) } val searchResult = runCatchingCancellable { gemSearch.search(query = query, chains = scopeChains, scope = scope) diff --git a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/wallets/WalletsRepository.kt b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/wallets/WalletsRepository.kt index dbf146be45..829dcd7183 100644 --- a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/wallets/WalletsRepository.kt +++ b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/wallets/WalletsRepository.kt @@ -12,6 +12,8 @@ interface WalletsRepository { fun getAll(): Flow> + suspend fun getEmptyWallets(): List + suspend fun addWatch(walletName: String, address: String, chain: Chain): Wallet suspend fun addControlled( diff --git a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/wallets/WalletsRepositoryImpl.kt b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/wallets/WalletsRepositoryImpl.kt index 65d3579ec5..3abab6bdcb 100644 --- a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/wallets/WalletsRepositoryImpl.kt +++ b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/wallets/WalletsRepositoryImpl.kt @@ -12,7 +12,6 @@ import com.gemwallet.android.data.service.store.database.entities.toDTO import com.gemwallet.android.data.service.store.database.entities.toRecord import com.gemwallet.android.domains.asset.defaultBasic import com.gemwallet.android.ext.asset -import com.gemwallet.android.ext.available import com.wallet.core.primitives.Account import com.wallet.core.primitives.Chain import com.wallet.core.primitives.Wallet @@ -45,6 +44,9 @@ class WalletsRepositoryImpl @Inject constructor( override fun getAll(): Flow> = walletsDao.getAll().toDTO() + override suspend fun getEmptyWallets(): List = + walletsDao.getEmptyWallets().map { it.toDTO(emptyList()) } + override suspend fun addWatch(walletName: String, address: String, chain: Chain): Wallet { val walletId = walletIdGenerator.generateWalletId(WalletType.View, chain, address) val hasWallet = getWallet(walletId).firstOrNull() @@ -79,11 +81,10 @@ class WalletsRepositoryImpl @Inject constructor( source: WalletSource ): Wallet { val accounts = mutableListOf() - val availableChains = Chain.available() val chains = if ((type == WalletType.Single || type == WalletType.PrivateKey) && chain != null) listOf( chain - ) else Chain.entries.filter(availableChains::contains) + ) else Chain.entries for (item in chains) { accounts.add(createAccount(type, data, item)) } diff --git a/android/data/repositories/src/test/kotlin/com/gemwallet/android/data/repositories/assets/AssetsRepositoryTest.kt b/android/data/repositories/src/test/kotlin/com/gemwallet/android/data/repositories/assets/AssetsRepositoryTest.kt index bddeca1306..c21daccd6d 100644 --- a/android/data/repositories/src/test/kotlin/com/gemwallet/android/data/repositories/assets/AssetsRepositoryTest.kt +++ b/android/data/repositories/src/test/kotlin/com/gemwallet/android/data/repositories/assets/AssetsRepositoryTest.kt @@ -21,11 +21,11 @@ import com.gemwallet.android.data.service.store.database.entities.mockDbAsset import com.gemwallet.android.data.service.store.database.entities.mockDbAssetInfo import com.gemwallet.android.domains.asset.defaultBasic import com.gemwallet.android.ext.asset -import com.gemwallet.android.ext.available import com.gemwallet.android.ext.isStakeSupported import com.gemwallet.android.ext.isSwapSupport import com.gemwallet.android.ext.toIdentifier import com.gemwallet.android.testkit.mockAccount +import com.gemwallet.android.testkit.mockAsset import com.gemwallet.android.testkit.mockAssetFull import com.gemwallet.android.testkit.mockAssetLink import com.gemwallet.android.testkit.mockAssetEthereum @@ -464,15 +464,12 @@ class AssetsRepositoryTest { every { sessionRepository.session() } returns sessionFlow mockkStatic("com.gemwallet.android.ext.ChainKt") mockkStatic("uniffi.gemstone.GemstoneKt") - every { Chain.available() } returns setOf(Chain.Solana, Chain.Ethereum) - every { Chain.Solana.asset() } returns mockAssetSolana() - every { Chain.Ethereum.asset() } returns mockAssetEthereum() - every { Chain.Solana.isSwapSupport() } returns true - every { Chain.Solana.isStakeSupported() } returns true - every { Chain.Ethereum.isSwapSupport() } returns true - every { Chain.Ethereum.isStakeSupported() } returns false - every { assetDefaultRank(Chain.Solana.string) } returns 99 - every { assetDefaultRank(Chain.Ethereum.string) } returns 77 + Chain.entries.forEach { chain -> + every { chain.asset() } returns mockAsset(chain = chain) + every { chain.isSwapSupport() } returns false + every { chain.isStakeSupported() } returns false + every { assetDefaultRank(chain.string) } returns if (chain == Chain.Solana) 99 else 77 + } val subject = createSubject() subject.updateNativeAssetRanks() diff --git a/android/data/services/store/schemas/com.gemwallet.android.data.service.store.database.GemDatabase/86.json b/android/data/services/store/schemas/com.gemwallet.android.data.service.store.database.GemDatabase/86.json new file mode 100644 index 0000000000..e34e05dd69 --- /dev/null +++ b/android/data/services/store/schemas/com.gemwallet.android.data.service.store.database.GemDatabase/86.json @@ -0,0 +1,2702 @@ +{ + "formatVersion": 1, + "database": { + "version": 86, + "identityHash": "be79416139dd7439f83baef92c76ae11", + "entities": [ + { + "tableName": "wallets", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `domain_name` TEXT, `type` TEXT NOT NULL, `position` INTEGER NOT NULL, `pinned` INTEGER NOT NULL, `index` INTEGER NOT NULL, `source` TEXT NOT NULL DEFAULT 'Import', `imageUrl` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "domainName", + "columnName": "domain_name", + "affinity": "TEXT" + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "pinned", + "columnName": "pinned", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "index", + "columnName": "index", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "source", + "columnName": "source", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'Import'" + }, + { + "fieldPath": "imageUrl", + "columnName": "imageUrl", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "accounts", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`wallet_id` TEXT NOT NULL, `derivation_path` TEXT NOT NULL, `address` TEXT NOT NULL, `chain` TEXT NOT NULL, `extendedPublicKey` TEXT, PRIMARY KEY(`wallet_id`, `chain`), FOREIGN KEY(`chain`) REFERENCES `asset`(`id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`wallet_id`) REFERENCES `wallets`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "walletId", + "columnName": "wallet_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "derivationPath", + "columnName": "derivation_path", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "chain", + "columnName": "chain", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "extendedPublicKey", + "columnName": "extendedPublicKey", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "wallet_id", + "chain" + ] + }, + "indices": [ + { + "name": "index_accounts_chain", + "unique": false, + "columnNames": [ + "chain" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_accounts_chain` ON `${TABLE_NAME}` (`chain`)" + } + ], + "foreignKeys": [ + { + "table": "asset", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "chain" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "wallets", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "wallet_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "addresses", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`chain` TEXT NOT NULL, `address` TEXT NOT NULL, `walletId` TEXT, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `status` TEXT NOT NULL, PRIMARY KEY(`chain`, `address`), FOREIGN KEY(`chain`) REFERENCES `asset`(`id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`walletId`) REFERENCES `wallets`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "chain", + "columnName": "chain", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "chain", + "address" + ] + }, + "indices": [ + { + "name": "index_addresses_chain", + "unique": false, + "columnNames": [ + "chain" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_addresses_chain` ON `${TABLE_NAME}` (`chain`)" + }, + { + "name": "index_addresses_walletId", + "unique": false, + "columnNames": [ + "walletId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_addresses_walletId` ON `${TABLE_NAME}` (`walletId`)" + } + ], + "foreignKeys": [ + { + "table": "asset", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "chain" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "wallets", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "walletId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "contacts", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `description` TEXT, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT" + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updatedAt", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "contacts_addresses", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `contactId` TEXT NOT NULL, `address` TEXT NOT NULL, `chain` TEXT NOT NULL, `memo` TEXT, PRIMARY KEY(`id`), FOREIGN KEY(`contactId`) REFERENCES `contacts`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contactId", + "columnName": "contactId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "chain", + "columnName": "chain", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "memo", + "columnName": "memo", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_contacts_addresses_contactId", + "unique": false, + "columnNames": [ + "contactId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_contacts_addresses_contactId` ON `${TABLE_NAME}` (`contactId`)" + } + ], + "foreignKeys": [ + { + "table": "contacts", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "contactId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "asset", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `symbol` TEXT NOT NULL, `decimals` INTEGER NOT NULL, `type` TEXT NOT NULL, `chain` TEXT NOT NULL, `is_enabled` INTEGER NOT NULL, `is_buy_enabled` INTEGER NOT NULL, `is_sell_enabled` INTEGER NOT NULL, `is_swap_enabled` INTEGER NOT NULL, `is_stake_enabled` INTEGER NOT NULL, `staking_apr` REAL, `rank` INTEGER NOT NULL, `updated_at` INTEGER NOT NULL, `associations` TEXT NOT NULL DEFAULT '[]', PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "decimals", + "columnName": "decimals", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "chain", + "columnName": "chain", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isEnabled", + "columnName": "is_enabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isBuyEnabled", + "columnName": "is_buy_enabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isSellEnabled", + "columnName": "is_sell_enabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isSwapEnabled", + "columnName": "is_swap_enabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isStakeEnabled", + "columnName": "is_stake_enabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "stakingApr", + "columnName": "staking_apr", + "affinity": "REAL" + }, + { + "fieldPath": "rank", + "columnName": "rank", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updated_at", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "associations", + "columnName": "associations", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'[]'" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "balances", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`asset_id` TEXT NOT NULL, `wallet_id` TEXT NOT NULL, `available` TEXT NOT NULL, `available_amount` REAL NOT NULL, `frozen` TEXT NOT NULL, `frozen_amount` REAL NOT NULL, `locked` TEXT NOT NULL, `locked_amount` REAL NOT NULL, `staked` TEXT NOT NULL, `staked_amount` REAL NOT NULL, `pending` TEXT NOT NULL, `pending_amount` REAL NOT NULL, `rewards` TEXT NOT NULL, `rewards_amount` REAL NOT NULL, `reserved` TEXT NOT NULL, `reserved_amount` REAL NOT NULL, `withdrawable` TEXT NOT NULL, `withdrawableAmount` REAL NOT NULL, `total_amount` REAL NOT NULL, `is_active` INTEGER NOT NULL, `is_pinned` INTEGER NOT NULL, `is_visible` INTEGER NOT NULL, `list_position` INTEGER NOT NULL, `votes` INTEGER NOT NULL DEFAULT 0, `energy_available` INTEGER NOT NULL DEFAULT 0, `energy_total` INTEGER NOT NULL DEFAULT 0, `bandwidth_available` INTEGER NOT NULL DEFAULT 0, `bandwidth_total` INTEGER NOT NULL DEFAULT 0, `updated_at` INTEGER, PRIMARY KEY(`asset_id`, `wallet_id`), FOREIGN KEY(`asset_id`) REFERENCES `asset`(`id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`wallet_id`) REFERENCES `wallets`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "assetId", + "columnName": "asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "walletId", + "columnName": "wallet_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "available", + "columnName": "available", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "availableAmount", + "columnName": "available_amount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "frozen", + "columnName": "frozen", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "frozenAmount", + "columnName": "frozen_amount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "locked", + "columnName": "locked", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lockedAmount", + "columnName": "locked_amount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "staked", + "columnName": "staked", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "stakedAmount", + "columnName": "staked_amount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "pending", + "columnName": "pending", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pendingAmount", + "columnName": "pending_amount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "rewards", + "columnName": "rewards", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "rewardsAmount", + "columnName": "rewards_amount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "reserved", + "columnName": "reserved", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "reservedAmount", + "columnName": "reserved_amount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "withdrawable", + "columnName": "withdrawable", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "withdrawableAmount", + "columnName": "withdrawableAmount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "totalAmount", + "columnName": "total_amount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "isActive", + "columnName": "is_active", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPinned", + "columnName": "is_pinned", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isVisible", + "columnName": "is_visible", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "listPosition", + "columnName": "list_position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "votes", + "columnName": "votes", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "energyAvailable", + "columnName": "energy_available", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "energyTotal", + "columnName": "energy_total", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "bandwidthAvailable", + "columnName": "bandwidth_available", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "bandwidthTotal", + "columnName": "bandwidth_total", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "updatedAt", + "columnName": "updated_at", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "asset_id", + "wallet_id" + ] + }, + "indices": [ + { + "name": "index_balances_wallet_id", + "unique": false, + "columnNames": [ + "wallet_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_balances_wallet_id` ON `${TABLE_NAME}` (`wallet_id`)" + } + ], + "foreignKeys": [ + { + "table": "asset", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "asset_id" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "wallets", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "wallet_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "prices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`asset_id` TEXT NOT NULL, `value` REAL, `usd_value` REAL, `day_changed` REAL, `currency` TEXT NOT NULL, PRIMARY KEY(`asset_id`))", + "fields": [ + { + "fieldPath": "assetId", + "columnName": "asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL" + }, + { + "fieldPath": "usdValue", + "columnName": "usd_value", + "affinity": "REAL" + }, + { + "fieldPath": "dayChanged", + "columnName": "day_changed", + "affinity": "REAL" + }, + { + "fieldPath": "currency", + "columnName": "currency", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "asset_id" + ] + } + }, + { + "tableName": "transactions", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `walletId` TEXT NOT NULL, `hash` TEXT NOT NULL, `assetId` TEXT NOT NULL, `feeAssetId` TEXT NOT NULL, `owner` TEXT NOT NULL, `recipient` TEXT NOT NULL, `contract` TEXT, `metadata` TEXT, `state` TEXT NOT NULL, `type` TEXT NOT NULL, `blockNumber` TEXT NOT NULL, `sequence` TEXT NOT NULL, `fee` TEXT NOT NULL, `value` TEXT NOT NULL, `payload` TEXT, `direction` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `estimatedConfirmationInSeconds` INTEGER, PRIMARY KEY(`id`, `walletId`), FOREIGN KEY(`walletId`) REFERENCES `wallets`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "assetId", + "columnName": "assetId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "feeAssetId", + "columnName": "feeAssetId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "owner", + "columnName": "owner", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contract", + "columnName": "contract", + "affinity": "TEXT" + }, + { + "fieldPath": "metadata", + "columnName": "metadata", + "affinity": "TEXT" + }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "blockNumber", + "columnName": "blockNumber", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sequence", + "columnName": "sequence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fee", + "columnName": "fee", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "payload", + "columnName": "payload", + "affinity": "TEXT" + }, + { + "fieldPath": "direction", + "columnName": "direction", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updatedAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "confirmationEtaSeconds", + "columnName": "estimatedConfirmationInSeconds", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id", + "walletId" + ] + }, + "indices": [ + { + "name": "index_transactions_walletId", + "unique": false, + "columnNames": [ + "walletId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_transactions_walletId` ON `${TABLE_NAME}` (`walletId`)" + } + ], + "foreignKeys": [ + { + "table": "wallets", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "walletId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "tx_swap_metadata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tx_id` TEXT NOT NULL, `from_asset_id` TEXT NOT NULL, `to_asset_id` TEXT NOT NULL, `from_amount` TEXT NOT NULL, `to_amount` TEXT NOT NULL, PRIMARY KEY(`tx_id`))", + "fields": [ + { + "fieldPath": "txId", + "columnName": "tx_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fromAssetId", + "columnName": "from_asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "toAssetId", + "columnName": "to_asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fromAmount", + "columnName": "from_amount", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "toAmount", + "columnName": "to_amount", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "tx_id" + ] + } + }, + { + "tableName": "wallets_connections", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `wallet_id` TEXT NOT NULL, `session_id` TEXT NOT NULL, `state` TEXT NOT NULL, `chains` TEXT NOT NULL, `created_at` INTEGER NOT NULL, `expire_at` INTEGER NOT NULL, `app_name` TEXT NOT NULL, `app_description` TEXT NOT NULL, `app_url` TEXT NOT NULL, `app_icon` TEXT NOT NULL, `redirect_native` TEXT, `redirect_universal` TEXT, PRIMARY KEY(`id`), FOREIGN KEY(`wallet_id`) REFERENCES `wallets`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "walletId", + "columnName": "wallet_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sessionId", + "columnName": "session_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "chains", + "columnName": "chains", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "expireAt", + "columnName": "expire_at", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "appName", + "columnName": "app_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "appDescription", + "columnName": "app_description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "appUrl", + "columnName": "app_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "appIcon", + "columnName": "app_icon", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "redirectNative", + "columnName": "redirect_native", + "affinity": "TEXT" + }, + { + "fieldPath": "redirectUniversal", + "columnName": "redirect_universal", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_wallets_connections_wallet_id", + "unique": false, + "columnNames": [ + "wallet_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_wallets_connections_wallet_id` ON `${TABLE_NAME}` (`wallet_id`)" + } + ], + "foreignKeys": [ + { + "table": "wallets", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "wallet_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "stake_validators", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `assetId` TEXT NOT NULL, `validatorId` TEXT NOT NULL, `name` TEXT NOT NULL, `isActive` INTEGER NOT NULL, `commission` REAL NOT NULL, `apr` REAL NOT NULL, `providerType` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`assetId`) REFERENCES `asset`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "assetId", + "columnName": "assetId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "validatorId", + "columnName": "validatorId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isActive", + "columnName": "isActive", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "commission", + "columnName": "commission", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "apr", + "columnName": "apr", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "providerType", + "columnName": "providerType", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_stake_validators_assetId", + "unique": false, + "columnNames": [ + "assetId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_stake_validators_assetId` ON `${TABLE_NAME}` (`assetId`)" + } + ], + "foreignKeys": [ + { + "table": "asset", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "assetId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "stake_delegations", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `walletId` TEXT NOT NULL, `assetId` TEXT NOT NULL, `validatorId` TEXT NOT NULL, `state` TEXT NOT NULL, `delegationId` TEXT NOT NULL, `balance` TEXT NOT NULL, `shares` TEXT NOT NULL, `rewards` TEXT NOT NULL, `completionDate` INTEGER, PRIMARY KEY(`walletId`, `id`), FOREIGN KEY(`walletId`) REFERENCES `wallets`(`id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`assetId`) REFERENCES `asset`(`id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`validatorId`) REFERENCES `stake_validators`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "assetId", + "columnName": "assetId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "validatorId", + "columnName": "validatorId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "delegationId", + "columnName": "delegationId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "balance", + "columnName": "balance", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shares", + "columnName": "shares", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "rewards", + "columnName": "rewards", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "completionDate", + "columnName": "completionDate", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "walletId", + "id" + ] + }, + "indices": [ + { + "name": "index_stake_delegations_assetId", + "unique": false, + "columnNames": [ + "assetId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_stake_delegations_assetId` ON `${TABLE_NAME}` (`assetId`)" + }, + { + "name": "index_stake_delegations_validatorId", + "unique": false, + "columnNames": [ + "validatorId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_stake_delegations_validatorId` ON `${TABLE_NAME}` (`validatorId`)" + } + ], + "foreignKeys": [ + { + "table": "wallets", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "walletId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "asset", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "assetId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "stake_validators", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "validatorId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "nodes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `status` TEXT NOT NULL, `priority` INTEGER NOT NULL, `chain` TEXT NOT NULL, PRIMARY KEY(`url`), FOREIGN KEY(`chain`) REFERENCES `asset`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "chain", + "columnName": "chain", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "url" + ] + }, + "indices": [ + { + "name": "index_nodes_chain", + "unique": false, + "columnNames": [ + "chain" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_nodes_chain` ON `${TABLE_NAME}` (`chain`)" + } + ], + "foreignKeys": [ + { + "table": "asset", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "chain" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "session", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `wallet_id` TEXT NOT NULL, `currency` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "walletId", + "columnName": "wallet_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "currency", + "columnName": "currency", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "banners", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`wallet_id` TEXT NOT NULL, `asset_id` TEXT NOT NULL, `chain` TEXT, `state` TEXT NOT NULL, `event` TEXT NOT NULL, PRIMARY KEY(`wallet_id`, `asset_id`), FOREIGN KEY(`chain`) REFERENCES `asset`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "walletId", + "columnName": "wallet_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "assetId", + "columnName": "asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "chain", + "columnName": "chain", + "affinity": "TEXT" + }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "event", + "columnName": "event", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "wallet_id", + "asset_id" + ] + }, + "indices": [ + { + "name": "index_banners_event", + "unique": false, + "columnNames": [ + "event" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_banners_event` ON `${TABLE_NAME}` (`event`)" + }, + { + "name": "index_banners_wallet_id", + "unique": false, + "columnNames": [ + "wallet_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_banners_wallet_id` ON `${TABLE_NAME}` (`wallet_id`)" + }, + { + "name": "index_banners_chain", + "unique": false, + "columnNames": [ + "chain" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_banners_chain` ON `${TABLE_NAME}` (`chain`)" + } + ], + "foreignKeys": [ + { + "table": "asset", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "chain" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "price_alerts", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `assetId` TEXT NOT NULL, `currency` TEXT NOT NULL, `price` REAL, `pricePercentChange` REAL, `priceDirection` TEXT, `lastNotifiedAt` INTEGER, `enabled` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "assetId", + "columnName": "assetId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "currency", + "columnName": "currency", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "price", + "columnName": "price", + "affinity": "REAL" + }, + { + "fieldPath": "pricePercentChange", + "columnName": "pricePercentChange", + "affinity": "REAL" + }, + { + "fieldPath": "priceDirection", + "columnName": "priceDirection", + "affinity": "TEXT" + }, + { + "fieldPath": "lastNotifiedAt", + "columnName": "lastNotifiedAt", + "affinity": "INTEGER" + }, + { + "fieldPath": "enabled", + "columnName": "enabled", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "nft_collections", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `description` TEXT, `chain` TEXT NOT NULL, `contractAddress` TEXT NOT NULL, `imageUrl` TEXT NOT NULL, `previewImageUrl` TEXT NOT NULL, `originalSourceUrl` TEXT NOT NULL, `status` TEXT, `links` TEXT, PRIMARY KEY(`id`), FOREIGN KEY(`chain`) REFERENCES `asset`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT" + }, + { + "fieldPath": "chain", + "columnName": "chain", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contractAddress", + "columnName": "contractAddress", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "imageUrl", + "columnName": "imageUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "previewImageUrl", + "columnName": "previewImageUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "originalSourceUrl", + "columnName": "originalSourceUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT" + }, + { + "fieldPath": "links", + "columnName": "links", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_nft_collections_chain", + "unique": false, + "columnNames": [ + "chain" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_nft_collections_chain` ON `${TABLE_NAME}` (`chain`)" + } + ], + "foreignKeys": [ + { + "table": "asset", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "chain" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "nft_assets", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `collection_id` TEXT NOT NULL, `token_id` TEXT NOT NULL, `token_type` TEXT NOT NULL, `name` TEXT NOT NULL, `description` TEXT, `chain` TEXT NOT NULL, `contract_address` TEXT, `image_url` TEXT NOT NULL, `preview_image_url` TEXT NOT NULL, `original_image_url` TEXT NOT NULL, `attributes` TEXT, PRIMARY KEY(`id`), FOREIGN KEY(`collection_id`) REFERENCES `nft_collections`(`id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`chain`) REFERENCES `asset`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "collectionId", + "columnName": "collection_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "tokenId", + "columnName": "token_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "tokenType", + "columnName": "token_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT" + }, + { + "fieldPath": "chain", + "columnName": "chain", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contractAddress", + "columnName": "contract_address", + "affinity": "TEXT" + }, + { + "fieldPath": "imageUrl", + "columnName": "image_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "previewImageUrl", + "columnName": "preview_image_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "originalSourceUrl", + "columnName": "original_image_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attributes", + "columnName": "attributes", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_nft_assets_collection_id", + "unique": false, + "columnNames": [ + "collection_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_nft_assets_collection_id` ON `${TABLE_NAME}` (`collection_id`)" + }, + { + "name": "index_nft_assets_chain", + "unique": false, + "columnNames": [ + "chain" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_nft_assets_chain` ON `${TABLE_NAME}` (`chain`)" + } + ], + "foreignKeys": [ + { + "table": "nft_collections", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "collection_id" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "asset", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "chain" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "nft_assets_associations", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`wallet_id` TEXT NOT NULL, `asset_id` TEXT NOT NULL, PRIMARY KEY(`wallet_id`, `asset_id`), FOREIGN KEY(`asset_id`) REFERENCES `nft_assets`(`id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`wallet_id`) REFERENCES `wallets`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "walletId", + "columnName": "wallet_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "assetId", + "columnName": "asset_id", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "wallet_id", + "asset_id" + ] + }, + "indices": [ + { + "name": "index_nft_assets_associations_asset_id", + "unique": false, + "columnNames": [ + "asset_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_nft_assets_associations_asset_id` ON `${TABLE_NAME}` (`asset_id`)" + } + ], + "foreignKeys": [ + { + "table": "nft_assets", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "asset_id" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "wallets", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "wallet_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "asset_links", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`asset_id` TEXT NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`asset_id`, `name`), FOREIGN KEY(`asset_id`) REFERENCES `asset`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "assetId", + "columnName": "asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "asset_id", + "name" + ] + }, + "foreignKeys": [ + { + "table": "asset", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "asset_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "asset_market", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`asset_id` TEXT NOT NULL, `marketCap` REAL, `marketCapFdv` REAL, `marketCapRank` INTEGER, `totalVolume` REAL, `circulatingSupply` REAL, `totalSupply` REAL, `maxSupply` REAL, `allTimeHigh` REAL, `allTimeHighDate` INTEGER, `allTimeHighChangePercentage` REAL, `allTimeLow` REAL, `allTimeLowDate` INTEGER, `allTimeLowChangePercentage` REAL, PRIMARY KEY(`asset_id`), FOREIGN KEY(`asset_id`) REFERENCES `asset`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "assetId", + "columnName": "asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "marketCap", + "columnName": "marketCap", + "affinity": "REAL" + }, + { + "fieldPath": "marketCapFdv", + "columnName": "marketCapFdv", + "affinity": "REAL" + }, + { + "fieldPath": "marketCapRank", + "columnName": "marketCapRank", + "affinity": "INTEGER" + }, + { + "fieldPath": "totalVolume", + "columnName": "totalVolume", + "affinity": "REAL" + }, + { + "fieldPath": "circulatingSupply", + "columnName": "circulatingSupply", + "affinity": "REAL" + }, + { + "fieldPath": "totalSupply", + "columnName": "totalSupply", + "affinity": "REAL" + }, + { + "fieldPath": "maxSupply", + "columnName": "maxSupply", + "affinity": "REAL" + }, + { + "fieldPath": "allTimeHigh", + "columnName": "allTimeHigh", + "affinity": "REAL" + }, + { + "fieldPath": "allTimeHighDate", + "columnName": "allTimeHighDate", + "affinity": "INTEGER" + }, + { + "fieldPath": "allTimeHighChangePercentage", + "columnName": "allTimeHighChangePercentage", + "affinity": "REAL" + }, + { + "fieldPath": "allTimeLow", + "columnName": "allTimeLow", + "affinity": "REAL" + }, + { + "fieldPath": "allTimeLowDate", + "columnName": "allTimeLowDate", + "affinity": "INTEGER" + }, + { + "fieldPath": "allTimeLowChangePercentage", + "columnName": "allTimeLowChangePercentage", + "affinity": "REAL" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "asset_id" + ] + }, + "foreignKeys": [ + { + "table": "asset", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "asset_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "asset_lists", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `count` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "count", + "columnName": "count", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "search", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `query` TEXT NOT NULL, `assetId` TEXT, `perpetualId` TEXT, `listId` TEXT, `priority` INTEGER NOT NULL, FOREIGN KEY(`assetId`) REFERENCES `asset`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`perpetualId`) REFERENCES `perpetuals`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`listId`) REFERENCES `asset_lists`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "query", + "columnName": "query", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "assetId", + "columnName": "assetId", + "affinity": "TEXT" + }, + { + "fieldPath": "perpetualId", + "columnName": "perpetualId", + "affinity": "TEXT" + }, + { + "fieldPath": "listId", + "columnName": "listId", + "affinity": "TEXT" + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_search_query", + "unique": false, + "columnNames": [ + "query" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_search_query` ON `${TABLE_NAME}` (`query`)" + }, + { + "name": "index_search_assetId_query", + "unique": true, + "columnNames": [ + "assetId", + "query" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_search_assetId_query` ON `${TABLE_NAME}` (`assetId`, `query`)" + }, + { + "name": "index_search_perpetualId_query", + "unique": true, + "columnNames": [ + "perpetualId", + "query" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_search_perpetualId_query` ON `${TABLE_NAME}` (`perpetualId`, `query`)" + }, + { + "name": "index_search_listId_query", + "unique": true, + "columnNames": [ + "listId", + "query" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_search_listId_query` ON `${TABLE_NAME}` (`listId`, `query`)" + } + ], + "foreignKeys": [ + { + "table": "asset", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "assetId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "perpetuals", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "perpetualId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "asset_lists", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "listId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "currency_rates", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`currency` TEXT NOT NULL, `rate` REAL NOT NULL, PRIMARY KEY(`currency`))", + "fields": [ + { + "fieldPath": "currency", + "columnName": "currency", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "rate", + "columnName": "rate", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "currency" + ] + } + }, + { + "tableName": "fiat_transactions", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `walletId` TEXT NOT NULL, `assetId` TEXT NOT NULL, `transactionType` TEXT NOT NULL, `provider` TEXT NOT NULL, `status` TEXT NOT NULL, `fiatAmount` REAL NOT NULL, `fiatCurrency` TEXT NOT NULL, `value` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `detailsUrl` TEXT, PRIMARY KEY(`id`, `walletId`), FOREIGN KEY(`walletId`) REFERENCES `wallets`(`id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`assetId`) REFERENCES `asset`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "assetId", + "columnName": "assetId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "transactionType", + "columnName": "transactionType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "provider", + "columnName": "provider", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fiatAmount", + "columnName": "fiatAmount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "fiatCurrency", + "columnName": "fiatCurrency", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "detailsUrl", + "columnName": "detailsUrl", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id", + "walletId" + ] + }, + "indices": [ + { + "name": "index_fiat_transactions_walletId", + "unique": false, + "columnNames": [ + "walletId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_fiat_transactions_walletId` ON `${TABLE_NAME}` (`walletId`)" + }, + { + "name": "index_fiat_transactions_assetId", + "unique": false, + "columnNames": [ + "assetId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_fiat_transactions_assetId` ON `${TABLE_NAME}` (`assetId`)" + } + ], + "foreignKeys": [ + { + "table": "wallets", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "walletId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "asset", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "assetId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "recent_assets", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`asset_id` TEXT NOT NULL, `wallet_id` TEXT NOT NULL, `to_asset_id` TEXT, `type` TEXT NOT NULL, `addedAt` INTEGER NOT NULL, PRIMARY KEY(`asset_id`, `wallet_id`, `type`), FOREIGN KEY(`asset_id`) REFERENCES `asset`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`wallet_id`) REFERENCES `wallets`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "assetId", + "columnName": "asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "walletId", + "columnName": "wallet_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "toAssetId", + "columnName": "to_asset_id", + "affinity": "TEXT" + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "addedAt", + "columnName": "addedAt", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "asset_id", + "wallet_id", + "type" + ] + }, + "indices": [ + { + "name": "index_recent_assets_wallet_id", + "unique": false, + "columnNames": [ + "wallet_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_recent_assets_wallet_id` ON `${TABLE_NAME}` (`wallet_id`)" + } + ], + "foreignKeys": [ + { + "table": "asset", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "asset_id" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "wallets", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "wallet_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "perpetuals", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `provider` TEXT NOT NULL, `assetId` TEXT NOT NULL, `identifier` TEXT NOT NULL, `price` REAL NOT NULL, `pricePercentChange24h` REAL NOT NULL, `openInterest` REAL NOT NULL, `volume24h` REAL NOT NULL, `funding` REAL NOT NULL, `maxLeverage` INTEGER NOT NULL, `isIsolatedOnly` INTEGER NOT NULL, `isPinned` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`assetId`) REFERENCES `asset`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "provider", + "columnName": "provider", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "assetId", + "columnName": "assetId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "identifier", + "columnName": "identifier", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "price", + "columnName": "price", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "pricePercentChange24h", + "columnName": "pricePercentChange24h", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "openInterest", + "columnName": "openInterest", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "volume24h", + "columnName": "volume24h", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "funding", + "columnName": "funding", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "maxLeverage", + "columnName": "maxLeverage", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isIsolatedOnly", + "columnName": "isIsolatedOnly", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPinned", + "columnName": "isPinned", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "perpetuals_asset_id_idx", + "unique": false, + "columnNames": [ + "assetId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `perpetuals_asset_id_idx` ON `${TABLE_NAME}` (`assetId`)" + } + ], + "foreignKeys": [ + { + "table": "asset", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "assetId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "perpetuals_positions", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `walletId` TEXT NOT NULL, `perpetualId` TEXT NOT NULL, `assetId` TEXT NOT NULL, `size` REAL NOT NULL, `sizeValue` REAL NOT NULL, `leverage` INTEGER NOT NULL, `entryPrice` REAL, `liquidationPrice` REAL, `marginType` TEXT NOT NULL, `direction` TEXT NOT NULL, `marginAmount` REAL NOT NULL, `takeProfitPrice` REAL, `takeProfitType` TEXT, `takeProfitOrderId` TEXT, `stopLossPrice` REAL, `stopLossType` TEXT, `stopLossOrderId` TEXT, `pnl` REAL NOT NULL, `funding` REAL, `updatedAt` INTEGER NOT NULL, PRIMARY KEY(`id`, `walletId`), FOREIGN KEY(`walletId`) REFERENCES `wallets`(`id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`perpetualId`) REFERENCES `perpetuals`(`id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`assetId`) REFERENCES `asset`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "perpetualId", + "columnName": "perpetualId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "assetId", + "columnName": "assetId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "size", + "columnName": "size", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "sizeValue", + "columnName": "sizeValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "leverage", + "columnName": "leverage", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryPrice", + "columnName": "entryPrice", + "affinity": "REAL" + }, + { + "fieldPath": "liquidationPrice", + "columnName": "liquidationPrice", + "affinity": "REAL" + }, + { + "fieldPath": "marginType", + "columnName": "marginType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "direction", + "columnName": "direction", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "marginAmount", + "columnName": "marginAmount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "takeProfitPrice", + "columnName": "takeProfitPrice", + "affinity": "REAL" + }, + { + "fieldPath": "takeProfitType", + "columnName": "takeProfitType", + "affinity": "TEXT" + }, + { + "fieldPath": "takeProfitOrderId", + "columnName": "takeProfitOrderId", + "affinity": "TEXT" + }, + { + "fieldPath": "stopLossPrice", + "columnName": "stopLossPrice", + "affinity": "REAL" + }, + { + "fieldPath": "stopLossType", + "columnName": "stopLossType", + "affinity": "TEXT" + }, + { + "fieldPath": "stopLossOrderId", + "columnName": "stopLossOrderId", + "affinity": "TEXT" + }, + { + "fieldPath": "pnl", + "columnName": "pnl", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "funding", + "columnName": "funding", + "affinity": "REAL" + }, + { + "fieldPath": "updatedAt", + "columnName": "updatedAt", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id", + "walletId" + ] + }, + "indices": [ + { + "name": "perpetuals_positions_wallet_id_idx", + "unique": false, + "columnNames": [ + "walletId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `perpetuals_positions_wallet_id_idx` ON `${TABLE_NAME}` (`walletId`)" + }, + { + "name": "perpetuals_positions_perpetual_id_idx", + "unique": false, + "columnNames": [ + "perpetualId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `perpetuals_positions_perpetual_id_idx` ON `${TABLE_NAME}` (`perpetualId`)" + }, + { + "name": "perpetuals_positions_asset_id_idx", + "unique": false, + "columnNames": [ + "assetId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `perpetuals_positions_asset_id_idx` ON `${TABLE_NAME}` (`assetId`)" + } + ], + "foreignKeys": [ + { + "table": "wallets", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "walletId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "perpetuals", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "perpetualId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "asset", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "assetId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "in_app_notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `wallet_id` TEXT NOT NULL, `read_at` INTEGER, `created_at` INTEGER NOT NULL, `item` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`wallet_id`) REFERENCES `wallets`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "walletId", + "columnName": "wallet_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "readAt", + "columnName": "read_at", + "affinity": "INTEGER" + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "item", + "columnName": "item", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_in_app_notifications_wallet_id", + "unique": false, + "columnNames": [ + "wallet_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_in_app_notifications_wallet_id` ON `${TABLE_NAME}` (`wallet_id`)" + }, + { + "name": "index_in_app_notifications_created_at", + "unique": false, + "columnNames": [ + "created_at" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_in_app_notifications_created_at` ON `${TABLE_NAME}` (`created_at`)" + } + ], + "foreignKeys": [ + { + "table": "wallets", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "wallet_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "support_messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `content` TEXT NOT NULL, `sender` TEXT NOT NULL, `status` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `images` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "images", + "columnName": "images", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_support_messages_createdAt", + "unique": false, + "columnNames": [ + "createdAt" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_support_messages_createdAt` ON `${TABLE_NAME}` (`createdAt`)" + } + ] + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'be79416139dd7439f83baef92c76ae11')" + ] + } +} \ No newline at end of file diff --git a/android/features/wallet-details/viewmodels/src/main/kotlin/com/gemwallet/android/features/wallet/viewmodels/LocalStore.kt b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/LocalStore.kt similarity index 57% rename from android/features/wallet-details/viewmodels/src/main/kotlin/com/gemwallet/android/features/wallet/viewmodels/LocalStore.kt rename to android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/LocalStore.kt index 9cab480aa9..083c8a7433 100644 --- a/android/features/wallet-details/viewmodels/src/main/kotlin/com/gemwallet/android/features/wallet/viewmodels/LocalStore.kt +++ b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/LocalStore.kt @@ -1,4 +1,4 @@ -package com.gemwallet.android.features.wallet.viewmodels +package com.gemwallet.android.data.service.store import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext @@ -7,19 +7,17 @@ import java.util.UUID import javax.inject.Inject class LocalStore @Inject constructor( - @ApplicationContext private val context: Context, + @param:ApplicationContext private val context: Context, ) { - fun save(data: ByteArray, extension: String): String { val fileName = "${UUID.randomUUID()}.$extension" File(context.filesDir, fileName).writeBytes(data) return fileName } - fun remove(fileName: String?) { - if (fileName.isNullOrEmpty()) { - return - } - File(context.filesDir, fileName).takeIf { it.exists() }?.delete() + fun remove(fileName: String?): Boolean { + if (fileName.isNullOrEmpty()) return true + val file = File(context.filesDir, fileName) + return !file.exists() || file.delete() } } diff --git a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/ChainConverters.kt b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/ChainConverters.kt index 03cce0b6da..dc80912e87 100644 --- a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/ChainConverters.kt +++ b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/ChainConverters.kt @@ -1,6 +1,7 @@ package com.gemwallet.android.data.service.store.database import androidx.room.TypeConverter +import com.gemwallet.android.ext.find import com.gemwallet.android.serializer.jsonEncoder import com.wallet.core.primitives.Chain import kotlinx.serialization.builtins.ListSerializer @@ -13,7 +14,7 @@ class ChainConverters { @TypeConverter fun toChain(value: String): Chain { - return Chain.entries.firstOrNull { it.string == value } + return Chain.find(value) ?: throw IllegalArgumentException("Unknown chain: $value") } diff --git a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/GemDatabase.kt b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/GemDatabase.kt index ef88dad953..453f91f566 100644 --- a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/GemDatabase.kt +++ b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/GemDatabase.kt @@ -36,7 +36,7 @@ import com.gemwallet.android.data.service.store.database.entities.DbTxSwapMetada import com.gemwallet.android.data.service.store.database.entities.DbWallet @Database( - version = 85, + version = 86, entities = [ DbWallet::class, DbAccount::class, diff --git a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/WalletsDao.kt b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/WalletsDao.kt index 9f2175b446..a70dc59507 100644 --- a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/WalletsDao.kt +++ b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/WalletsDao.kt @@ -17,6 +17,13 @@ interface WalletsDao { """) fun getAll(): Flow>> + @Query(""" + SELECT wallets.* FROM wallets + LEFT JOIN accounts ON wallets.id = accounts.wallet_id + WHERE accounts.wallet_id IS NULL + """) + suspend fun getEmptyWallets(): List + @Query("SELECT * FROM wallets WHERE id = :id") fun getById(id: String): Flow @@ -28,4 +35,4 @@ interface WalletsDao { @Delete suspend fun delete(account: DbWallet) -} \ No newline at end of file +} diff --git a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/DatabaseModule.kt b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/DatabaseModule.kt index c8dd24dddd..8627f8e3db 100644 --- a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/DatabaseModule.kt +++ b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/DatabaseModule.kt @@ -40,7 +40,10 @@ import javax.inject.Singleton object DatabaseModule { @Singleton @Provides - fun provideRoom(@ApplicationContext context: Context, passwordStore: PasswordStore): GemDatabase = Room.databaseBuilder( + fun provideRoom( + @ApplicationContext context: Context, + passwordStore: PasswordStore, + ): GemDatabase = Room.databaseBuilder( context = context, klass = GemDatabase::class.java, name = "gem.db", @@ -89,6 +92,7 @@ object DatabaseModule { .addMigrations(Migration_82_83) .addMigrations(Migration_83_84) .addMigrations(Migration_84_85) + .addMigrations(Migration_85_86) .build() @Singleton diff --git a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_63_64.kt b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_63_64.kt index f3854e966a..9ca7b81d75 100644 --- a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_63_64.kt +++ b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_63_64.kt @@ -39,7 +39,7 @@ class Migration_63_64(context: Context, private val passwordStore: PasswordStore val walletId = accountsCursor.getString(0) val address = accountsCursor.getString(1) val chainEnumName = accountsCursor.getString(2) - val chain = Chain.entries.firstOrNull { it.name == chainEnumName } ?: continue + val chain = findLegacyChain(chainEnumName) ?: continue val account = Account( chain = chain, address = address, @@ -114,4 +114,7 @@ class Migration_63_64(context: Context, private val passwordStore: PasswordStore db.execSQL("DELETE FROM transactions WHERE walletId = ?", arrayOf(walletId)) } } + + private fun findLegacyChain(name: String): Chain? = + Chain.entries.firstOrNull { it.name == name } } diff --git a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_85_86.kt b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_85_86.kt new file mode 100644 index 0000000000..fba10ebc38 --- /dev/null +++ b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_85_86.kt @@ -0,0 +1,76 @@ +package com.gemwallet.android.data.service.store.database.di + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +object Migration_85_86 : Migration(85, 86) { + internal const val SEI = "sei" + + override fun migrate(db: SupportSQLiteDatabase) { + removeChains(db, setOf(SEI)) + } +} + +internal fun removeChains(db: SupportSQLiteDatabase, chains: Set) { + for (chain in chains) { + chainRemovalStatements(chain).forEach { statement -> + db.execSQL(statement.sql, statement.arguments.toTypedArray()) + } + } +} + +internal data class MigrationStatement( + val sql: String, + val arguments: List, +) { + override fun toString(): String = "$sql | ${arguments.joinToString()}" +} + +internal fun chainRemovalStatements(chain: String): List { + val assetIdPattern = "${chain}\\_%" + val quotedChain = "\"$chain\"" + val quotedAssetIdPrefix = "\"${chain}_" + fun assetId(column: String) = "$column = ? OR $column LIKE ? ESCAPE '\\'" + + return listOf( + MigrationStatement( + "DELETE FROM wallets_connections WHERE instr(chains, ?) > 0", + listOf(quotedChain), + ), + MigrationStatement( + "DELETE FROM in_app_notifications WHERE instr(item, ?) > 0 OR instr(item, ?) > 0", + listOf(quotedChain, quotedAssetIdPrefix), + ), + MigrationStatement( + "UPDATE asset SET associations = '[]' WHERE instr(associations, ?) > 0 OR instr(associations, ?) > 0", + listOf(quotedChain, quotedAssetIdPrefix), + ), + MigrationStatement("DELETE FROM contacts_addresses WHERE chain = ?", listOf(chain)), + MigrationStatement( + "DELETE FROM tx_swap_metadata WHERE ${assetId("from_asset_id")} OR ${assetId("to_asset_id")}", + listOf(chain, assetIdPattern, chain, assetIdPattern), + ), + MigrationStatement( + "DELETE FROM transactions WHERE ${assetId("assetId")} OR ${assetId("feeAssetId")}", + listOf(chain, assetIdPattern, chain, assetIdPattern), + ), + MigrationStatement( + "DELETE FROM prices WHERE ${assetId("asset_id")}", + listOf(chain, assetIdPattern), + ), + MigrationStatement( + "DELETE FROM price_alerts WHERE ${assetId("assetId")}", + listOf(chain, assetIdPattern), + ), + MigrationStatement( + "DELETE FROM recent_assets WHERE ${assetId("asset_id")} OR ${assetId("to_asset_id")}", + listOf(chain, assetIdPattern, chain, assetIdPattern), + ), + MigrationStatement( + "DELETE FROM banners WHERE chain = ? OR ${assetId("asset_id")}", + listOf(chain, chain, assetIdPattern), + ), + ) + listOf("nft_assets", "nft_collections", "nodes", "addresses", "accounts", "asset").map { table -> + MigrationStatement("DELETE FROM $table WHERE chain = ?", listOf(chain)) + } +} diff --git a/android/data/services/store/src/test/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_85_86Test.kt b/android/data/services/store/src/test/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_85_86Test.kt new file mode 100644 index 0000000000..8cef2c1efa --- /dev/null +++ b/android/data/services/store/src/test/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_85_86Test.kt @@ -0,0 +1,32 @@ +package com.gemwallet.android.data.service.store.database.di + +import org.junit.Assert.assertEquals +import org.junit.Test + +class Migration_85_86Test { + + @Test + fun chainRemovalStatementsUseExactSeiIdentifiers() { + assertEquals( + """ + DELETE FROM wallets_connections WHERE instr(chains, ?) > 0 | "sei" + DELETE FROM in_app_notifications WHERE instr(item, ?) > 0 OR instr(item, ?) > 0 | "sei", "sei_ + UPDATE asset SET associations = '[]' WHERE instr(associations, ?) > 0 OR instr(associations, ?) > 0 | "sei", "sei_ + DELETE FROM contacts_addresses WHERE chain = ? | sei + DELETE FROM tx_swap_metadata WHERE from_asset_id = ? OR from_asset_id LIKE ? ESCAPE '\' OR to_asset_id = ? OR to_asset_id LIKE ? ESCAPE '\' | sei, sei\_%, sei, sei\_% + DELETE FROM transactions WHERE assetId = ? OR assetId LIKE ? ESCAPE '\' OR feeAssetId = ? OR feeAssetId LIKE ? ESCAPE '\' | sei, sei\_%, sei, sei\_% + DELETE FROM prices WHERE asset_id = ? OR asset_id LIKE ? ESCAPE '\' | sei, sei\_% + DELETE FROM price_alerts WHERE assetId = ? OR assetId LIKE ? ESCAPE '\' | sei, sei\_% + DELETE FROM recent_assets WHERE asset_id = ? OR asset_id LIKE ? ESCAPE '\' OR to_asset_id = ? OR to_asset_id LIKE ? ESCAPE '\' | sei, sei\_%, sei, sei\_% + DELETE FROM banners WHERE chain = ? OR asset_id = ? OR asset_id LIKE ? ESCAPE '\' | sei, sei, sei\_% + DELETE FROM nft_assets WHERE chain = ? | sei + DELETE FROM nft_collections WHERE chain = ? | sei + DELETE FROM nodes WHERE chain = ? | sei + DELETE FROM addresses WHERE chain = ? | sei + DELETE FROM accounts WHERE chain = ? | sei + DELETE FROM asset WHERE chain = ? | sei + """.trimIndent(), + chainRemovalStatements(Migration_85_86.SEI).joinToString("\n"), + ) + } +} diff --git a/android/features/assets/viewmodels/src/main/kotlin/com/gemwallet/android/features/assets/viewmodels/NetworkAssetsViewModel.kt b/android/features/assets/viewmodels/src/main/kotlin/com/gemwallet/android/features/assets/viewmodels/NetworkAssetsViewModel.kt index f1abfadad6..0b22f82015 100644 --- a/android/features/assets/viewmodels/src/main/kotlin/com/gemwallet/android/features/assets/viewmodels/NetworkAssetsViewModel.kt +++ b/android/features/assets/viewmodels/src/main/kotlin/com/gemwallet/android/features/assets/viewmodels/NetworkAssetsViewModel.kt @@ -9,10 +9,10 @@ import com.gemwallet.android.application.asset_select.coordinators.SwitchAssetVi import com.gemwallet.android.application.assets.coordinators.HideAsset import com.gemwallet.android.application.assets.coordinators.ToggleAssetPin import com.gemwallet.android.application.session.coordinators.GetSession -import com.gemwallet.android.ui.R import com.gemwallet.android.domains.asset.aggregates.AssetInfoDataAggregate import com.gemwallet.android.domains.asset.aggregates.AssetRowNaming import com.gemwallet.android.domains.asset.aggregates.toAssetInfoDataAggregate +import com.gemwallet.android.ui.R import com.gemwallet.android.ui.models.navigation.RouteArgument import com.wallet.core.primitives.AssetId import com.wallet.core.primitives.AssetType diff --git a/android/features/settings/contacts/presents/src/main/kotlin/com/gemwallet/android/features/settings/contacts/presents/ContactChainSelectScene.kt b/android/features/settings/contacts/presents/src/main/kotlin/com/gemwallet/android/features/settings/contacts/presents/ContactChainSelectScene.kt index 00644bb10d..002e51cf45 100644 --- a/android/features/settings/contacts/presents/src/main/kotlin/com/gemwallet/android/features/settings/contacts/presents/ContactChainSelectScene.kt +++ b/android/features/settings/contacts/presents/src/main/kotlin/com/gemwallet/android/features/settings/contacts/presents/ContactChainSelectScene.kt @@ -19,7 +19,7 @@ fun ContactChainSelectScene( val chainFilter = rememberTextFieldState() val query = chainFilter.text.toString() - val chains = remember(query) { Chain.entries.toList().filter(query.lowercase()) } + val chains = remember(query) { Chain.entries.filter(query.lowercase()) } SelectChain( chains = chains, diff --git a/android/features/transfer_amount/presents/src/main/kotlin/com/gemwallet/android/features/transfer_amount/presents/ValidatorsScene.kt b/android/features/transfer_amount/presents/src/main/kotlin/com/gemwallet/android/features/transfer_amount/presents/ValidatorsScene.kt index 9c70eca03c..05e2965235 100644 --- a/android/features/transfer_amount/presents/src/main/kotlin/com/gemwallet/android/features/transfer_amount/presents/ValidatorsScene.kt +++ b/android/features/transfer_amount/presents/src/main/kotlin/com/gemwallet/android/features/transfer_amount/presents/ValidatorsScene.kt @@ -69,7 +69,7 @@ fun PreviewValidatorsScene() { recommended = emptyList(), validators = listOf( DelegationValidator( - chain = Chain.Sei, + chain = Chain.Cosmos, id = "some_validator_id", name = "Castlenode", isActive = true, @@ -78,7 +78,7 @@ fun PreviewValidatorsScene() { providerType = StakeProviderType.Stake, ), DelegationValidator( - chain = Chain.Sei, + chain = Chain.Cosmos, id = "some_validator_id_1", name = "Ubik Capital 0%Fee", isActive = true, @@ -87,7 +87,7 @@ fun PreviewValidatorsScene() { providerType = StakeProviderType.Stake, ), DelegationValidator( - chain = Chain.Sei, + chain = Chain.Cosmos, id = "some_validator_id_2", name = "Virtual Hive", isActive = true, @@ -102,4 +102,4 @@ fun PreviewValidatorsScene() { onSelect = {}, ) } -} \ No newline at end of file +} diff --git a/android/features/wallet-details/viewmodels/build.gradle.kts b/android/features/wallet-details/viewmodels/build.gradle.kts index 4e774d1c8a..c43a1e4389 100644 --- a/android/features/wallet-details/viewmodels/build.gradle.kts +++ b/android/features/wallet-details/viewmodels/build.gradle.kts @@ -43,6 +43,7 @@ dependencies { api(project(":ui-models")) implementation(project(":ui")) implementation(project(":data:repositories")) + implementation(project(":data:services:store")) implementation(libs.ktx.core) implementation(libs.lifecycle.runtime.ktx) @@ -56,4 +57,4 @@ dependencies { testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) -} \ No newline at end of file +} diff --git a/android/features/wallet-details/viewmodels/src/main/kotlin/com/gemwallet/android/features/wallet/viewmodels/WalletAvatarService.kt b/android/features/wallet-details/viewmodels/src/main/kotlin/com/gemwallet/android/features/wallet/viewmodels/WalletAvatarService.kt index 60032f610f..2a4df5f92a 100644 --- a/android/features/wallet-details/viewmodels/src/main/kotlin/com/gemwallet/android/features/wallet/viewmodels/WalletAvatarService.kt +++ b/android/features/wallet-details/viewmodels/src/main/kotlin/com/gemwallet/android/features/wallet/viewmodels/WalletAvatarService.kt @@ -7,6 +7,7 @@ import coil3.request.ImageRequest import coil3.request.SuccessResult import coil3.toBitmap import com.gemwallet.android.application.wallet.coordinators.SetWalletAvatar +import com.gemwallet.android.data.service.store.LocalStore import com.wallet.core.primitives.WalletId import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers @@ -16,7 +17,7 @@ import javax.inject.Singleton @Singleton class WalletAvatarService @Inject constructor( - @ApplicationContext private val context: Context, + @param:ApplicationContext private val context: Context, private val emojiRenderer: EmojiAvatarRenderer, private val localStore: LocalStore, private val setWalletAvatar: SetWalletAvatar, diff --git a/android/features/wallets/viewmodels/src/main/kotlin/com/gemwallet/android/features/wallets/viewmodels/WalletsViewModel.kt b/android/features/wallets/viewmodels/src/main/kotlin/com/gemwallet/android/features/wallets/viewmodels/WalletsViewModel.kt index bb1c5d3038..4b5a98a0a8 100644 --- a/android/features/wallets/viewmodels/src/main/kotlin/com/gemwallet/android/features/wallets/viewmodels/WalletsViewModel.kt +++ b/android/features/wallets/viewmodels/src/main/kotlin/com/gemwallet/android/features/wallets/viewmodels/WalletsViewModel.kt @@ -30,7 +30,7 @@ class WalletsViewModel @Inject constructor( } fun deleteWallet(walletId: WalletId, onBoard: () -> Unit) = viewModelScope.launch { - deleteWallet.deleteWallet(walletId, onBoard) {} + deleteWallet.deleteWallet(walletId, onBoard) } fun togglePin(walletId: WalletId) = viewModelScope.launch(Dispatchers.IO) { diff --git a/android/gemcore/src/main/kotlin/com/gemwallet/android/application/wallet/coordinators/DeleteWallet.kt b/android/gemcore/src/main/kotlin/com/gemwallet/android/application/wallet/coordinators/DeleteWallet.kt index 373b634f15..4db19040f9 100644 --- a/android/gemcore/src/main/kotlin/com/gemwallet/android/application/wallet/coordinators/DeleteWallet.kt +++ b/android/gemcore/src/main/kotlin/com/gemwallet/android/application/wallet/coordinators/DeleteWallet.kt @@ -5,7 +5,9 @@ import com.wallet.core.primitives.WalletId interface DeleteWallet { suspend fun deleteWallet( walletId: WalletId, - onBoard: () -> Unit, - onComplete: () -> Unit, - ) + onBoard: () -> Unit = {}, + onComplete: () -> Unit = {}, + ): Boolean + + suspend fun deleteEmptyWallets(): Boolean } diff --git a/android/gemcore/src/main/kotlin/com/gemwallet/android/domains/asset/IconUrlGeneration.kt b/android/gemcore/src/main/kotlin/com/gemwallet/android/domains/asset/IconUrlGeneration.kt index 6c932cbd27..f27ef4fceb 100644 --- a/android/gemcore/src/main/kotlin/com/gemwallet/android/domains/asset/IconUrlGeneration.kt +++ b/android/gemcore/src/main/kotlin/com/gemwallet/android/domains/asset/IconUrlGeneration.kt @@ -1,5 +1,6 @@ package com.gemwallet.android.domains.asset +import com.gemwallet.android.Constants import com.gemwallet.android.ext.asset import com.gemwallet.android.ext.toIdentifier import com.gemwallet.android.ext.twoSubtokenIds @@ -8,7 +9,6 @@ import com.wallet.core.primitives.Asset import com.wallet.core.primitives.AssetId import com.wallet.core.primitives.AssetSubtype import com.wallet.core.primitives.AssetType -import com.gemwallet.android.Constants import com.wallet.core.primitives.Chain import com.wallet.core.primitives.DelegationValidator import com.wallet.core.primitives.FiatProvider @@ -19,13 +19,7 @@ import uniffi.gemstone.SwapperProvider //fun Int.getDrawableUri() = "android.resource://com.gemwallet.android/drawable/$this" -fun Chain.getIconUrl(): String { - val icon = when (this) { - Chain.SeiEvm -> Chain.Sei.string - else -> string - } - return "file:///android_asset/chains/icons/${icon}.svg" -} +fun Chain.getIconUrl(): String = "file:///android_asset/chains/icons/${string}.svg" fun AssetId.getIconUrl(): String = when { tokenId.isNullOrEmpty() -> when (chain) { diff --git a/android/gemcore/src/main/kotlin/com/gemwallet/android/ext/AssetId.kt b/android/gemcore/src/main/kotlin/com/gemwallet/android/ext/AssetId.kt index 396fbba20b..c75aa708e5 100644 --- a/android/gemcore/src/main/kotlin/com/gemwallet/android/ext/AssetId.kt +++ b/android/gemcore/src/main/kotlin/com/gemwallet/android/ext/AssetId.kt @@ -13,7 +13,7 @@ fun AssetId.type() = if (tokenId.isNullOrEmpty()) AssetSubtype.NATIVE else Asset fun String.toAssetId(): AssetId? { val components = split("_", limit = 2) val chainId = components.firstOrNull() ?: return null - val chain = Chain.entries.firstOrNull { it.string == chainId } ?: return null + val chain = Chain.find(chainId) ?: return null val token = if (components.size > 1) components[1] else null return AssetId(chain, token) } @@ -22,4 +22,4 @@ fun AssetId.twoSubtokenIds(): Pair? = tokenId ?.split("::") ?.takeIf { it.size >= 2 }?.let { Pair(it[0], it[1]) - } \ No newline at end of file + } diff --git a/android/gemcore/src/main/kotlin/com/gemwallet/android/ext/Chain.kt b/android/gemcore/src/main/kotlin/com/gemwallet/android/ext/Chain.kt index b4fe3590eb..139a35f1e3 100644 --- a/android/gemcore/src/main/kotlin/com/gemwallet/android/ext/Chain.kt +++ b/android/gemcore/src/main/kotlin/com/gemwallet/android/ext/Chain.kt @@ -16,8 +16,11 @@ import uniffi.gemstone.validateAddress import uniffi.gemstone.checksumAddress as gemstoneChecksumAddress import java.math.BigInteger +private val config by lazy { Config() } +private val allChains: List = Chain.entries + private val chainAssetCache: Map by lazy { - Chain.entries.associateWith { chain -> + allChains.associateWith { chain -> val wrapper = uniffi.gemstone.chainAssetWrapper(chain.string) ChainAsset( asset = wrapper.asset.toDTO(), @@ -26,10 +29,16 @@ private val chainAssetCache: Map by lazy { } } +private val chainConfigCache by lazy { + allChains.associateWith { chain -> config.getChainConfig(chain.string) } +} + private fun Chain.chainAsset(): ChainAsset { return chainAssetCache[this] ?: throw IllegalArgumentException("Unsupported chain: $string") } +private fun Chain.chainConfig() = chainConfigCache[this] ?: throw IllegalArgumentException("Unsupported chain: $string") + fun Chain.assetType(): AssetType? { return when (this) { Chain.OpBNB, @@ -76,7 +85,6 @@ fun Chain.assetType(): AssetType? { Chain.Osmosis, Chain.Celestia, Chain.Injective, - Chain.Sei, Chain.Noble, Chain.Celo, Chain.Bitcoin, @@ -107,19 +115,19 @@ fun Chain.toEVM(): EVMChain? { return EVMChain.entries.firstOrNull { it.string == string } } -fun Chain.getReserveBalance(): BigInteger = Config().getChainConfig(this.string).accountActivationFee?.toBigInteger() ?: BigInteger.ZERO +fun Chain.getReserveBalance(): BigInteger = chainConfig().accountActivationFee?.toBigInteger() ?: BigInteger.ZERO -fun Chain.getTokenActivationFee(): BigInteger = Config().getChainConfig(this.string).tokenActivationFee?.toBigInteger() ?: BigInteger.ZERO +fun Chain.getTokenActivationFee(): BigInteger = chainConfig().tokenActivationFee?.toBigInteger() ?: BigInteger.ZERO -fun Chain.getReserveBalanceUrl(): String? = Config().getChainConfig(this.string).accountActivationFeeUrl +fun Chain.getReserveBalanceUrl(): String? = chainConfig().accountActivationFeeUrl -fun Chain.getMinimumAccountBalance(): Long = Config().getChainConfig(this.string).minimumAccountBalance?.toLong() ?: 0L +fun Chain.getMinimumAccountBalance(): Long = chainConfig().minimumAccountBalance?.toLong() ?: 0L -fun Chain.isStakeSupported(): Boolean = Config().getChainConfig(this.string).isStakeSupported +fun Chain.isStakeSupported(): Boolean = chainConfig().isStakeSupported -fun Chain.isNftSupported(): Boolean = Config().getChainConfig(this.string).isNftSupported +fun Chain.isNftSupported(): Boolean = chainConfig().isNftSupported -fun Chain.isDefiSupported(): Boolean = Config().getChainConfig(this.string).isDefiSupported +fun Chain.isDefiSupported(): Boolean = chainConfig().isDefiSupported fun Chain.asset(): Asset { return chainAsset().asset @@ -129,11 +137,7 @@ fun Chain.networkName(): String { return chainAsset().networkName } -fun Chain.Companion.findByString(value: String): Chain? { - return Chain.entries.firstOrNull { it.string == value } -} - -fun Chain.Companion.available() = Chain.entries.toSet() +fun Chain.Companion.find(value: String): Chain? = allChains.firstOrNull { it.string == value } fun List.filter(query: String): List { return if (query.isBlank()) this else filter { chain -> @@ -169,7 +173,6 @@ fun Chain.toChainType(): ChainType { Chain.Osmosis, Chain.Celestia, Chain.Injective, - Chain.Sei, Chain.Noble, Chain.Cosmos -> ChainType.Cosmos Chain.AvalancheC, @@ -206,12 +209,12 @@ fun Chain.toChainType(): ChainType { fun Chain.getNetworkId(): String { - return Config().getChainConfig(string).networkId + return chainConfig().networkId } fun Chain.isSwapSupport(): Boolean { return try { - Config().getChainConfig(string).isSwapSupported + chainConfig().isSwapSupported } catch (_: Throwable) { false } @@ -220,10 +223,10 @@ fun Chain.isSwapSupport(): Boolean { fun Chain.Companion.swapSupport() = Chain.entries.filter { it.isSwapSupport() } fun Chain.feeUnitType() = FeeUnitType.entries.firstOrNull { - it.string == Config().getChainConfig(string).feeUnitType + it.string == chainConfig().feeUnitType } -fun Chain.isMemoSupport() = Config().getChainConfig(string).isMemoSupported +fun Chain.isMemoSupport() = chainConfig().isMemoSupported fun Chain.isValidAddress(address: String): Boolean = validateAddress(checksumAddress(address), string) @@ -232,7 +235,7 @@ fun Chain.checksumAddress(address: String): String = gemstoneChecksumAddress(add fun Chain.isPrivateKeyImportSupported(): Boolean = supportsPrivateKeyImport(string) fun uniffi.gemstone.Chain.toChain(): Chain? { - return Chain.entries.firstOrNull { it.string == this } + return Chain.find(this) } val Chain.Companion.referralChain: Chain get() = Chain.Ethereum diff --git a/android/gemcore/src/main/kotlin/com/gemwallet/android/ext/StakeChain.kt b/android/gemcore/src/main/kotlin/com/gemwallet/android/ext/StakeChain.kt index 3b920cce16..8a57684b21 100644 --- a/android/gemcore/src/main/kotlin/com/gemwallet/android/ext/StakeChain.kt +++ b/android/gemcore/src/main/kotlin/com/gemwallet/android/ext/StakeChain.kt @@ -29,7 +29,6 @@ fun StakeChain.freezed(): Boolean = when (this) { StakeChain.Tron -> true StakeChain.Cosmos, StakeChain.Injective, - StakeChain.Sei, StakeChain.Celestia, StakeChain.Osmosis, StakeChain.Solana, @@ -45,7 +44,6 @@ fun StakeChain.toGemStakeChain(): GemStakeChain = when (this) { StakeChain.Cosmos -> GemStakeChain.COSMOS StakeChain.Osmosis -> GemStakeChain.OSMOSIS StakeChain.Injective -> GemStakeChain.INJECTIVE - StakeChain.Sei -> GemStakeChain.SEI StakeChain.Celestia -> GemStakeChain.CELESTIA StakeChain.Ethereum -> GemStakeChain.ETHEREUM StakeChain.Solana -> GemStakeChain.SOLANA @@ -55,4 +53,4 @@ fun StakeChain.toGemStakeChain(): GemStakeChain = when (this) { StakeChain.Tron -> GemStakeChain.TRON StakeChain.Aptos -> GemStakeChain.APTOS StakeChain.HyperCore -> GemStakeChain.HYPER_CORE -} \ No newline at end of file +} diff --git a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/TransactionId.kt b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/TransactionId.kt index 9eeabd570c..8c03a6bcd0 100644 --- a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/TransactionId.kt +++ b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/TransactionId.kt @@ -1,5 +1,6 @@ package com.wallet.core.primitives +import com.gemwallet.android.ext.find import com.gemwallet.android.serializer.TransactionIdSerializer import kotlinx.serialization.Serializable @@ -15,7 +16,7 @@ data class TransactionId( return null } - val chain = Chain.entries.firstOrNull { it.string == components[0] } ?: return null + val chain = Chain.find(components[0]) ?: return null return TransactionId(chain, components[1]) } } diff --git a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/generated/Chain.kt b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/generated/Chain.kt index 2b7d4efa02..453201e304 100644 --- a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/generated/Chain.kt +++ b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/generated/Chain.kt @@ -63,8 +63,6 @@ enum class Chain(val string: String) { Celestia("celestia"), @SerialName("injective") Injective("injective"), - @SerialName("sei") - Sei("sei"), @SerialName("seievm") SeiEvm("seievm"), @SerialName("manta") diff --git a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/generated/ChainCosmos.kt b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/generated/ChainCosmos.kt index 370a8555e8..dce4933468 100644 --- a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/generated/ChainCosmos.kt +++ b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/generated/ChainCosmos.kt @@ -21,8 +21,6 @@ enum class CosmosChain(val string: String) { Mayachain("mayachain"), @SerialName("injective") Injective("injective"), - @SerialName("sei") - Sei("sei"), @SerialName("noble") Noble("noble"), } diff --git a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/generated/ChainStake.kt b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/generated/ChainStake.kt index d18ff56b03..6408f6ae36 100644 --- a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/generated/ChainStake.kt +++ b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/generated/ChainStake.kt @@ -15,8 +15,6 @@ enum class StakeChain(val string: String) { Osmosis("osmosis"), @SerialName("injective") Injective("injective"), - @SerialName("sei") - Sei("sei"), @SerialName("celestia") Celestia("celestia"), @SerialName("ethereum") diff --git a/android/gemcore/src/test/kotlin/com/gemwallet/android/ext/ChainExtTest.kt b/android/gemcore/src/test/kotlin/com/gemwallet/android/ext/ChainExtTest.kt index d78ecf15fb..44998eae4b 100644 --- a/android/gemcore/src/test/kotlin/com/gemwallet/android/ext/ChainExtTest.kt +++ b/android/gemcore/src/test/kotlin/com/gemwallet/android/ext/ChainExtTest.kt @@ -16,7 +16,7 @@ class ChainExtTest { assertEquals(AssetType.ERC20, Chain.SeiEvm.assetType()) assertEquals(EVMChain.SeiEvm, Chain.SeiEvm.toEVM()) assertEquals(ChainType.Ethereum, Chain.SeiEvm.toChainType()) - assertEquals("file:///android_asset/chains/icons/sei.svg", Chain.SeiEvm.getIconUrl()) + assertEquals("file:///android_asset/chains/icons/seievm.svg", Chain.SeiEvm.getIconUrl()) } @Test diff --git a/android/gemcore/src/testFixtures/kotlin/com/gemwallet/android/testkit/GemstoneTestKeys.kt b/android/gemcore/src/testFixtures/kotlin/com/gemwallet/android/testkit/GemstoneTestKeys.kt index 250b900ff9..d8b52f6778 100644 --- a/android/gemcore/src/testFixtures/kotlin/com/gemwallet/android/testkit/GemstoneTestKeys.kt +++ b/android/gemcore/src/testFixtures/kotlin/com/gemwallet/android/testkit/GemstoneTestKeys.kt @@ -1,6 +1,7 @@ package com.gemwallet.android.testkit import android.content.Context +import com.gemwallet.android.ext.find import com.gemwallet.android.ext.words import com.wallet.core.primitives.Account import com.wallet.core.primitives.Chain @@ -49,6 +50,6 @@ private fun GemKeystoreAccount.toAccount(): Account { } private fun String.toPrimitiveChain(): Chain { - return Chain.entries.firstOrNull { it.string == this } + return Chain.find(this) ?: throw IllegalArgumentException("Unsupported chain: $this") } diff --git a/android/ui/src/main/kotlin/com/gemwallet/android/ui/components/list_item/ValidatorItem.kt b/android/ui/src/main/kotlin/com/gemwallet/android/ui/components/list_item/ValidatorItem.kt index abcc0511e0..4c4375a904 100644 --- a/android/ui/src/main/kotlin/com/gemwallet/android/ui/components/list_item/ValidatorItem.kt +++ b/android/ui/src/main/kotlin/com/gemwallet/android/ui/components/list_item/ValidatorItem.kt @@ -91,7 +91,7 @@ fun PreviewValidatorItem() { WalletTheme { ValidatorItem( data = DelegationValidator( - chain = Chain.Sei, + chain = Chain.Cosmos, id = "some_validator_id", name = "Castlenode", isActive = true, @@ -112,7 +112,7 @@ fun PreviewValidatorItemSelected() { WalletTheme { ValidatorItem( data = DelegationValidator( - chain = Chain.Sei, + chain = Chain.Cosmos, id = "some_validator_id", name = "Castlenode", isActive = true, diff --git a/core/apps/daemon/src/parser/mod.rs b/core/apps/daemon/src/parser/mod.rs index fd828407ff..052bcccee5 100644 --- a/core/apps/daemon/src/parser/mod.rs +++ b/core/apps/daemon/src/parser/mod.rs @@ -219,15 +219,16 @@ pub async fn run(settings: Settings, chain: Option, health_state: Arc = if let Some(chain) = chain { - vec![chain] - } else { - database + let chains: Vec = match chain { + Some(chain) if !chain.is_disabled() => vec![chain], + Some(_) => Vec::new(), + None => database .parser_state()? .get_parser_states()? .into_iter() .flat_map(|x| Chain::from_str(x.chain.as_ref())) - .collect() + .filter(|chain| !chain.is_disabled()) + .collect(), }; info_with_fields!("parser init", chains = format!("{:?}", chains)); diff --git a/core/apps/dynode/src/config/mod.rs b/core/apps/dynode/src/config/mod.rs index 325cac5aec..2e1a6cbfb1 100644 --- a/core/apps/dynode/src/config/mod.rs +++ b/core/apps/dynode/src/config/mod.rs @@ -288,6 +288,7 @@ pub fn load_config() -> Result<(NodeConfig, HashMap), Config .collect::, _>>()? .into_iter() .flat_map(|cf| cf.chains) + .filter(|config| !config.chain.is_disabled()) .map(|c| (c.chain, c)) .collect(); diff --git a/core/crates/gem_derivation/src/mnemonic/tests/derivation.rs b/core/crates/gem_derivation/src/mnemonic/tests/derivation.rs index 895a5c6cba..98f1d077fb 100644 --- a/core/crates/gem_derivation/src/mnemonic/tests/derivation.rs +++ b/core/crates/gem_derivation/src/mnemonic/tests/derivation.rs @@ -88,7 +88,7 @@ fn test_derive_accounts_public_key() { #[test] fn test_mnemonic_derivation_coverage_for_every_chain() { - for chain in Chain::all() { + for chain in Chain::all_with_disabled() { let expected = expected_derivation(chain); let accounts = derive_accounts_from_mnemonic(PHRASE, vec![chain]).unwrap(); assert_eq!(accounts.len(), 1); diff --git a/core/crates/gem_evm/src/uniswap/deployment/v3.rs b/core/crates/gem_evm/src/uniswap/deployment/v3.rs index 86c4f7da46..5f69c06d2a 100644 --- a/core/crates/gem_evm/src/uniswap/deployment/v3.rs +++ b/core/crates/gem_evm/src/uniswap/deployment/v3.rs @@ -302,7 +302,7 @@ mod tests { #[test] fn test_deployment_addresses_are_checksummed() { - for chain in Chain::all() { + for chain in Chain::all_with_disabled() { let deployments = [ get_uniswap_router_deployment_by_chain(&chain), get_pancakeswap_router_deployment_by_chain(&chain), diff --git a/core/crates/gem_evm/src/uniswap/deployment/v4.rs b/core/crates/gem_evm/src/uniswap/deployment/v4.rs index 9767d25434..3b3abc7f45 100644 --- a/core/crates/gem_evm/src/uniswap/deployment/v4.rs +++ b/core/crates/gem_evm/src/uniswap/deployment/v4.rs @@ -175,7 +175,7 @@ mod tests { #[test] fn test_deployment_addresses_are_checksummed() { - for chain in Chain::all() { + for chain in Chain::all_with_disabled() { let Some(deployment) = get_uniswap_deployment_by_chain(&chain) else { continue; }; diff --git a/core/crates/primitives/src/chain.rs b/core/crates/primitives/src/chain.rs index 7a4f8e7375..6b5c97fe56 100644 --- a/core/crates/primitives/src/chain.rs +++ b/core/crates/primitives/src/chain.rs @@ -39,6 +39,7 @@ pub enum Chain { Gnosis, Celestia, Injective, + #[typeshare(skip)] Sei, SeiEvm, Manta, @@ -153,6 +154,10 @@ impl Chain { self.config().is_defi_supported } + pub fn is_disabled(&self) -> bool { + self.config().is_disabled() + } + // milliseconds pub fn block_time(&self) -> u32 { self.config().block_time @@ -163,11 +168,15 @@ impl Chain { } pub fn all() -> Vec { - Self::iter().collect::>() + Self::iter().filter(|chain| !chain.is_disabled()).collect() + } + + pub fn all_with_disabled() -> Vec { + Self::iter().collect() } pub fn stakeable() -> Vec { - Self::all().into_iter().filter(|x| x.is_stake_supported()).collect() + Self::all().into_iter().filter(|chain| chain.is_stake_supported()).collect() } pub fn perpetual_chains() -> Vec { @@ -179,6 +188,12 @@ impl Chain { mod tests { use super::*; + #[test] + fn test_all() { + assert_eq!(Chain::all().into_iter().filter(Chain::is_disabled).collect::>(), vec![]); + assert_eq!(Chain::all_with_disabled().into_iter().filter(Chain::is_disabled).collect::>(), vec![Chain::Sei]); + } + #[test] fn test_mayachain_swap_not_supported() { assert!(!Chain::Mayachain.is_swap_supported()); diff --git a/core/crates/primitives/src/chain_config.rs b/core/crates/primitives/src/chain_config.rs index 4bdde2d962..c78fe4a730 100644 --- a/core/crates/primitives/src/chain_config.rs +++ b/core/crates/primitives/src/chain_config.rs @@ -50,6 +50,14 @@ pub struct ChainConfig { pub stake: Option, } +const DISABLED_CHAINS: &[Chain] = &[Chain::Sei]; + +impl ChainConfig { + pub fn is_disabled(&self) -> bool { + DISABLED_CHAINS.contains(&self.chain) + } +} + // Centralized chain configurations. Add new chains here. static CHAIN_CONFIGS: LazyLock> = LazyLock::new(|| { vec![ diff --git a/core/crates/primitives/src/chain_cosmos.rs b/core/crates/primitives/src/chain_cosmos.rs index ee17bd3d4c..948f68cb57 100644 --- a/core/crates/primitives/src/chain_cosmos.rs +++ b/core/crates/primitives/src/chain_cosmos.rs @@ -17,6 +17,7 @@ pub enum CosmosChain { Thorchain, Mayachain, Injective, + #[typeshare(skip)] Sei, Noble, } diff --git a/core/crates/primitives/src/chain_stake.rs b/core/crates/primitives/src/chain_stake.rs index 99f3894950..42b257453d 100644 --- a/core/crates/primitives/src/chain_stake.rs +++ b/core/crates/primitives/src/chain_stake.rs @@ -15,6 +15,7 @@ pub enum StakeChain { Cosmos, Osmosis, Injective, + #[typeshare(skip)] Sei, Celestia, Ethereum, diff --git a/core/crates/settings_chain/src/node_check.rs b/core/crates/settings_chain/src/node_check.rs index 3325eac495..75299d751c 100644 --- a/core/crates/settings_chain/src/node_check.rs +++ b/core/crates/settings_chain/src/node_check.rs @@ -106,7 +106,7 @@ mod tests { #[test] fn test_wallet_node_check_request() { - for chain in Chain::all() { + for chain in Chain::all_with_disabled() { let NodeCheckRequest::Wallet { address, transaction_id } = node_check_request(chain, NodeCheckProfile::Wallet) else { panic!("wallet profile expected for {chain}"); }; diff --git a/ios/Packages/FeatureServices/AppService/OnstartService.swift b/ios/Packages/FeatureServices/AppService/OnstartService.swift index 6d9be1f5b8..ae838f60fc 100644 --- a/ios/Packages/FeatureServices/AppService/OnstartService.swift +++ b/ios/Packages/FeatureServices/AppService/OnstartService.swift @@ -60,6 +60,12 @@ public struct OnstartService: Sendable { } catch { debugLog("v3 keystore migration could not enumerate wallets: \(error)") } + do { + try await walletService.deleteEmptyWallets() + } catch { + debugLog("wallet cleanup error: \(error)") + return + } do { try walletService.setup(chains: AssetConfiguration.allChains) } catch { diff --git a/ios/Packages/FeatureServices/BalanceService/Tests/BalanceServiceTests.swift b/ios/Packages/FeatureServices/BalanceService/Tests/BalanceServiceTests.swift index dffcf34600..86001baf13 100644 --- a/ios/Packages/FeatureServices/BalanceService/Tests/BalanceServiceTests.swift +++ b/ios/Packages/FeatureServices/BalanceService/Tests/BalanceServiceTests.swift @@ -13,32 +13,28 @@ import StoreTestKit import Testing struct BalanceServiceTests { - // `seievm` (Chain.seiEvm) has `sei` (Chain.sei) as a rawValue prefix, so a - // string-prefix asset match pulls a Sei-EVM token into the Sei account's - // token fetch. Matching by typed chain must skip it. Real colliding pairs in - // the current chain set: bitcoin/bitcoincash and sei/seievm. @Test func updateBalanceSkipsPrefixCollidingChainToken() async throws { - let sei = Chain.sei.assetId - let seiEvmToken = AssetId(chain: .seiEvm, tokenId: "0xtoken") + let bitcoin = Chain.bitcoin.assetId + let bitcoinCashToken = AssetId(chain: .bitcoinCash, tokenId: "token") - let db = DB.mockWithChains([.sei, .seiEvm]) - try AssetStore(db: db).add(assets: [.mock(asset: .mock(id: seiEvmToken))]) - let wallet = Wallet.mock(accounts: [.mock(chain: .sei, address: "sei-address")]) + let db = DB.mockWithChains([.bitcoin, .bitcoinCash]) + try AssetStore(db: db).add(assets: [.mock(asset: .mock(id: bitcoinCashToken))]) + let wallet = Wallet.mock(accounts: [.mock(chain: .bitcoin, address: "bitcoin-address")]) try WalletStore(db: db).addWallet(wallet) let balanceStore = BalanceStore.mock(db: db) let chainService = ChainServiceMock() - chainService.coinBalances["sei-address"] = AssetBalance(assetId: sei, balance: .mock(available: 100)) - chainService.tokenBalances["sei-address"] = [AssetBalance(assetId: seiEvmToken, balance: .mock(available: 50))] + chainService.coinBalances["bitcoin-address"] = AssetBalance(assetId: bitcoin, balance: .mock(available: 100)) + chainService.tokenBalances["bitcoin-address"] = [AssetBalance(assetId: bitcoinCashToken, balance: .mock(available: 50))] let service = BalanceService.mock( balanceStore: balanceStore, assetsService: .mock(assetStore: .mock(db: db), balanceStore: balanceStore), chainServiceFactory: ChainServiceFactoryMock(chainService: chainService), ) - await service.updateBalance(for: wallet, assetIds: [sei, seiEvmToken]) + await service.updateBalance(for: wallet, assetIds: [bitcoin, bitcoinCashToken]) - #expect(try balanceStore.getBalance(walletId: wallet.id, assetId: sei)?.available == 100) - #expect(try balanceStore.getBalance(walletId: wallet.id, assetId: seiEvmToken) == nil) + #expect(try balanceStore.getBalance(walletId: wallet.id, assetId: bitcoin)?.available == 100) + #expect(try balanceStore.getBalance(walletId: wallet.id, assetId: bitcoinCashToken) == nil) } } diff --git a/ios/Packages/FeatureServices/WalletService/Tests/WalletServiceTests.swift b/ios/Packages/FeatureServices/WalletService/Tests/WalletServiceTests.swift index b3e36e567d..5dece3d5bf 100644 --- a/ios/Packages/FeatureServices/WalletService/Tests/WalletServiceTests.swift +++ b/ios/Packages/FeatureServices/WalletService/Tests/WalletServiceTests.swift @@ -6,6 +6,7 @@ import Observation import Preferences import PreferencesTestKit import Primitives +import PrimitivesTestKit import Store import StoreTestKit import Testing @@ -221,6 +222,22 @@ struct WalletServiceTests { } } + @Test + func deleteEmptyWalletsUsesNormalDeletion() async throws { + let walletStore = WalletStore.mock(db: .mockWithChains([.ethereum])) + let wallet = Wallet.mock(id: .multicoin(address: "empty")) + let walletPreferences = WalletPreferences(walletId: wallet.id) + defer { walletPreferences.clear() } + let service = WalletService.mock(walletStore: walletStore) + try walletStore.addWallet(wallet) + walletPreferences.completeInitialLoadAssets = true + + try await service.deleteEmptyWallets() + + #expect(try walletStore.getWallet(id: wallet.id) == nil) + #expect(!walletPreferences.completeInitialLoadAssets) + } + @Test func loadOrCreateWalletMarksSubscriptionsDirty() async throws { let rawPreferences = Preferences.mock() diff --git a/ios/Packages/FeatureServices/WalletService/WalletService.swift b/ios/Packages/FeatureServices/WalletService/WalletService.swift index 5e5d93a501..9479bc7c91 100644 --- a/ios/Packages/FeatureServices/WalletService/WalletService.swift +++ b/ios/Packages/FeatureServices/WalletService/WalletService.swift @@ -87,6 +87,12 @@ public struct WalletService: Sendable { preferences.invalidateSubscriptions() } + public func deleteEmptyWallets() async throws { + for wallet in try walletStore.getWallets() where wallet.accounts.isEmpty { + try await delete(wallet) + } + } + public func setup(chains: [Chain]) throws { let wallets = walletSessionService.wallets.filter { $0.type == .multicoin } guard !wallets.isEmpty else { return } diff --git a/ios/Packages/GemstonePrimitives/Sources/Extensions/GemStakeChain+GemstonePrimitives.swift b/ios/Packages/GemstonePrimitives/Sources/Extensions/GemStakeChain+GemstonePrimitives.swift index 0216e1bd61..fb43608a7d 100644 --- a/ios/Packages/GemstonePrimitives/Sources/Extensions/GemStakeChain+GemstonePrimitives.swift +++ b/ios/Packages/GemstonePrimitives/Sources/Extensions/GemStakeChain+GemstonePrimitives.swift @@ -11,7 +11,6 @@ public extension StakeChain { case .cosmos: .cosmos case .osmosis: .osmosis case .injective: .injective - case .sei: .sei case .celestia: .celestia case .ethereum: .ethereum case .solana: .solana diff --git a/ios/Packages/Keystore/Tests/LocalKeystoreTests.swift b/ios/Packages/Keystore/Tests/LocalKeystoreTests.swift index 460c3a2abf..eff715d774 100644 --- a/ios/Packages/Keystore/Tests/LocalKeystoreTests.swift +++ b/ios/Packages/Keystore/Tests/LocalKeystoreTests.swift @@ -193,8 +193,6 @@ struct LocalKeystoreTests { "celestia142j9u5eaduzd7faumygud6ruhdwme98qpwmfv7" case .injective: "inj13u6g7vqgw074mgmf2ze2cadzvkz9snlwcrtq8a" - case .sei: - "sei142j9u5eaduzd7faumygud6ruhdwme98qagm0sj" case .noble: "noble142j9u5eaduzd7faumygud6ruhdwme98qc8l3wa" case .near: diff --git a/ios/Packages/Primitives/Sources/Extensions/CosmosChain+Primitives.swift b/ios/Packages/Primitives/Sources/Extensions/CosmosChain+Primitives.swift index 377f86d9d1..ead4797897 100644 --- a/ios/Packages/Primitives/Sources/Extensions/CosmosChain+Primitives.swift +++ b/ios/Packages/Primitives/Sources/Extensions/CosmosChain+Primitives.swift @@ -18,7 +18,6 @@ public extension CosmosChain { case .osmosis: CosmosDenom.uosmo case .celestia: CosmosDenom.utia case .injective: CosmosDenom.inj - case .sei: CosmosDenom.usei case .noble: CosmosDenom.uusdc } } diff --git a/ios/Packages/Primitives/Sources/Generated/Chain.swift b/ios/Packages/Primitives/Sources/Generated/Chain.swift index 9f64138ffa..712006299a 100644 --- a/ios/Packages/Primitives/Sources/Generated/Chain.swift +++ b/ios/Packages/Primitives/Sources/Generated/Chain.swift @@ -32,7 +32,6 @@ public enum Chain: String, Codable, CaseIterable, Equatable, Hashable, Sendable case gnosis case celestia case injective - case sei case seiEvm = "seievm" case manta case blast diff --git a/ios/Packages/Primitives/Sources/Generated/ChainCosmos.swift b/ios/Packages/Primitives/Sources/Generated/ChainCosmos.swift index 7c7c833182..663ee5bbc0 100644 --- a/ios/Packages/Primitives/Sources/Generated/ChainCosmos.swift +++ b/ios/Packages/Primitives/Sources/Generated/ChainCosmos.swift @@ -11,7 +11,6 @@ public enum CosmosChain: String, Codable, CaseIterable, Equatable, Sendable { case thorchain case mayachain case injective - case sei case noble } diff --git a/ios/Packages/Primitives/Sources/Generated/ChainStake.swift b/ios/Packages/Primitives/Sources/Generated/ChainStake.swift index b4442d3c24..44d998a790 100644 --- a/ios/Packages/Primitives/Sources/Generated/ChainStake.swift +++ b/ios/Packages/Primitives/Sources/Generated/ChainStake.swift @@ -8,7 +8,6 @@ public enum StakeChain: String, Codable, CaseIterable, Equatable, Sendable { case cosmos case osmosis case injective - case sei case celestia case ethereum case solana diff --git a/ios/Packages/PrimitivesComponents/Sources/Types/ChainImage.swift b/ios/Packages/PrimitivesComponents/Sources/Types/ChainImage.swift index 5319d8c661..33067b3e7a 100644 --- a/ios/Packages/PrimitivesComponents/Sources/Types/ChainImage.swift +++ b/ios/Packages/PrimitivesComponents/Sources/Types/ChainImage.swift @@ -48,7 +48,7 @@ public struct ChainImage: Sendable { case .gnosis: Images.Chains.gnosis case .celestia: Images.Chains.celestia case .injective: Images.Chains.injective - case .sei, .seiEvm: Images.Chains.sei + case .seiEvm: Images.Chains.seievm case .manta: Images.Chains.manta case .noble: Images.Chains.noble case .mantle: Images.Chains.mantle diff --git a/ios/Packages/PrimitivesComponents/Sources/ViewModels/BalanceViewModel.swift b/ios/Packages/PrimitivesComponents/Sources/ViewModels/BalanceViewModel.swift index 9f37659301..ff3a1b3ea5 100644 --- a/ios/Packages/PrimitivesComponents/Sources/ViewModels/BalanceViewModel.swift +++ b/ios/Packages/PrimitivesComponents/Sources/ViewModels/BalanceViewModel.swift @@ -66,7 +66,7 @@ public struct BalanceViewModel: Sendable { let amount = switch type { case .stake: switch StakeChain(rawValue: asset.chain.rawValue) { - case .celestia, .cosmos, .hyperCore, .injective, .osmosis, .sei, .smartChain, .solana, .sui, .ethereum, .aptos, .monad, .none: + case .celestia, .cosmos, .hyperCore, .injective, .osmosis, .smartChain, .solana, .sui, .ethereum, .aptos, .monad, .none: balance.staked + balance.pending case .tron: balance.frozen + balance.locked + balance.pending @@ -79,7 +79,7 @@ public struct BalanceViewModel: Sendable { public var hasStakingResources: Bool { switch StakeChain(rawValue: asset.chain.rawValue) { - case .celestia, .cosmos, .hyperCore, .injective, .osmosis, .sei, .smartChain, .solana, .sui, .ethereum, .aptos, .monad, .none: + case .celestia, .cosmos, .hyperCore, .injective, .osmosis, .smartChain, .solana, .sui, .ethereum, .aptos, .monad, .none: false case .tron: true @@ -88,7 +88,7 @@ public struct BalanceViewModel: Sendable { public var hasFrozenResources: Bool { switch StakeChain(rawValue: asset.chain.rawValue) { - case .celestia, .cosmos, .hyperCore, .injective, .osmosis, .sei, .smartChain, .solana, .sui, .ethereum, .aptos, .monad, .none: + case .celestia, .cosmos, .hyperCore, .injective, .osmosis, .smartChain, .solana, .sui, .ethereum, .aptos, .monad, .none: false case .tron: !(balance.frozen + balance.locked).isZero diff --git a/ios/Packages/Store/Sources/Migrations.swift b/ios/Packages/Store/Sources/Migrations.swift index 8d3b17e1d7..5ef8f612c6 100644 --- a/ios/Packages/Store/Sources/Migrations.swift +++ b/ios/Packages/Store/Sources/Migrations.swift @@ -5,6 +5,8 @@ import GRDB import Primitives struct Migrations { + static let sei = "sei" + var migrator = DatabaseMigrator() init( @@ -23,6 +25,62 @@ struct Migrations { try? db.execute(sql: "DELETE FROM \(AssetRecord.databaseTableName) WHERE chain = ?", arguments: [chain]) } + static func removeChains(_ db: Database, chains: Set) throws { + for chain in chains { + let assetIdPattern = "\(chain)\\_%" + let quotedChain = "\"\(chain)\"" + let quotedAssetIdPrefix = "\"\(chain)_" + + try db.execute( + sql: "DELETE FROM \(WalletConnectionRecord.databaseTableName) WHERE instr(chains, ?) > 0", + arguments: [quotedChain], + ) + try db.execute( + sql: "DELETE FROM \(NotificationRecord.databaseTableName) WHERE instr(item, ?) > 0 OR instr(item, ?) > 0", + arguments: [quotedChain, quotedAssetIdPrefix], + ) + try db.execute( + sql: "UPDATE \(AssetRecord.databaseTableName) SET associations = '[]' WHERE instr(associations, ?) > 0 OR instr(associations, ?) > 0", + arguments: [quotedChain, quotedAssetIdPrefix], + ) + try db.execute( + sql: "DELETE FROM \(ContactAddressRecord.databaseTableName) WHERE chain = ?", + arguments: [chain], + ) + try db.execute( + sql: "DELETE FROM \(TransactionAssetAssociationRecord.databaseTableName) WHERE assetId = ? OR assetId LIKE ? ESCAPE '\\'", + arguments: [chain, assetIdPattern], + ) + try db.execute( + sql: "DELETE FROM \(TransactionRecord.databaseTableName) WHERE chain = ? OR assetId = ? OR assetId LIKE ? ESCAPE '\\' OR feeAssetId = ? OR feeAssetId LIKE ? ESCAPE '\\'", + arguments: [chain, chain, assetIdPattern, chain, assetIdPattern], + ) + try db.execute( + sql: "DELETE FROM \(PriceRecord.databaseTableName) WHERE assetId = ? OR assetId LIKE ? ESCAPE '\\'", + arguments: [chain, assetIdPattern], + ) + try db.execute( + sql: "DELETE FROM \(PriceAlertRecord.databaseTableName) WHERE assetId = ? OR assetId LIKE ? ESCAPE '\\'", + arguments: [chain, assetIdPattern], + ) + try db.execute( + sql: "DELETE FROM \(RecentActivityRecord.databaseTableName) WHERE assetId = ? OR assetId LIKE ? ESCAPE '\\' OR toAssetId = ? OR toAssetId LIKE ? ESCAPE '\\'", + arguments: [chain, assetIdPattern, chain, assetIdPattern], + ) + try db.execute( + sql: "DELETE FROM \(BannerRecord.databaseTableName) WHERE chain = ? OR assetId = ? OR assetId LIKE ? ESCAPE '\\'", + arguments: [chain, chain, assetIdPattern], + ) + try db.execute(sql: "DELETE FROM \(NFTAssetRecord.databaseTableName) WHERE chain = ?", arguments: [chain]) + try db.execute(sql: "DELETE FROM \(NFTCollectionRecord.databaseTableName) WHERE chain = ?", arguments: [chain]) + try db.execute(sql: "DELETE FROM \(NodeSelectedRecord.databaseTableName) WHERE chain = ?", arguments: [chain]) + try db.execute(sql: "DELETE FROM \(NodeRecord.databaseTableName) WHERE chain = ?", arguments: [chain]) + try db.execute(sql: "DELETE FROM \(AddressRecord.databaseTableName) WHERE chain = ?", arguments: [chain]) + try db.execute(sql: "DELETE FROM \(AccountRecord.databaseTableName) WHERE chain = ?", arguments: [chain]) + try db.execute(sql: "DELETE FROM \(AssetRecord.databaseTableName) WHERE chain = ?", arguments: [chain]) + } + } + private static func clearTables(_ db: Database, tableNames: [String]) throws { for tableName in tableNames where try db.tableExists(tableName) { try db.execute(sql: "DELETE FROM \(tableName)") @@ -513,6 +571,10 @@ struct Migrations { } } + migrator.registerMigration("Remove Sei chain") { db in + try Self.removeChains(db, chains: [Self.sei]) + } + try migrator.migrate(dbQueue) } } diff --git a/ios/Packages/Store/Tests/StoreTests/MigrationsTests.swift b/ios/Packages/Store/Tests/StoreTests/MigrationsTests.swift index 0b9e76e50b..8081dc3784 100644 --- a/ios/Packages/Store/Tests/StoreTests/MigrationsTests.swift +++ b/ios/Packages/Store/Tests/StoreTests/MigrationsTests.swift @@ -61,4 +61,54 @@ struct MigrationsTests { #expect(try! db.tableExists(AddressRecord.databaseTableName)) } } + + @Test + func removeChainsRemovesSeiReferences() throws { + let store = DB.mock() + let chain = Migrations.sei + + try store.dbQueue.write { db in + try seedChain(db, chain: chain) + + try Migrations.removeChains(db, chains: [chain]) + + #expect( + try String.fetchOne( + db, + sql: """ + SELECT 'assets=' || (SELECT COUNT(*) FROM assets WHERE chain = '\(chain)') || + ',accounts=' || (SELECT COUNT(*) FROM wallets_accounts WHERE chain = '\(chain)') || + ',connections=' || (SELECT COUNT(*) FROM wallets_connections WHERE instr(chains, '"\(chain)"') > 0) || + ',notifications=' || (SELECT COUNT(*) FROM notifications WHERE instr(item, '"\(chain)"') > 0) + """, + ) == "assets=0,accounts=0,connections=0,notifications=0", + ) + } + } + + private func seedChain(_ db: Database, chain: String) throws { + let walletId = "wallet-\(chain)" + try db.execute( + sql: "INSERT INTO wallets (id, name, type, `index`, `order`, isPinned) VALUES (?, ?, 'single', 0, 0, 0)", + arguments: [walletId, chain], + ) + try db.execute( + sql: "INSERT INTO assets (id, chain, name, symbol, decimals, type, isEnabled, isBuyable, isSellable, isSwappable, isStakeable, isEarnable, rank, hasImage, associations) " + + "VALUES (?, ?, ?, ?, 6, 'native', 1, 0, 0, 0, 0, 0, 1, 0, ?)", + arguments: [chain, chain, chain, chain, "[]"], + ) + try db.execute( + sql: "INSERT INTO wallets_accounts (walletId, chain, address, `index`, derivationPath) VALUES (?, ?, ?, 0, '')", + arguments: [walletId, chain, "\(chain)-address"], + ) + try db.execute( + sql: "INSERT INTO wallets_connections (id, sessionId, walletId, state, chains, createdAt, expireAt, appName, appDescription, appLink, appIcon) " + + "VALUES (?, ?, ?, 'active', ?, 0, 1, 'App', 'App', 'https://app.example', 'https://app.example/icon.png')", + arguments: ["connection-\(chain)", "session-\(chain)", walletId, "[\"\(chain)\"]"], + ) + try db.execute( + sql: "INSERT INTO notifications (id, walletId, createdAt, item) VALUES (?, ?, 0, ?)", + arguments: ["notification-\(chain)", walletId, "{\"icon\":{\"type\":\"asset\",\"value\":\"\(chain)\"}}"], + ) + } } diff --git a/ios/Packages/Style/Sources/Images.swift b/ios/Packages/Style/Sources/Images.swift index a1c004294e..af640790e9 100644 --- a/ios/Packages/Style/Sources/Images.swift +++ b/ios/Packages/Style/Sources/Images.swift @@ -41,7 +41,7 @@ public enum Images { public static let optimism = Image(.optimism) public static let osmosis = Image(.osmosis) public static let polygon = Image(.polygon) - public static let sei = Image(.sei) + public static let seievm = Image(.seievm) public static let smartchain = Image(.smartchain) public static let solana = Image(.solana) public static let sui = Image(.sui) @@ -260,7 +260,7 @@ public enum Images { (Images.Chains.optimism, "Optimism"), (Images.Chains.osmosis, "Osmosis"), (Images.Chains.polygon, "Polygon"), - (Images.Chains.sei, "Sei"), + (Images.Chains.seievm, "Sei EVM"), (Images.Chains.smartchain, "Smart Chain"), (Images.Chains.solana, "Solana"), (Images.Chains.sui, "Sui"), diff --git a/ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/sei.imageset/Contents.json b/ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/seievm.imageset/Contents.json similarity index 85% rename from ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/sei.imageset/Contents.json rename to ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/seievm.imageset/Contents.json index 67883e8a19..78271a15b1 100644 --- a/ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/sei.imageset/Contents.json +++ b/ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/seievm.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "sei.svg", + "filename" : "seievm.svg", "idiom" : "universal" } ], diff --git a/ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/sei.imageset/sei.svg b/ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/seievm.imageset/seievm.svg similarity index 100% rename from ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/sei.imageset/sei.svg rename to ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/seievm.imageset/seievm.svg From 16e4666be4d771b01cf87396b6b7a30a3f987a73 Mon Sep 17 00:00:00 2001 From: gemcoder21 <104884878+gemcoder21@users.noreply.github.com> Date: Sat, 15 Aug 2026 21:14:18 +0000 Subject: [PATCH 2/4] Remove empty-wallet cleanup and simplify chain handling Remove the empty-wallet deletion flow and simplify chain handling. Deleted/streamlined DeleteWallet API (no boolean return, no deleteEmptyWallets), removed avatar file deletion and moved LocalStore into the wallet-details feature, and updated DI to match. WalletsRepository/DAO no longer expose getEmptyWallets; tests adapted. iOS OnstartService/WalletService no longer call deleteEmptyWallets. Core chain logic: mark Sei as deprecated, remove is_disabled/all_with_disabled, and make Chain::all exclude Sei; callers updated to use Chain::all. Small related test updates and cleanup across Kotlin/Swift/Rust modules. --- .../com/gemwallet/android/MainViewModel.kt | 8 +--- .../android/MainViewModelAuthStateTest.kt | 2 - .../data/coordinators/di/WalletModule.kt | 12 +----- .../coordinators/wallet/DeleteWalletImpl.kt | 40 +++++-------------- .../wallet/DeleteWalletImplTest.kt | 29 ++------------ .../repositories/wallets/WalletsRepository.kt | 2 - .../wallets/WalletsRepositoryImpl.kt | 3 -- .../data/service/store/database/WalletsDao.kt | 7 ---- .../viewmodels/build.gradle.kts | 1 - .../features/wallet/viewmodels}/LocalStore.kt | 14 ++++--- .../wallet/viewmodels/WalletAvatarService.kt | 3 +- .../wallets/viewmodels/WalletsViewModel.kt | 2 +- .../wallet/coordinators/DeleteWallet.kt | 8 ++-- core/apps/daemon/src/parser/mod.rs | 11 +++-- core/apps/dynode/src/config/mod.rs | 1 - .../src/mnemonic/tests/derivation.rs | 2 +- .../gem_evm/src/uniswap/deployment/v3.rs | 2 +- .../gem_evm/src/uniswap/deployment/v4.rs | 2 +- core/crates/primitives/src/chain.rs | 17 +------- core/crates/primitives/src/chain_config.rs | 8 ---- core/crates/settings_chain/src/node_check.rs | 2 +- .../AppService/OnstartService.swift | 6 --- .../Tests/WalletServiceTests.swift | 17 -------- .../WalletService/WalletService.swift | 6 --- 24 files changed, 42 insertions(+), 163 deletions(-) rename android/{data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store => features/wallet-details/viewmodels/src/main/kotlin/com/gemwallet/android/features/wallet/viewmodels}/LocalStore.kt (57%) diff --git a/android/app/src/main/kotlin/com/gemwallet/android/MainViewModel.kt b/android/app/src/main/kotlin/com/gemwallet/android/MainViewModel.kt index 3981998626..afbd71f0a0 100644 --- a/android/app/src/main/kotlin/com/gemwallet/android/MainViewModel.kt +++ b/android/app/src/main/kotlin/com/gemwallet/android/MainViewModel.kt @@ -3,7 +3,6 @@ package com.gemwallet.android import android.content.Intent import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.gemwallet.android.application.wallet.coordinators.DeleteWallet import com.gemwallet.android.data.repositories.bridge.BridgesRepository import com.gemwallet.android.data.repositories.config.UserConfig import com.gemwallet.android.model.AuthState @@ -30,7 +29,6 @@ class MainViewModel @Inject constructor( private val bridgesRepository: BridgesRepository, private val syncService: SyncService, private val migrateV3KeystoreService: MigrateV3KeystoreService, - private val deleteWallet: DeleteWallet, private val checkAccountsService: CheckAccountsService, private val lockTimer: LockTimer, private val pendingNavigationCoordinator: PendingNavigationCoordinator, @@ -80,12 +78,10 @@ class MainViewModel @Inject constructor( fun isAuthRequired(): Boolean = userConfig.authRequired() internal fun maintain() { + viewModelScope.launch(Dispatchers.IO) { syncService.sync() } viewModelScope.launch(Dispatchers.IO) { migrateV3KeystoreService() - if (deleteWallet.deleteEmptyWallets()) { - checkAccountsService() - syncService.sync() - } + checkAccountsService() } } diff --git a/android/app/src/test/kotlin/com/gemwallet/android/MainViewModelAuthStateTest.kt b/android/app/src/test/kotlin/com/gemwallet/android/MainViewModelAuthStateTest.kt index 1265e4fa09..c925b15ec3 100644 --- a/android/app/src/test/kotlin/com/gemwallet/android/MainViewModelAuthStateTest.kt +++ b/android/app/src/test/kotlin/com/gemwallet/android/MainViewModelAuthStateTest.kt @@ -1,6 +1,5 @@ package com.gemwallet.android -import com.gemwallet.android.application.wallet.coordinators.DeleteWallet import com.gemwallet.android.data.repositories.bridge.BridgesRepository import com.gemwallet.android.data.repositories.config.UserConfig import com.gemwallet.android.model.AuthState @@ -93,7 +92,6 @@ class MainViewModelAuthStateTest { bridgesRepository = mockk(relaxed = true), syncService = mockk(relaxed = true), migrateV3KeystoreService = mockk(relaxed = true), - deleteWallet = mockk(relaxed = true), checkAccountsService = mockk(relaxed = true), lockTimer = mockk(relaxed = true), pendingNavigationCoordinator = mockk(relaxed = true), diff --git a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/di/WalletModule.kt b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/di/WalletModule.kt index accf9eae6c..d45dfaddfa 100644 --- a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/di/WalletModule.kt +++ b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/di/WalletModule.kt @@ -22,10 +22,9 @@ import com.gemwallet.android.data.coordinators.wallet.SetWalletAvatarImpl import com.gemwallet.android.data.coordinators.wallet.SetWalletNameImpl import com.gemwallet.android.data.coordinators.wallet.ToggleWalletPinImpl import com.gemwallet.android.data.coordinators.wallet.WalletIdGeneratorImpl +import com.gemwallet.android.data.service.store.WalletPreferencesFactory import com.gemwallet.android.data.repositories.session.SessionRepository import com.gemwallet.android.data.repositories.wallets.WalletsRepository -import com.gemwallet.android.data.service.store.LocalStore -import com.gemwallet.android.data.service.store.WalletPreferencesFactory import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -95,15 +94,8 @@ object WalletModule { walletsRepository: WalletsRepository, deleteKeyStoreOperator: DeleteKeyStoreOperator, walletPreferencesFactory: WalletPreferencesFactory, - localStore: LocalStore, ): DeleteWallet { - return DeleteWalletImpl( - sessionRepository, - walletsRepository, - deleteKeyStoreOperator, - walletPreferencesFactory, - localStore, - ) + return DeleteWalletImpl(sessionRepository, walletsRepository, deleteKeyStoreOperator, walletPreferencesFactory) } @Provides diff --git a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/wallet/DeleteWalletImpl.kt b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/wallet/DeleteWalletImpl.kt index e4b129d56d..49684cc3ef 100644 --- a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/wallet/DeleteWalletImpl.kt +++ b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/wallet/DeleteWalletImpl.kt @@ -3,63 +3,44 @@ package com.gemwallet.android.data.coordinators.wallet import android.util.Log import com.gemwallet.android.application.wallet.coordinators.DeleteWallet import com.gemwallet.android.blockchain.operators.DeleteKeyStoreOperator +import com.gemwallet.android.data.service.store.WalletPreferencesFactory import com.gemwallet.android.data.repositories.session.SessionRepository import com.gemwallet.android.data.repositories.wallets.WalletsRepository -import com.gemwallet.android.data.service.store.LocalStore -import com.gemwallet.android.data.service.store.WalletPreferencesFactory -import com.wallet.core.primitives.Wallet import com.wallet.core.primitives.WalletId import com.wallet.core.primitives.WalletType import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.withContext +import javax.inject.Inject +import javax.inject.Singleton -class DeleteWalletImpl( +@Singleton +class DeleteWalletImpl @Inject constructor( private val sessionRepository: SessionRepository, private val walletsRepository: WalletsRepository, private val deleteKeyStoreOperator: DeleteKeyStoreOperator, private val walletPreferencesFactory: WalletPreferencesFactory, - private val localStore: LocalStore, ) : DeleteWallet { - override suspend fun deleteEmptyWallets(): Boolean = withContext(Dispatchers.IO) { - for (wallet in walletsRepository.getEmptyWallets()) { - if (!deleteWallet(wallet)) return@withContext false - } - true - } - override suspend fun deleteWallet( walletId: WalletId, onBoard: () -> Unit, - onComplete: () -> Unit, - ): Boolean = withContext(Dispatchers.IO) { - val wallet = walletsRepository.getWallet(walletId).firstOrNull() ?: return@withContext false - deleteWallet(wallet, onBoard, onComplete) - } - - private suspend fun deleteWallet( - wallet: Wallet, - onBoard: () -> Unit = {}, - onComplete: () -> Unit = {}, - ): Boolean { - val walletId = wallet.id + onComplete: () -> Unit + ) = withContext(Dispatchers.IO) { + val wallet = walletsRepository.getWallet(walletId).firstOrNull() ?: return@withContext val currentWalletId = sessionRepository.session().firstOrNull()?.wallet?.id // Delete the keystore before the DB row; if it fails, keep the wallet so the secret is never orphaned silently. if (wallet.type != WalletType.View && !deleteKeyStoreOperator(wallet)) { Log.e(TAG, "keystore delete failed for ${walletId.id}; keeping the wallet") - return false + return@withContext } if (!walletsRepository.removeWallet(walletId = walletId)) { Log.e(TAG, "wallet row removal failed for ${walletId.id}; retry delete to finish") - return false + return@withContext } walletPreferencesFactory.create(walletId.id).clear() - if (!localStore.remove(wallet.imageUrl)) { - Log.e(TAG, "wallet avatar delete failed for ${walletId.id}") - } val callback: () -> Unit = if (currentWalletId == walletId) { val nextWallet = walletsRepository.getAll().firstOrNull() @@ -81,7 +62,6 @@ class DeleteWalletImpl( withContext(Dispatchers.Main) { callback() } - return true } private companion object { diff --git a/android/data/coordinators/src/test/kotlin/com/gemwallet/android/data/coordinators/wallet/DeleteWalletImplTest.kt b/android/data/coordinators/src/test/kotlin/com/gemwallet/android/data/coordinators/wallet/DeleteWalletImplTest.kt index 9928bbbfb0..a8e8bb0129 100644 --- a/android/data/coordinators/src/test/kotlin/com/gemwallet/android/data/coordinators/wallet/DeleteWalletImplTest.kt +++ b/android/data/coordinators/src/test/kotlin/com/gemwallet/android/data/coordinators/wallet/DeleteWalletImplTest.kt @@ -3,7 +3,6 @@ package com.gemwallet.android.data.coordinators.wallet import com.gemwallet.android.blockchain.operators.DeleteKeyStoreOperator import com.gemwallet.android.data.repositories.session.SessionRepository import com.gemwallet.android.data.repositories.wallets.WalletsRepository -import com.gemwallet.android.data.service.store.LocalStore import com.gemwallet.android.data.service.store.WalletPreferences import com.gemwallet.android.data.service.store.WalletPreferencesFactory import com.gemwallet.android.model.Session @@ -23,8 +22,6 @@ import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import org.junit.After -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test @@ -35,7 +32,6 @@ class DeleteWalletImplTest { private val walletsRepository = mockk(relaxed = true) private val deleteKeyStoreOperator = mockk() private val walletPreferences = mockk(relaxed = true) - private val localStore = mockk(relaxed = true) private val walletPreferencesFactory = mockk { every { create(any()) } returns walletPreferences } @@ -45,7 +41,6 @@ class DeleteWalletImplTest { walletsRepository, deleteKeyStoreOperator, walletPreferencesFactory, - localStore, ) @Before @@ -65,42 +60,26 @@ class DeleteWalletImplTest { every { sessionRepository.session() } returns MutableStateFlow(null) every { deleteKeyStoreOperator(wallet) } returns false - val deleted = delete.deleteWallet(wallet.id) + delete.deleteWallet(wallet.id, onBoard = {}, onComplete = {}) - assertFalse(deleted) verify { deleteKeyStoreOperator(wallet) } coVerify(exactly = 0) { walletsRepository.removeWallet(any()) } verify(exactly = 0) { walletPreferences.clear() } } @Test - fun cleansWalletDataWhenDeleted() = runTest { - val wallet = mockWallet(id = "wallet-1", type = WalletType.Multicoin).copy(imageUrl = "avatar.png") + fun clearsWalletPreferencesWhenWalletDeleted() = runTest { + val wallet = mockWallet(id = "wallet-1", type = WalletType.Multicoin) every { walletsRepository.getWallet(wallet.id) } returns flowOf(wallet) every { walletsRepository.getAll() } returns flowOf(emptyList()) every { sessionRepository.session() } returns MutableStateFlow(null) every { deleteKeyStoreOperator(wallet) } returns true coEvery { walletsRepository.removeWallet(wallet.id) } returns true - val deleted = delete.deleteWallet(wallet.id) + delete.deleteWallet(wallet.id, onBoard = {}, onComplete = {}) - assertTrue(deleted) coVerify { walletsRepository.removeWallet(wallet.id) } verify { walletPreferencesFactory.create(wallet.id.id) } verify { walletPreferences.clear() } - verify { localStore.remove("avatar.png") } - } - - @Test - fun deletesEmptyWallets() = runTest { - val emptyWallet = mockWallet(id = "empty-wallet", accounts = emptyList()) - coEvery { walletsRepository.getEmptyWallets() } returns listOf(emptyWallet) - every { sessionRepository.session() } returns MutableStateFlow(null) - every { deleteKeyStoreOperator(emptyWallet) } returns true - coEvery { walletsRepository.removeWallet(emptyWallet.id) } returns true - - assertTrue(delete.deleteEmptyWallets()) - - coVerify { walletsRepository.removeWallet(emptyWallet.id) } } } diff --git a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/wallets/WalletsRepository.kt b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/wallets/WalletsRepository.kt index 829dcd7183..dbf146be45 100644 --- a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/wallets/WalletsRepository.kt +++ b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/wallets/WalletsRepository.kt @@ -12,8 +12,6 @@ interface WalletsRepository { fun getAll(): Flow> - suspend fun getEmptyWallets(): List - suspend fun addWatch(walletName: String, address: String, chain: Chain): Wallet suspend fun addControlled( diff --git a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/wallets/WalletsRepositoryImpl.kt b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/wallets/WalletsRepositoryImpl.kt index 3abab6bdcb..369be99bc9 100644 --- a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/wallets/WalletsRepositoryImpl.kt +++ b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/wallets/WalletsRepositoryImpl.kt @@ -44,9 +44,6 @@ class WalletsRepositoryImpl @Inject constructor( override fun getAll(): Flow> = walletsDao.getAll().toDTO() - override suspend fun getEmptyWallets(): List = - walletsDao.getEmptyWallets().map { it.toDTO(emptyList()) } - override suspend fun addWatch(walletName: String, address: String, chain: Chain): Wallet { val walletId = walletIdGenerator.generateWalletId(WalletType.View, chain, address) val hasWallet = getWallet(walletId).firstOrNull() diff --git a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/WalletsDao.kt b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/WalletsDao.kt index a70dc59507..d1845b7591 100644 --- a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/WalletsDao.kt +++ b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/WalletsDao.kt @@ -17,13 +17,6 @@ interface WalletsDao { """) fun getAll(): Flow>> - @Query(""" - SELECT wallets.* FROM wallets - LEFT JOIN accounts ON wallets.id = accounts.wallet_id - WHERE accounts.wallet_id IS NULL - """) - suspend fun getEmptyWallets(): List - @Query("SELECT * FROM wallets WHERE id = :id") fun getById(id: String): Flow diff --git a/android/features/wallet-details/viewmodels/build.gradle.kts b/android/features/wallet-details/viewmodels/build.gradle.kts index c43a1e4389..0c5521ab65 100644 --- a/android/features/wallet-details/viewmodels/build.gradle.kts +++ b/android/features/wallet-details/viewmodels/build.gradle.kts @@ -43,7 +43,6 @@ dependencies { api(project(":ui-models")) implementation(project(":ui")) implementation(project(":data:repositories")) - implementation(project(":data:services:store")) implementation(libs.ktx.core) implementation(libs.lifecycle.runtime.ktx) diff --git a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/LocalStore.kt b/android/features/wallet-details/viewmodels/src/main/kotlin/com/gemwallet/android/features/wallet/viewmodels/LocalStore.kt similarity index 57% rename from android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/LocalStore.kt rename to android/features/wallet-details/viewmodels/src/main/kotlin/com/gemwallet/android/features/wallet/viewmodels/LocalStore.kt index 083c8a7433..9cab480aa9 100644 --- a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/LocalStore.kt +++ b/android/features/wallet-details/viewmodels/src/main/kotlin/com/gemwallet/android/features/wallet/viewmodels/LocalStore.kt @@ -1,4 +1,4 @@ -package com.gemwallet.android.data.service.store +package com.gemwallet.android.features.wallet.viewmodels import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext @@ -7,17 +7,19 @@ import java.util.UUID import javax.inject.Inject class LocalStore @Inject constructor( - @param:ApplicationContext private val context: Context, + @ApplicationContext private val context: Context, ) { + fun save(data: ByteArray, extension: String): String { val fileName = "${UUID.randomUUID()}.$extension" File(context.filesDir, fileName).writeBytes(data) return fileName } - fun remove(fileName: String?): Boolean { - if (fileName.isNullOrEmpty()) return true - val file = File(context.filesDir, fileName) - return !file.exists() || file.delete() + fun remove(fileName: String?) { + if (fileName.isNullOrEmpty()) { + return + } + File(context.filesDir, fileName).takeIf { it.exists() }?.delete() } } diff --git a/android/features/wallet-details/viewmodels/src/main/kotlin/com/gemwallet/android/features/wallet/viewmodels/WalletAvatarService.kt b/android/features/wallet-details/viewmodels/src/main/kotlin/com/gemwallet/android/features/wallet/viewmodels/WalletAvatarService.kt index 2a4df5f92a..60032f610f 100644 --- a/android/features/wallet-details/viewmodels/src/main/kotlin/com/gemwallet/android/features/wallet/viewmodels/WalletAvatarService.kt +++ b/android/features/wallet-details/viewmodels/src/main/kotlin/com/gemwallet/android/features/wallet/viewmodels/WalletAvatarService.kt @@ -7,7 +7,6 @@ import coil3.request.ImageRequest import coil3.request.SuccessResult import coil3.toBitmap import com.gemwallet.android.application.wallet.coordinators.SetWalletAvatar -import com.gemwallet.android.data.service.store.LocalStore import com.wallet.core.primitives.WalletId import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers @@ -17,7 +16,7 @@ import javax.inject.Singleton @Singleton class WalletAvatarService @Inject constructor( - @param:ApplicationContext private val context: Context, + @ApplicationContext private val context: Context, private val emojiRenderer: EmojiAvatarRenderer, private val localStore: LocalStore, private val setWalletAvatar: SetWalletAvatar, diff --git a/android/features/wallets/viewmodels/src/main/kotlin/com/gemwallet/android/features/wallets/viewmodels/WalletsViewModel.kt b/android/features/wallets/viewmodels/src/main/kotlin/com/gemwallet/android/features/wallets/viewmodels/WalletsViewModel.kt index 4b5a98a0a8..bb1c5d3038 100644 --- a/android/features/wallets/viewmodels/src/main/kotlin/com/gemwallet/android/features/wallets/viewmodels/WalletsViewModel.kt +++ b/android/features/wallets/viewmodels/src/main/kotlin/com/gemwallet/android/features/wallets/viewmodels/WalletsViewModel.kt @@ -30,7 +30,7 @@ class WalletsViewModel @Inject constructor( } fun deleteWallet(walletId: WalletId, onBoard: () -> Unit) = viewModelScope.launch { - deleteWallet.deleteWallet(walletId, onBoard) + deleteWallet.deleteWallet(walletId, onBoard) {} } fun togglePin(walletId: WalletId) = viewModelScope.launch(Dispatchers.IO) { diff --git a/android/gemcore/src/main/kotlin/com/gemwallet/android/application/wallet/coordinators/DeleteWallet.kt b/android/gemcore/src/main/kotlin/com/gemwallet/android/application/wallet/coordinators/DeleteWallet.kt index 4db19040f9..373b634f15 100644 --- a/android/gemcore/src/main/kotlin/com/gemwallet/android/application/wallet/coordinators/DeleteWallet.kt +++ b/android/gemcore/src/main/kotlin/com/gemwallet/android/application/wallet/coordinators/DeleteWallet.kt @@ -5,9 +5,7 @@ import com.wallet.core.primitives.WalletId interface DeleteWallet { suspend fun deleteWallet( walletId: WalletId, - onBoard: () -> Unit = {}, - onComplete: () -> Unit = {}, - ): Boolean - - suspend fun deleteEmptyWallets(): Boolean + onBoard: () -> Unit, + onComplete: () -> Unit, + ) } diff --git a/core/apps/daemon/src/parser/mod.rs b/core/apps/daemon/src/parser/mod.rs index 052bcccee5..fd828407ff 100644 --- a/core/apps/daemon/src/parser/mod.rs +++ b/core/apps/daemon/src/parser/mod.rs @@ -219,16 +219,15 @@ pub async fn run(settings: Settings, chain: Option, health_state: Arc = match chain { - Some(chain) if !chain.is_disabled() => vec![chain], - Some(_) => Vec::new(), - None => database + let chains: Vec = if let Some(chain) = chain { + vec![chain] + } else { + database .parser_state()? .get_parser_states()? .into_iter() .flat_map(|x| Chain::from_str(x.chain.as_ref())) - .filter(|chain| !chain.is_disabled()) - .collect(), + .collect() }; info_with_fields!("parser init", chains = format!("{:?}", chains)); diff --git a/core/apps/dynode/src/config/mod.rs b/core/apps/dynode/src/config/mod.rs index 2e1a6cbfb1..325cac5aec 100644 --- a/core/apps/dynode/src/config/mod.rs +++ b/core/apps/dynode/src/config/mod.rs @@ -288,7 +288,6 @@ pub fn load_config() -> Result<(NodeConfig, HashMap), Config .collect::, _>>()? .into_iter() .flat_map(|cf| cf.chains) - .filter(|config| !config.chain.is_disabled()) .map(|c| (c.chain, c)) .collect(); diff --git a/core/crates/gem_derivation/src/mnemonic/tests/derivation.rs b/core/crates/gem_derivation/src/mnemonic/tests/derivation.rs index 98f1d077fb..895a5c6cba 100644 --- a/core/crates/gem_derivation/src/mnemonic/tests/derivation.rs +++ b/core/crates/gem_derivation/src/mnemonic/tests/derivation.rs @@ -88,7 +88,7 @@ fn test_derive_accounts_public_key() { #[test] fn test_mnemonic_derivation_coverage_for_every_chain() { - for chain in Chain::all_with_disabled() { + for chain in Chain::all() { let expected = expected_derivation(chain); let accounts = derive_accounts_from_mnemonic(PHRASE, vec![chain]).unwrap(); assert_eq!(accounts.len(), 1); diff --git a/core/crates/gem_evm/src/uniswap/deployment/v3.rs b/core/crates/gem_evm/src/uniswap/deployment/v3.rs index 5f69c06d2a..86c4f7da46 100644 --- a/core/crates/gem_evm/src/uniswap/deployment/v3.rs +++ b/core/crates/gem_evm/src/uniswap/deployment/v3.rs @@ -302,7 +302,7 @@ mod tests { #[test] fn test_deployment_addresses_are_checksummed() { - for chain in Chain::all_with_disabled() { + for chain in Chain::all() { let deployments = [ get_uniswap_router_deployment_by_chain(&chain), get_pancakeswap_router_deployment_by_chain(&chain), diff --git a/core/crates/gem_evm/src/uniswap/deployment/v4.rs b/core/crates/gem_evm/src/uniswap/deployment/v4.rs index 3b3abc7f45..9767d25434 100644 --- a/core/crates/gem_evm/src/uniswap/deployment/v4.rs +++ b/core/crates/gem_evm/src/uniswap/deployment/v4.rs @@ -175,7 +175,7 @@ mod tests { #[test] fn test_deployment_addresses_are_checksummed() { - for chain in Chain::all_with_disabled() { + for chain in Chain::all() { let Some(deployment) = get_uniswap_deployment_by_chain(&chain) else { continue; }; diff --git a/core/crates/primitives/src/chain.rs b/core/crates/primitives/src/chain.rs index 6b5c97fe56..ee1b5c4675 100644 --- a/core/crates/primitives/src/chain.rs +++ b/core/crates/primitives/src/chain.rs @@ -39,6 +39,7 @@ pub enum Chain { Gnosis, Celestia, Injective, + // TODO: Remove Sei after November 15, 2026. #[typeshare(skip)] Sei, SeiEvm, @@ -154,10 +155,6 @@ impl Chain { self.config().is_defi_supported } - pub fn is_disabled(&self) -> bool { - self.config().is_disabled() - } - // milliseconds pub fn block_time(&self) -> u32 { self.config().block_time @@ -168,11 +165,7 @@ impl Chain { } pub fn all() -> Vec { - Self::iter().filter(|chain| !chain.is_disabled()).collect() - } - - pub fn all_with_disabled() -> Vec { - Self::iter().collect() + Self::iter().filter(|chain| *chain != Self::Sei).collect() } pub fn stakeable() -> Vec { @@ -188,12 +181,6 @@ impl Chain { mod tests { use super::*; - #[test] - fn test_all() { - assert_eq!(Chain::all().into_iter().filter(Chain::is_disabled).collect::>(), vec![]); - assert_eq!(Chain::all_with_disabled().into_iter().filter(Chain::is_disabled).collect::>(), vec![Chain::Sei]); - } - #[test] fn test_mayachain_swap_not_supported() { assert!(!Chain::Mayachain.is_swap_supported()); diff --git a/core/crates/primitives/src/chain_config.rs b/core/crates/primitives/src/chain_config.rs index c78fe4a730..4bdde2d962 100644 --- a/core/crates/primitives/src/chain_config.rs +++ b/core/crates/primitives/src/chain_config.rs @@ -50,14 +50,6 @@ pub struct ChainConfig { pub stake: Option, } -const DISABLED_CHAINS: &[Chain] = &[Chain::Sei]; - -impl ChainConfig { - pub fn is_disabled(&self) -> bool { - DISABLED_CHAINS.contains(&self.chain) - } -} - // Centralized chain configurations. Add new chains here. static CHAIN_CONFIGS: LazyLock> = LazyLock::new(|| { vec![ diff --git a/core/crates/settings_chain/src/node_check.rs b/core/crates/settings_chain/src/node_check.rs index 75299d751c..3325eac495 100644 --- a/core/crates/settings_chain/src/node_check.rs +++ b/core/crates/settings_chain/src/node_check.rs @@ -106,7 +106,7 @@ mod tests { #[test] fn test_wallet_node_check_request() { - for chain in Chain::all_with_disabled() { + for chain in Chain::all() { let NodeCheckRequest::Wallet { address, transaction_id } = node_check_request(chain, NodeCheckProfile::Wallet) else { panic!("wallet profile expected for {chain}"); }; diff --git a/ios/Packages/FeatureServices/AppService/OnstartService.swift b/ios/Packages/FeatureServices/AppService/OnstartService.swift index ae838f60fc..6d9be1f5b8 100644 --- a/ios/Packages/FeatureServices/AppService/OnstartService.swift +++ b/ios/Packages/FeatureServices/AppService/OnstartService.swift @@ -60,12 +60,6 @@ public struct OnstartService: Sendable { } catch { debugLog("v3 keystore migration could not enumerate wallets: \(error)") } - do { - try await walletService.deleteEmptyWallets() - } catch { - debugLog("wallet cleanup error: \(error)") - return - } do { try walletService.setup(chains: AssetConfiguration.allChains) } catch { diff --git a/ios/Packages/FeatureServices/WalletService/Tests/WalletServiceTests.swift b/ios/Packages/FeatureServices/WalletService/Tests/WalletServiceTests.swift index 5dece3d5bf..b3e36e567d 100644 --- a/ios/Packages/FeatureServices/WalletService/Tests/WalletServiceTests.swift +++ b/ios/Packages/FeatureServices/WalletService/Tests/WalletServiceTests.swift @@ -6,7 +6,6 @@ import Observation import Preferences import PreferencesTestKit import Primitives -import PrimitivesTestKit import Store import StoreTestKit import Testing @@ -222,22 +221,6 @@ struct WalletServiceTests { } } - @Test - func deleteEmptyWalletsUsesNormalDeletion() async throws { - let walletStore = WalletStore.mock(db: .mockWithChains([.ethereum])) - let wallet = Wallet.mock(id: .multicoin(address: "empty")) - let walletPreferences = WalletPreferences(walletId: wallet.id) - defer { walletPreferences.clear() } - let service = WalletService.mock(walletStore: walletStore) - try walletStore.addWallet(wallet) - walletPreferences.completeInitialLoadAssets = true - - try await service.deleteEmptyWallets() - - #expect(try walletStore.getWallet(id: wallet.id) == nil) - #expect(!walletPreferences.completeInitialLoadAssets) - } - @Test func loadOrCreateWalletMarksSubscriptionsDirty() async throws { let rawPreferences = Preferences.mock() diff --git a/ios/Packages/FeatureServices/WalletService/WalletService.swift b/ios/Packages/FeatureServices/WalletService/WalletService.swift index 9479bc7c91..5e5d93a501 100644 --- a/ios/Packages/FeatureServices/WalletService/WalletService.swift +++ b/ios/Packages/FeatureServices/WalletService/WalletService.swift @@ -87,12 +87,6 @@ public struct WalletService: Sendable { preferences.invalidateSubscriptions() } - public func deleteEmptyWallets() async throws { - for wallet in try walletStore.getWallets() where wallet.accounts.isEmpty { - try await delete(wallet) - } - } - public func setup(chains: [Chain]) throws { let wallets = walletSessionService.wallets.filter { $0.type == .multicoin } guard !wallets.isEmpty else { return } From 85c1cc945b4001352f41e3699b043d603f88df7e Mon Sep 17 00:00:00 2001 From: gemcoder21 <104884878+gemcoder21@users.noreply.github.com> Date: Sat, 15 Aug 2026 22:03:55 +0000 Subject: [PATCH 3/4] Remove legacy 'sei' chain and migration updates Remove legacy 'sei' references and preserve SeiEVM (seievm) across platforms. Key changes: refactor DB migration to remove 'sei' (Migration_85_86), add Android instrumentation test for migration, update iOS migration/tests and rename seievm assets to sei.svg, map Chain.SeiEvm icon to sei.svg, switch many call sites to Chain.available() and safer Chain lookups (entries.firstOrNull), update various repository and test logic, and add small core/rust test ensuring legacy Sei is excluded. Misc. formatting and minor API cleanups. --- .../chains/icons/{seievm.svg => sei.svg} | 0 .../android/services/CheckAccountsService.kt | 3 +- .../services/CheckAccountsServiceTest.kt | 11 +- .../operators/TestCreateAccountOperator.kt | 3 +- .../operators/gemstone/GemMappers.kt | 3 +- .../data/coordinators/di/WalletModule.kt | 2 +- .../repositories/assets/AssetsRepository.kt | 3 +- .../chains/ChainInfoRepository.kt | 3 +- .../repositories/tokens/TokensRepository.kt | 2 +- .../repositories/tokens/WalletSearchTokens.kt | 2 +- .../wallets/WalletsRepositoryImpl.kt | 4 +- .../assets/AssetsRepositoryTest.kt | 17 +- .../service/store/Migration_85_86Test.kt | 146 ++++++++++++++++++ .../service/store/database/ChainConverters.kt | 3 +- .../data/service/store/database/WalletsDao.kt | 2 +- .../store/database/di/DatabaseModule.kt | 5 +- .../store/database/di/Migration_63_64.kt | 5 +- .../store/database/di/Migration_85_86.kt | 131 ++++++++-------- .../store/database/di/Migration_85_86Test.kt | 32 ---- .../viewmodels/NetworkAssetsViewModel.kt | 2 +- .../presents/ContactChainSelectScene.kt | 2 +- .../presents/ValidatorsScene.kt | 2 +- .../viewmodels/build.gradle.kts | 2 +- .../domains/asset/IconUrlGeneration.kt | 10 +- .../com/gemwallet/android/ext/AssetId.kt | 4 +- .../kotlin/com/gemwallet/android/ext/Chain.kt | 41 +++-- .../com/gemwallet/android/ext/StakeChain.kt | 2 +- .../wallet/core/primitives/TransactionId.kt | 3 +- .../com/gemwallet/android/ext/ChainExtTest.kt | 2 +- .../android/testkit/GemstoneTestKeys.kt | 3 +- core/crates/primitives/src/chain.rs | 9 +- .../Sources/Types/ChainImage.swift | 2 +- ios/Packages/Store/Sources/Migrations.swift | 81 +++------- .../Tests/StoreTests/MigrationsTests.swift | 47 +++--- ios/Packages/Style/Sources/Images.swift | 4 +- .../Contents.json | 2 +- .../seievm.svg => sei.imageset/sei.svg} | 0 37 files changed, 352 insertions(+), 243 deletions(-) rename android/app/src/main/assets/chains/icons/{seievm.svg => sei.svg} (100%) create mode 100644 android/data/services/store/src/androidTest/kotlin/com/gemwallet/android/service/store/Migration_85_86Test.kt delete mode 100644 android/data/services/store/src/test/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_85_86Test.kt rename ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/{seievm.imageset => sei.imageset}/Contents.json (85%) rename ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/{seievm.imageset/seievm.svg => sei.imageset/sei.svg} (100%) diff --git a/android/app/src/main/assets/chains/icons/seievm.svg b/android/app/src/main/assets/chains/icons/sei.svg similarity index 100% rename from android/app/src/main/assets/chains/icons/seievm.svg rename to android/app/src/main/assets/chains/icons/sei.svg diff --git a/android/app/src/main/kotlin/com/gemwallet/android/services/CheckAccountsService.kt b/android/app/src/main/kotlin/com/gemwallet/android/services/CheckAccountsService.kt index 4cbe15e32a..5c84da7a3c 100644 --- a/android/app/src/main/kotlin/com/gemwallet/android/services/CheckAccountsService.kt +++ b/android/app/src/main/kotlin/com/gemwallet/android/services/CheckAccountsService.kt @@ -4,6 +4,7 @@ import com.gemwallet.android.application.PasswordStore import com.gemwallet.android.blockchain.operators.AddAccountsOperator import com.gemwallet.android.data.repositories.assets.AssetsRepository import com.gemwallet.android.data.repositories.wallets.WalletsRepository +import com.gemwallet.android.ext.available import com.wallet.core.primitives.AssetId import com.wallet.core.primitives.Chain import com.wallet.core.primitives.WalletType @@ -38,7 +39,7 @@ class CheckAccountsService @Inject constructor( } val accountChains = wallet.accounts.map { it.chain }.toSet() - val newChains = Chain.entries.filterNot(accountChains::contains) + val newChains = Chain.available().filterNot(accountChains::contains) // Backfill new chains via add_accounts, skips gracefully if the v4 keystore isn't ready (e.g. migration pending) and retries next launch. val newAccounts = if (newChains.isNotEmpty()) { diff --git a/android/app/src/test/kotlin/com/gemwallet/android/services/CheckAccountsServiceTest.kt b/android/app/src/test/kotlin/com/gemwallet/android/services/CheckAccountsServiceTest.kt index 5ac061e5af..276326ec98 100644 --- a/android/app/src/test/kotlin/com/gemwallet/android/services/CheckAccountsServiceTest.kt +++ b/android/app/src/test/kotlin/com/gemwallet/android/services/CheckAccountsServiceTest.kt @@ -4,16 +4,17 @@ import com.gemwallet.android.application.PasswordStore import com.gemwallet.android.blockchain.operators.AddAccountsOperator import com.gemwallet.android.data.repositories.assets.AssetsRepository import com.gemwallet.android.data.repositories.wallets.WalletsRepository +import com.gemwallet.android.ext.available import com.gemwallet.android.testkit.mockAccount import com.gemwallet.android.testkit.mockAsset import com.gemwallet.android.testkit.mockWallet import com.wallet.core.primitives.Chain -import com.wallet.core.primitives.WalletType import io.mockk.coEvery import io.mockk.coJustRun import io.mockk.coVerify import io.mockk.every import io.mockk.mockk +import io.mockk.mockkStatic import io.mockk.unmockkAll import io.mockk.verify import kotlinx.coroutines.flow.flowOf @@ -42,14 +43,14 @@ class CheckAccountsServiceTest { @Test fun invoke_repairsMissingNativeAssetsWithoutCreatingNewAccounts() = runBlocking { - val accounts = Chain.entries.map { chain -> + val accounts = Chain.available().map { chain -> mockAccount(chain = chain) } val wallet = mockWallet( id = "wallet-1", accounts = accounts, ) - val nativeAssets = Chain.entries + val nativeAssets = Chain.available() .filterNot { it == Chain.Ethereum } .map { chain -> mockAsset(chain = chain) } @@ -75,9 +76,11 @@ class CheckAccountsServiceTest { @Test fun invoke_doesNotRepairWhenExpectedNativeAssetsExist() = runBlocking { + mockkStatic("com.gemwallet.android.ext.ChainKt") + every { Chain.available() } returns setOf(Chain.Solana) + val wallet = mockWallet( id = "wallet-1", - type = WalletType.Single, accounts = listOf(mockAccount(chain = Chain.Solana)), ) val nativeAssets = listOf( diff --git a/android/blockchain/src/androidTest/kotlin/com/gemwallet/android/blockchain/operators/TestCreateAccountOperator.kt b/android/blockchain/src/androidTest/kotlin/com/gemwallet/android/blockchain/operators/TestCreateAccountOperator.kt index db49d909d0..5cada8ac47 100644 --- a/android/blockchain/src/androidTest/kotlin/com/gemwallet/android/blockchain/operators/TestCreateAccountOperator.kt +++ b/android/blockchain/src/androidTest/kotlin/com/gemwallet/android/blockchain/operators/TestCreateAccountOperator.kt @@ -1,6 +1,7 @@ package com.gemwallet.android.blockchain.operators import androidx.test.core.app.ApplicationProvider +import com.gemwallet.android.ext.available import com.gemwallet.android.testkit.LOCAL_KEYSTORE_TEST_PHRASE import com.gemwallet.android.testkit.TEST_PHRASE import com.gemwallet.android.testkit.gemstoneTestAccount @@ -45,7 +46,7 @@ class TestCreateAccountOperator { @Test fun testCreate_account_derive_address_matches_ios_local_keystore() { - Chain.entries.forEach { chain -> + Chain.available().forEach { chain -> val account = gemstoneTestAccount(context, chain = chain, phrase = LOCAL_KEYSTORE_TEST_PHRASE) assertEquals("Unexpected derived address for $chain", expectedAddress(chain), account.address) diff --git a/android/blockchain/src/main/kotlin/com/gemwallet/android/blockchain/operators/gemstone/GemMappers.kt b/android/blockchain/src/main/kotlin/com/gemwallet/android/blockchain/operators/gemstone/GemMappers.kt index 2b7fd076c8..747c9e5ffd 100644 --- a/android/blockchain/src/main/kotlin/com/gemwallet/android/blockchain/operators/gemstone/GemMappers.kt +++ b/android/blockchain/src/main/kotlin/com/gemwallet/android/blockchain/operators/gemstone/GemMappers.kt @@ -1,6 +1,5 @@ package com.gemwallet.android.blockchain.operators.gemstone -import com.gemwallet.android.ext.find import com.wallet.core.primitives.Account import com.wallet.core.primitives.Chain import uniffi.gemstone.GemKeystoreAccount @@ -13,5 +12,5 @@ internal fun GemKeystoreAccount.toAccount(): Account = Account( ) internal fun String.toPrimitiveChain(): Chain = - Chain.find(this) + Chain.entries.firstOrNull { it.string == this } ?: throw IllegalArgumentException("Unsupported chain: $this") diff --git a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/di/WalletModule.kt b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/di/WalletModule.kt index d45dfaddfa..1ad5be003c 100644 --- a/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/di/WalletModule.kt +++ b/android/data/coordinators/src/main/kotlin/com/gemwallet/android/data/coordinators/di/WalletModule.kt @@ -112,4 +112,4 @@ object WalletModule { ): SetCurrentWallet { return SetCurrentWalletImpl(sessionRepository, walletsRepository) } -} +} \ No newline at end of file diff --git a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/assets/AssetsRepository.kt b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/assets/AssetsRepository.kt index ad9cc32710..ac37e8b7c8 100644 --- a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/assets/AssetsRepository.kt +++ b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/assets/AssetsRepository.kt @@ -21,6 +21,7 @@ import com.gemwallet.android.data.service.store.database.entities.toUpdateRecord import com.gemwallet.android.domains.asset.chain import com.gemwallet.android.domains.asset.defaultBasic import com.gemwallet.android.ext.asset +import com.gemwallet.android.ext.available import com.gemwallet.android.ext.toAssetId import com.gemwallet.android.ext.toIdentifier import com.gemwallet.android.model.AssetBalance @@ -360,7 +361,7 @@ class AssetsRepository @Inject constructor( } suspend fun updateNativeAssetRanks() = withContext(Dispatchers.IO) { - val basics = Chain.entries.map { it.asset().defaultBasic } + val basics = Chain.available().map { it.asset().defaultBasic } assetsDao.insert(basics.map { it.toRecord() }) for (basic in basics) { runCatching { assetsDao.updateAssetRank(basic.asset.id.toIdentifier(), basic.score.rank) } diff --git a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/chains/ChainInfoRepository.kt b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/chains/ChainInfoRepository.kt index 5dbcf5138b..9e0765e793 100644 --- a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/chains/ChainInfoRepository.kt +++ b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/chains/ChainInfoRepository.kt @@ -1,9 +1,10 @@ package com.gemwallet.android.data.repositories.chains import com.gemwallet.android.domains.asset.defaultAssetRank +import com.gemwallet.android.ext.available import com.wallet.core.primitives.Chain import javax.inject.Inject class ChainInfoRepository @Inject constructor() { - fun getAll() = Chain.entries.sortedByDescending { it.defaultAssetRank } + fun getAll() = Chain.available().sortedByDescending { it.defaultAssetRank } } diff --git a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/tokens/TokensRepository.kt b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/tokens/TokensRepository.kt index 3b77403dba..e76ca7d25d 100644 --- a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/tokens/TokensRepository.kt +++ b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/tokens/TokensRepository.kt @@ -36,7 +36,7 @@ class TokensRepository ( if (query.isEmpty() && tags.isEmpty()) { return@withContext false } - val networkAssets = async { tokenService.search(query, chains.ifEmpty { Chain.entries }) } + val networkAssets = async { tokenService.search(query, chains.ifEmpty { Chain.entries.toList() }) } val tokens = runCatchingCancellable { searchAssets.searchAssets( query = query, diff --git a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/tokens/WalletSearchTokens.kt b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/tokens/WalletSearchTokens.kt index 97fca25799..16469c09a4 100644 --- a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/tokens/WalletSearchTokens.kt +++ b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/tokens/WalletSearchTokens.kt @@ -44,7 +44,7 @@ class WalletSearchTokens( if (scope.isAll && query.isEmpty()) { return@withContext false } - val chains = if (scope.isAll) scopeChains.ifEmpty { Chain.entries } else emptyList() + val chains = if (scope.isAll) scopeChains.ifEmpty { Chain.entries.toList() } else emptyList() val networkAssets = async { tokenService.search(query, chains) } val searchResult = runCatchingCancellable { gemSearch.search(query = query, chains = scopeChains, scope = scope) diff --git a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/wallets/WalletsRepositoryImpl.kt b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/wallets/WalletsRepositoryImpl.kt index 369be99bc9..65d3579ec5 100644 --- a/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/wallets/WalletsRepositoryImpl.kt +++ b/android/data/repositories/src/main/kotlin/com/gemwallet/android/data/repositories/wallets/WalletsRepositoryImpl.kt @@ -12,6 +12,7 @@ import com.gemwallet.android.data.service.store.database.entities.toDTO import com.gemwallet.android.data.service.store.database.entities.toRecord import com.gemwallet.android.domains.asset.defaultBasic import com.gemwallet.android.ext.asset +import com.gemwallet.android.ext.available import com.wallet.core.primitives.Account import com.wallet.core.primitives.Chain import com.wallet.core.primitives.Wallet @@ -78,10 +79,11 @@ class WalletsRepositoryImpl @Inject constructor( source: WalletSource ): Wallet { val accounts = mutableListOf() + val availableChains = Chain.available() val chains = if ((type == WalletType.Single || type == WalletType.PrivateKey) && chain != null) listOf( chain - ) else Chain.entries + ) else Chain.entries.filter(availableChains::contains) for (item in chains) { accounts.add(createAccount(type, data, item)) } diff --git a/android/data/repositories/src/test/kotlin/com/gemwallet/android/data/repositories/assets/AssetsRepositoryTest.kt b/android/data/repositories/src/test/kotlin/com/gemwallet/android/data/repositories/assets/AssetsRepositoryTest.kt index c21daccd6d..bddeca1306 100644 --- a/android/data/repositories/src/test/kotlin/com/gemwallet/android/data/repositories/assets/AssetsRepositoryTest.kt +++ b/android/data/repositories/src/test/kotlin/com/gemwallet/android/data/repositories/assets/AssetsRepositoryTest.kt @@ -21,11 +21,11 @@ import com.gemwallet.android.data.service.store.database.entities.mockDbAsset import com.gemwallet.android.data.service.store.database.entities.mockDbAssetInfo import com.gemwallet.android.domains.asset.defaultBasic import com.gemwallet.android.ext.asset +import com.gemwallet.android.ext.available import com.gemwallet.android.ext.isStakeSupported import com.gemwallet.android.ext.isSwapSupport import com.gemwallet.android.ext.toIdentifier import com.gemwallet.android.testkit.mockAccount -import com.gemwallet.android.testkit.mockAsset import com.gemwallet.android.testkit.mockAssetFull import com.gemwallet.android.testkit.mockAssetLink import com.gemwallet.android.testkit.mockAssetEthereum @@ -464,12 +464,15 @@ class AssetsRepositoryTest { every { sessionRepository.session() } returns sessionFlow mockkStatic("com.gemwallet.android.ext.ChainKt") mockkStatic("uniffi.gemstone.GemstoneKt") - Chain.entries.forEach { chain -> - every { chain.asset() } returns mockAsset(chain = chain) - every { chain.isSwapSupport() } returns false - every { chain.isStakeSupported() } returns false - every { assetDefaultRank(chain.string) } returns if (chain == Chain.Solana) 99 else 77 - } + every { Chain.available() } returns setOf(Chain.Solana, Chain.Ethereum) + every { Chain.Solana.asset() } returns mockAssetSolana() + every { Chain.Ethereum.asset() } returns mockAssetEthereum() + every { Chain.Solana.isSwapSupport() } returns true + every { Chain.Solana.isStakeSupported() } returns true + every { Chain.Ethereum.isSwapSupport() } returns true + every { Chain.Ethereum.isStakeSupported() } returns false + every { assetDefaultRank(Chain.Solana.string) } returns 99 + every { assetDefaultRank(Chain.Ethereum.string) } returns 77 val subject = createSubject() subject.updateNativeAssetRanks() diff --git a/android/data/services/store/src/androidTest/kotlin/com/gemwallet/android/service/store/Migration_85_86Test.kt b/android/data/services/store/src/androidTest/kotlin/com/gemwallet/android/service/store/Migration_85_86Test.kt new file mode 100644 index 0000000000..aa9c2084e0 --- /dev/null +++ b/android/data/services/store/src/androidTest/kotlin/com/gemwallet/android/service/store/Migration_85_86Test.kt @@ -0,0 +1,146 @@ +package com.gemwallet.android.service.store + +import androidx.room.testing.MigrationTestHelper +import androidx.sqlite.db.SupportSQLiteDatabase +import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.gemwallet.android.data.service.store.database.GemDatabase +import com.gemwallet.android.data.service.store.database.di.Migration_85_86 +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class Migration_85_86Test { + + private val testDb = "migration-85-86-test" + + @get:Rule + val helper = MigrationTestHelper( + InstrumentationRegistry.getInstrumentation(), + GemDatabase::class.java, + emptyList(), + FrameworkSQLiteOpenHelperFactory(), + ) + + @Before + fun setUp() { + InstrumentationRegistry.getInstrumentation().targetContext.deleteDatabase(testDb) + } + + @Test + fun migrate85To86RemovesSeiAndPreservesSeiEvm() { + helper.createDatabase(testDb, 85).apply { + seed() + close() + } + + val db = helper.runMigrationsAndValidate(testDb, 86, true, Migration_85_86) + + assertEquals(0, db.count("asset", "chain = 'sei'")) + assertEquals(2, db.count("asset", "chain = 'seievm'")) + assertEquals(0, db.count("accounts", "chain = 'sei'")) + assertEquals(1, db.count("accounts", "chain = 'seievm'")) + assertEquals(1, db.count("wallets")) + assertEquals(0, db.count("wallets_connections", "instr(chains, '\"sei\"') > 0")) + assertEquals(1, db.count("wallets_connections")) + assertEquals(0, db.count("in_app_notifications", "instr(item, '\"sei\"') > 0")) + assertEquals(1, db.count("in_app_notifications")) + assertEquals(0, db.count("contacts_addresses", "chain = 'sei'")) + assertEquals(1, db.count("contacts_addresses", "chain = 'seievm'")) + assertEquals(1, db.count("transactions")) + assertEquals(1, db.count("tx_swap_metadata")) + assertEquals(1, db.count("prices")) + assertEquals(1, db.count("price_alerts")) + assertEquals(1, db.count("recent_assets")) + assertEquals(1, db.count("banners")) + assertEquals("[]", db.string("SELECT associations FROM asset WHERE id = 'seievm'")) + + db.query("PRAGMA foreign_key_check").use { cursor -> + assertFalse(cursor.moveToFirst()) + } + db.close() + } + + private fun SupportSQLiteDatabase.seed() { + execSQL("INSERT INTO wallets (id, name, type, position, pinned, `index`, source) VALUES ('wallet', 'Wallet', 'multicoin', 0, 0, 0, 'Import')") + execSQL("INSERT INTO contacts (id, name, createdAt, updatedAt) VALUES ('contact', 'Contact', 0, 0)") + insertAsset("sei", "sei", "[]") + insertAsset("sei_token", "sei", "[]") + insertAsset("seievm", "seievm", "[{\"assetId\":\"sei\"}]") + insertAsset("seievm_token", "seievm", "[]") + execSQL("INSERT INTO accounts (wallet_id, derivation_path, address, chain) VALUES ('wallet', '', 'sei-address', 'sei')") + execSQL("INSERT INTO accounts (wallet_id, derivation_path, address, chain) VALUES ('wallet', '', 'seievm-address', 'seievm')") + execSQL("INSERT INTO contacts_addresses (id, contactId, address, chain) VALUES ('sei-contact', 'contact', 'sei-address', 'sei')") + execSQL("INSERT INTO contacts_addresses (id, contactId, address, chain) VALUES ('seievm-contact', 'contact', 'seievm-address', 'seievm')") + seedDependentRecords() + execSQL("INSERT INTO wallets_connections (id, wallet_id, session_id, state, chains, created_at, expire_at, app_name, app_description, app_url, app_icon) VALUES ('sei-connection', 'wallet', 'sei-session', 'active', '[\"sei\"]', 0, 1, 'App', 'App', 'https://example.com', 'https://example.com/icon')") + execSQL("INSERT INTO wallets_connections (id, wallet_id, session_id, state, chains, created_at, expire_at, app_name, app_description, app_url, app_icon) VALUES ('seievm-connection', 'wallet', 'seievm-session', 'active', '[\"seievm\"]', 0, 1, 'App', 'App', 'https://example.com', 'https://example.com/icon')") + execSQL("INSERT INTO in_app_notifications (id, wallet_id, created_at, item) VALUES ('sei-notification', 'wallet', 0, '{\"chain\":\"sei\"}')") + execSQL("INSERT INTO in_app_notifications (id, wallet_id, created_at, item) VALUES ('seievm-notification', 'wallet', 0, '{\"chain\":\"seievm\"}')") + insertTransaction("sei-transaction", "sei", "seievm") + insertTransaction("sei-fee-transaction", "seievm", "sei") + insertTransaction("seievm-transaction", "seievm", "seievm") + execSQL("INSERT INTO tx_swap_metadata (tx_id, from_asset_id, to_asset_id, from_amount, to_amount) VALUES ('sei-transaction', 'seievm', 'seievm', '1', '1')") + execSQL("INSERT INTO tx_swap_metadata (tx_id, from_asset_id, to_asset_id, from_amount, to_amount) VALUES ('sei-fee-transaction', 'seievm', 'seievm', '1', '1')") + execSQL("INSERT INTO tx_swap_metadata (tx_id, from_asset_id, to_asset_id, from_amount, to_amount) VALUES ('seievm-transaction', 'seievm', 'seievm', '1', '1')") + execSQL("INSERT INTO prices (asset_id, currency) VALUES ('sei_token', 'USD'), ('seievm_token', 'USD')") + execSQL("INSERT INTO price_alerts (assetId, currency, enabled) VALUES ('sei_token', 'USD', 1), ('seievm_token', 'USD', 1)") + execSQL("INSERT INTO recent_assets (asset_id, wallet_id, to_asset_id, type, addedAt) VALUES ('seievm', 'wallet', 'sei_token', 'swap', 0), ('seievm', 'wallet', NULL, 'asset', 0)") + execSQL("INSERT INTO banners (wallet_id, asset_id, chain, state, event) VALUES ('wallet', 'sei_token', NULL, 'active', 'event'), ('wallet', 'seievm_token', 'seievm', 'active', 'event')") + } + + private fun SupportSQLiteDatabase.seedDependentRecords() { + execSQL("INSERT INTO addresses (chain, address, walletId, name, type, status) VALUES ('sei', 'address', 'wallet', 'Address', 'address', 'active')") + execSQL("INSERT INTO nodes (url, status, priority, chain) VALUES ('https://sei.example', 'active', 0, 'sei')") + execSQL("INSERT INTO asset_links (asset_id, name, url) VALUES ('sei_token', 'website', 'https://sei.example')") + execSQL("INSERT INTO asset_market (asset_id) VALUES ('sei_token')") + execSQL("INSERT INTO fiat_transactions (id, walletId, assetId, transactionType, provider, status, fiatAmount, fiatCurrency, value, createdAt) VALUES ('fiat', 'wallet', 'sei_token', 'buy', 'provider', 'complete', 1, 'USD', '1', 0)") + execSQL("INSERT INTO stake_validators (id, assetId, validatorId, name, isActive, commission, apr, providerType) VALUES ('validator', 'sei', 'validator', 'Validator', 1, 0, 0, 'stake')") + execSQL("INSERT INTO stake_delegations (id, walletId, assetId, validatorId, state, delegationId, balance, shares, rewards) VALUES ('delegation', 'wallet', 'sei', 'validator', 'active', 'delegation', '1', '1', '0')") + execSQL("INSERT INTO nft_collections (id, name, chain, contractAddress, imageUrl, previewImageUrl, originalSourceUrl) VALUES ('collection', 'Collection', 'sei', 'contract', '', '', '')") + execSQL("INSERT INTO nft_assets (id, collection_id, token_id, token_type, name, chain, image_url, preview_image_url, original_image_url) VALUES ('nft', 'collection', '1', 'token', 'NFT', 'sei', '', '', '')") + execSQL("INSERT INTO nft_assets_associations (wallet_id, asset_id) VALUES ('wallet', 'nft')") + execSQL("INSERT INTO perpetuals (id, name, provider, assetId, identifier, price, pricePercentChange24h, openInterest, volume24h, funding, maxLeverage, isIsolatedOnly, isPinned) VALUES ('perpetual', 'Perpetual', 'provider', 'sei_token', 'identifier', 1, 0, 0, 0, 0, 1, 0, 0)") + execSQL("INSERT INTO perpetuals_positions (id, walletId, perpetualId, assetId, size, sizeValue, leverage, marginType, direction, marginAmount, pnl, updatedAt) VALUES ('position', 'wallet', 'perpetual', 'sei_token', 1, 1, 1, 'cross', 'long', 1, 0, 0)") + execSQL("INSERT INTO search (query, assetId, priority) VALUES ('sei', 'sei_token', 0)") + execSQL("INSERT INTO search (query, perpetualId, priority) VALUES ('perpetual', 'perpetual', 0)") + execSQL( + "INSERT INTO balances (asset_id, wallet_id, available, available_amount, frozen, frozen_amount, locked, locked_amount, staked, staked_amount, pending, pending_amount, rewards, rewards_amount, reserved, reserved_amount, withdrawable, withdrawableAmount, total_amount, is_active, is_pinned, is_visible, list_position) " + + "VALUES ('sei_token', 'wallet', '0', 0, '0', 0, '0', 0, '0', 0, '0', 0, '0', 0, '0', 0, '0', 0, 0, 1, 0, 1, 0)", + ) + } + + private fun SupportSQLiteDatabase.insertAsset(id: String, chain: String, associations: String) { + execSQL( + "INSERT INTO asset (id, name, symbol, decimals, type, chain, is_enabled, is_buy_enabled, is_sell_enabled, is_swap_enabled, is_stake_enabled, rank, updated_at, associations) " + + "VALUES (?, ?, ?, 6, 'NATIVE', ?, 1, 0, 0, 0, 0, 1, 0, ?)", + arrayOf(id, id, id, chain, associations), + ) + } + + private fun SupportSQLiteDatabase.insertTransaction(id: String, assetId: String, feeAssetId: String) { + execSQL( + "INSERT INTO transactions (id, walletId, hash, assetId, feeAssetId, owner, recipient, state, type, blockNumber, sequence, fee, value, direction, createdAt, updatedAt) " + + "VALUES (?, 'wallet', ?, ?, ?, 'owner', 'recipient', 'confirmed', 'transfer', '1', '1', '1', '1', 'outgoing', 0, 0)", + arrayOf(id, id, assetId, feeAssetId), + ) + } + + private fun SupportSQLiteDatabase.count(table: String, where: String? = null): Long { + val suffix = where?.let { " WHERE $it" }.orEmpty() + return query("SELECT COUNT(*) FROM $table$suffix").use { cursor -> + cursor.moveToFirst() + cursor.getLong(0) + } + } + + private fun SupportSQLiteDatabase.string(sql: String): String = query(sql).use { cursor -> + cursor.moveToFirst() + cursor.getString(0) + } +} diff --git a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/ChainConverters.kt b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/ChainConverters.kt index dc80912e87..03cce0b6da 100644 --- a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/ChainConverters.kt +++ b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/ChainConverters.kt @@ -1,7 +1,6 @@ package com.gemwallet.android.data.service.store.database import androidx.room.TypeConverter -import com.gemwallet.android.ext.find import com.gemwallet.android.serializer.jsonEncoder import com.wallet.core.primitives.Chain import kotlinx.serialization.builtins.ListSerializer @@ -14,7 +13,7 @@ class ChainConverters { @TypeConverter fun toChain(value: String): Chain { - return Chain.find(value) + return Chain.entries.firstOrNull { it.string == value } ?: throw IllegalArgumentException("Unknown chain: $value") } diff --git a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/WalletsDao.kt b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/WalletsDao.kt index d1845b7591..9f2175b446 100644 --- a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/WalletsDao.kt +++ b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/WalletsDao.kt @@ -28,4 +28,4 @@ interface WalletsDao { @Delete suspend fun delete(account: DbWallet) -} +} \ No newline at end of file diff --git a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/DatabaseModule.kt b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/DatabaseModule.kt index 8627f8e3db..9644270e8f 100644 --- a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/DatabaseModule.kt +++ b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/DatabaseModule.kt @@ -40,10 +40,7 @@ import javax.inject.Singleton object DatabaseModule { @Singleton @Provides - fun provideRoom( - @ApplicationContext context: Context, - passwordStore: PasswordStore, - ): GemDatabase = Room.databaseBuilder( + fun provideRoom(@ApplicationContext context: Context, passwordStore: PasswordStore): GemDatabase = Room.databaseBuilder( context = context, klass = GemDatabase::class.java, name = "gem.db", diff --git a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_63_64.kt b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_63_64.kt index 9ca7b81d75..f3854e966a 100644 --- a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_63_64.kt +++ b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_63_64.kt @@ -39,7 +39,7 @@ class Migration_63_64(context: Context, private val passwordStore: PasswordStore val walletId = accountsCursor.getString(0) val address = accountsCursor.getString(1) val chainEnumName = accountsCursor.getString(2) - val chain = findLegacyChain(chainEnumName) ?: continue + val chain = Chain.entries.firstOrNull { it.name == chainEnumName } ?: continue val account = Account( chain = chain, address = address, @@ -114,7 +114,4 @@ class Migration_63_64(context: Context, private val passwordStore: PasswordStore db.execSQL("DELETE FROM transactions WHERE walletId = ?", arrayOf(walletId)) } } - - private fun findLegacyChain(name: String): Chain? = - Chain.entries.firstOrNull { it.name == name } } diff --git a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_85_86.kt b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_85_86.kt index fba10ebc38..8c95f76909 100644 --- a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_85_86.kt +++ b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_85_86.kt @@ -4,73 +4,82 @@ import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase object Migration_85_86 : Migration(85, 86) { - internal const val SEI = "sei" - override fun migrate(db: SupportSQLiteDatabase) { - removeChains(db, setOf(SEI)) - } -} - -internal fun removeChains(db: SupportSQLiteDatabase, chains: Set) { - for (chain in chains) { - chainRemovalStatements(chain).forEach { statement -> - db.execSQL(statement.sql, statement.arguments.toTypedArray()) - } + removeChain(db, "sei") } } -internal data class MigrationStatement( - val sql: String, - val arguments: List, -) { - override fun toString(): String = "$sql | ${arguments.joinToString()}" -} - -internal fun chainRemovalStatements(chain: String): List { +private fun removeChain(db: SupportSQLiteDatabase, chain: String) { val assetIdPattern = "${chain}\\_%" val quotedChain = "\"$chain\"" val quotedAssetIdPrefix = "\"${chain}_" - fun assetId(column: String) = "$column = ? OR $column LIKE ? ESCAPE '\\'" - - return listOf( - MigrationStatement( - "DELETE FROM wallets_connections WHERE instr(chains, ?) > 0", - listOf(quotedChain), - ), - MigrationStatement( - "DELETE FROM in_app_notifications WHERE instr(item, ?) > 0 OR instr(item, ?) > 0", - listOf(quotedChain, quotedAssetIdPrefix), - ), - MigrationStatement( - "UPDATE asset SET associations = '[]' WHERE instr(associations, ?) > 0 OR instr(associations, ?) > 0", - listOf(quotedChain, quotedAssetIdPrefix), - ), - MigrationStatement("DELETE FROM contacts_addresses WHERE chain = ?", listOf(chain)), - MigrationStatement( - "DELETE FROM tx_swap_metadata WHERE ${assetId("from_asset_id")} OR ${assetId("to_asset_id")}", - listOf(chain, assetIdPattern, chain, assetIdPattern), - ), - MigrationStatement( - "DELETE FROM transactions WHERE ${assetId("assetId")} OR ${assetId("feeAssetId")}", - listOf(chain, assetIdPattern, chain, assetIdPattern), - ), - MigrationStatement( - "DELETE FROM prices WHERE ${assetId("asset_id")}", - listOf(chain, assetIdPattern), - ), - MigrationStatement( - "DELETE FROM price_alerts WHERE ${assetId("assetId")}", - listOf(chain, assetIdPattern), - ), - MigrationStatement( - "DELETE FROM recent_assets WHERE ${assetId("asset_id")} OR ${assetId("to_asset_id")}", - listOf(chain, assetIdPattern, chain, assetIdPattern), - ), - MigrationStatement( - "DELETE FROM banners WHERE chain = ? OR ${assetId("asset_id")}", - listOf(chain, chain, assetIdPattern), - ), - ) + listOf("nft_assets", "nft_collections", "nodes", "addresses", "accounts", "asset").map { table -> - MigrationStatement("DELETE FROM $table WHERE chain = ?", listOf(chain)) + fun deleteAssetReferences(table: String, column: String) { + db.execSQL( + "DELETE FROM $table WHERE $column = ? OR $column LIKE ? ESCAPE '\\'", + arrayOf(chain, assetIdPattern), + ) } + + db.execSQL("DELETE FROM wallets_connections WHERE instr(chains, ?) > 0", arrayOf(quotedChain)) + db.execSQL( + "DELETE FROM in_app_notifications WHERE instr(item, ?) > 0 OR instr(item, ?) > 0", + arrayOf(quotedChain, quotedAssetIdPrefix), + ) + db.execSQL( + "UPDATE asset SET associations = '[]' WHERE instr(associations, ?) > 0 OR instr(associations, ?) > 0", + arrayOf(quotedChain, quotedAssetIdPrefix), + ) + db.execSQL("DELETE FROM contacts_addresses WHERE chain = ?", arrayOf(chain)) + db.execSQL( + "DELETE FROM tx_swap_metadata WHERE " + + "from_asset_id = ? OR from_asset_id LIKE ? ESCAPE '\\' OR " + + "to_asset_id = ? OR to_asset_id LIKE ? ESCAPE '\\' OR " + + "tx_id IN (SELECT id FROM transactions WHERE " + + "assetId = ? OR assetId LIKE ? ESCAPE '\\' OR " + + "feeAssetId = ? OR feeAssetId LIKE ? ESCAPE '\\')", + arrayOf(chain, assetIdPattern, chain, assetIdPattern, chain, assetIdPattern, chain, assetIdPattern), + ) + db.execSQL( + "DELETE FROM transactions WHERE assetId = ? OR assetId LIKE ? ESCAPE '\\' OR " + + "feeAssetId = ? OR feeAssetId LIKE ? ESCAPE '\\'", + arrayOf(chain, assetIdPattern, chain, assetIdPattern), + ) + db.execSQL( + "DELETE FROM search WHERE assetId = ? OR assetId LIKE ? ESCAPE '\\' OR " + + "perpetualId IN (SELECT id FROM perpetuals WHERE assetId = ? OR assetId LIKE ? ESCAPE '\\')", + arrayOf(chain, assetIdPattern, chain, assetIdPattern), + ) + db.execSQL( + "DELETE FROM perpetuals_positions WHERE assetId = ? OR assetId LIKE ? ESCAPE '\\' OR " + + "perpetualId IN (SELECT id FROM perpetuals WHERE assetId = ? OR assetId LIKE ? ESCAPE '\\')", + arrayOf(chain, assetIdPattern, chain, assetIdPattern), + ) + deleteAssetReferences("perpetuals", "assetId") + deleteAssetReferences("stake_delegations", "assetId") + deleteAssetReferences("stake_validators", "assetId") + db.execSQL( + "DELETE FROM nft_assets_associations WHERE asset_id IN (SELECT id FROM nft_assets WHERE chain = ?)", + arrayOf(chain), + ) + db.execSQL("DELETE FROM nft_assets WHERE chain = ?", arrayOf(chain)) + db.execSQL("DELETE FROM nft_collections WHERE chain = ?", arrayOf(chain)) + deleteAssetReferences("balances", "asset_id") + deleteAssetReferences("asset_links", "asset_id") + deleteAssetReferences("asset_market", "asset_id") + deleteAssetReferences("fiat_transactions", "assetId") + deleteAssetReferences("prices", "asset_id") + deleteAssetReferences("price_alerts", "assetId") + db.execSQL( + "DELETE FROM recent_assets WHERE asset_id = ? OR asset_id LIKE ? ESCAPE '\\' OR " + + "to_asset_id = ? OR to_asset_id LIKE ? ESCAPE '\\'", + arrayOf(chain, assetIdPattern, chain, assetIdPattern), + ) + db.execSQL( + "DELETE FROM banners WHERE chain = ? OR asset_id = ? OR asset_id LIKE ? ESCAPE '\\'", + arrayOf(chain, chain, assetIdPattern), + ) + db.execSQL("DELETE FROM nodes WHERE chain = ?", arrayOf(chain)) + db.execSQL("DELETE FROM addresses WHERE chain = ?", arrayOf(chain)) + db.execSQL("DELETE FROM accounts WHERE chain = ?", arrayOf(chain)) + db.execSQL("DELETE FROM asset WHERE chain = ?", arrayOf(chain)) } diff --git a/android/data/services/store/src/test/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_85_86Test.kt b/android/data/services/store/src/test/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_85_86Test.kt deleted file mode 100644 index 8cef2c1efa..0000000000 --- a/android/data/services/store/src/test/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_85_86Test.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.gemwallet.android.data.service.store.database.di - -import org.junit.Assert.assertEquals -import org.junit.Test - -class Migration_85_86Test { - - @Test - fun chainRemovalStatementsUseExactSeiIdentifiers() { - assertEquals( - """ - DELETE FROM wallets_connections WHERE instr(chains, ?) > 0 | "sei" - DELETE FROM in_app_notifications WHERE instr(item, ?) > 0 OR instr(item, ?) > 0 | "sei", "sei_ - UPDATE asset SET associations = '[]' WHERE instr(associations, ?) > 0 OR instr(associations, ?) > 0 | "sei", "sei_ - DELETE FROM contacts_addresses WHERE chain = ? | sei - DELETE FROM tx_swap_metadata WHERE from_asset_id = ? OR from_asset_id LIKE ? ESCAPE '\' OR to_asset_id = ? OR to_asset_id LIKE ? ESCAPE '\' | sei, sei\_%, sei, sei\_% - DELETE FROM transactions WHERE assetId = ? OR assetId LIKE ? ESCAPE '\' OR feeAssetId = ? OR feeAssetId LIKE ? ESCAPE '\' | sei, sei\_%, sei, sei\_% - DELETE FROM prices WHERE asset_id = ? OR asset_id LIKE ? ESCAPE '\' | sei, sei\_% - DELETE FROM price_alerts WHERE assetId = ? OR assetId LIKE ? ESCAPE '\' | sei, sei\_% - DELETE FROM recent_assets WHERE asset_id = ? OR asset_id LIKE ? ESCAPE '\' OR to_asset_id = ? OR to_asset_id LIKE ? ESCAPE '\' | sei, sei\_%, sei, sei\_% - DELETE FROM banners WHERE chain = ? OR asset_id = ? OR asset_id LIKE ? ESCAPE '\' | sei, sei, sei\_% - DELETE FROM nft_assets WHERE chain = ? | sei - DELETE FROM nft_collections WHERE chain = ? | sei - DELETE FROM nodes WHERE chain = ? | sei - DELETE FROM addresses WHERE chain = ? | sei - DELETE FROM accounts WHERE chain = ? | sei - DELETE FROM asset WHERE chain = ? | sei - """.trimIndent(), - chainRemovalStatements(Migration_85_86.SEI).joinToString("\n"), - ) - } -} diff --git a/android/features/assets/viewmodels/src/main/kotlin/com/gemwallet/android/features/assets/viewmodels/NetworkAssetsViewModel.kt b/android/features/assets/viewmodels/src/main/kotlin/com/gemwallet/android/features/assets/viewmodels/NetworkAssetsViewModel.kt index 0b22f82015..f1abfadad6 100644 --- a/android/features/assets/viewmodels/src/main/kotlin/com/gemwallet/android/features/assets/viewmodels/NetworkAssetsViewModel.kt +++ b/android/features/assets/viewmodels/src/main/kotlin/com/gemwallet/android/features/assets/viewmodels/NetworkAssetsViewModel.kt @@ -9,10 +9,10 @@ import com.gemwallet.android.application.asset_select.coordinators.SwitchAssetVi import com.gemwallet.android.application.assets.coordinators.HideAsset import com.gemwallet.android.application.assets.coordinators.ToggleAssetPin import com.gemwallet.android.application.session.coordinators.GetSession +import com.gemwallet.android.ui.R import com.gemwallet.android.domains.asset.aggregates.AssetInfoDataAggregate import com.gemwallet.android.domains.asset.aggregates.AssetRowNaming import com.gemwallet.android.domains.asset.aggregates.toAssetInfoDataAggregate -import com.gemwallet.android.ui.R import com.gemwallet.android.ui.models.navigation.RouteArgument import com.wallet.core.primitives.AssetId import com.wallet.core.primitives.AssetType diff --git a/android/features/settings/contacts/presents/src/main/kotlin/com/gemwallet/android/features/settings/contacts/presents/ContactChainSelectScene.kt b/android/features/settings/contacts/presents/src/main/kotlin/com/gemwallet/android/features/settings/contacts/presents/ContactChainSelectScene.kt index 002e51cf45..00644bb10d 100644 --- a/android/features/settings/contacts/presents/src/main/kotlin/com/gemwallet/android/features/settings/contacts/presents/ContactChainSelectScene.kt +++ b/android/features/settings/contacts/presents/src/main/kotlin/com/gemwallet/android/features/settings/contacts/presents/ContactChainSelectScene.kt @@ -19,7 +19,7 @@ fun ContactChainSelectScene( val chainFilter = rememberTextFieldState() val query = chainFilter.text.toString() - val chains = remember(query) { Chain.entries.filter(query.lowercase()) } + val chains = remember(query) { Chain.entries.toList().filter(query.lowercase()) } SelectChain( chains = chains, diff --git a/android/features/transfer_amount/presents/src/main/kotlin/com/gemwallet/android/features/transfer_amount/presents/ValidatorsScene.kt b/android/features/transfer_amount/presents/src/main/kotlin/com/gemwallet/android/features/transfer_amount/presents/ValidatorsScene.kt index 05e2965235..3721718dae 100644 --- a/android/features/transfer_amount/presents/src/main/kotlin/com/gemwallet/android/features/transfer_amount/presents/ValidatorsScene.kt +++ b/android/features/transfer_amount/presents/src/main/kotlin/com/gemwallet/android/features/transfer_amount/presents/ValidatorsScene.kt @@ -102,4 +102,4 @@ fun PreviewValidatorsScene() { onSelect = {}, ) } -} +} \ No newline at end of file diff --git a/android/features/wallet-details/viewmodels/build.gradle.kts b/android/features/wallet-details/viewmodels/build.gradle.kts index 0c5521ab65..4e774d1c8a 100644 --- a/android/features/wallet-details/viewmodels/build.gradle.kts +++ b/android/features/wallet-details/viewmodels/build.gradle.kts @@ -56,4 +56,4 @@ dependencies { testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) -} +} \ No newline at end of file diff --git a/android/gemcore/src/main/kotlin/com/gemwallet/android/domains/asset/IconUrlGeneration.kt b/android/gemcore/src/main/kotlin/com/gemwallet/android/domains/asset/IconUrlGeneration.kt index f27ef4fceb..5370f147b1 100644 --- a/android/gemcore/src/main/kotlin/com/gemwallet/android/domains/asset/IconUrlGeneration.kt +++ b/android/gemcore/src/main/kotlin/com/gemwallet/android/domains/asset/IconUrlGeneration.kt @@ -1,6 +1,5 @@ package com.gemwallet.android.domains.asset -import com.gemwallet.android.Constants import com.gemwallet.android.ext.asset import com.gemwallet.android.ext.toIdentifier import com.gemwallet.android.ext.twoSubtokenIds @@ -9,6 +8,7 @@ import com.wallet.core.primitives.Asset import com.wallet.core.primitives.AssetId import com.wallet.core.primitives.AssetSubtype import com.wallet.core.primitives.AssetType +import com.gemwallet.android.Constants import com.wallet.core.primitives.Chain import com.wallet.core.primitives.DelegationValidator import com.wallet.core.primitives.FiatProvider @@ -19,7 +19,13 @@ import uniffi.gemstone.SwapperProvider //fun Int.getDrawableUri() = "android.resource://com.gemwallet.android/drawable/$this" -fun Chain.getIconUrl(): String = "file:///android_asset/chains/icons/${string}.svg" +fun Chain.getIconUrl(): String { + val icon = when (this) { + Chain.SeiEvm -> "sei" + else -> string + } + return "file:///android_asset/chains/icons/${icon}.svg" +} fun AssetId.getIconUrl(): String = when { tokenId.isNullOrEmpty() -> when (chain) { diff --git a/android/gemcore/src/main/kotlin/com/gemwallet/android/ext/AssetId.kt b/android/gemcore/src/main/kotlin/com/gemwallet/android/ext/AssetId.kt index c75aa708e5..396fbba20b 100644 --- a/android/gemcore/src/main/kotlin/com/gemwallet/android/ext/AssetId.kt +++ b/android/gemcore/src/main/kotlin/com/gemwallet/android/ext/AssetId.kt @@ -13,7 +13,7 @@ fun AssetId.type() = if (tokenId.isNullOrEmpty()) AssetSubtype.NATIVE else Asset fun String.toAssetId(): AssetId? { val components = split("_", limit = 2) val chainId = components.firstOrNull() ?: return null - val chain = Chain.find(chainId) ?: return null + val chain = Chain.entries.firstOrNull { it.string == chainId } ?: return null val token = if (components.size > 1) components[1] else null return AssetId(chain, token) } @@ -22,4 +22,4 @@ fun AssetId.twoSubtokenIds(): Pair? = tokenId ?.split("::") ?.takeIf { it.size >= 2 }?.let { Pair(it[0], it[1]) - } + } \ No newline at end of file diff --git a/android/gemcore/src/main/kotlin/com/gemwallet/android/ext/Chain.kt b/android/gemcore/src/main/kotlin/com/gemwallet/android/ext/Chain.kt index 139a35f1e3..af76b1c8b0 100644 --- a/android/gemcore/src/main/kotlin/com/gemwallet/android/ext/Chain.kt +++ b/android/gemcore/src/main/kotlin/com/gemwallet/android/ext/Chain.kt @@ -16,11 +16,8 @@ import uniffi.gemstone.validateAddress import uniffi.gemstone.checksumAddress as gemstoneChecksumAddress import java.math.BigInteger -private val config by lazy { Config() } -private val allChains: List = Chain.entries - private val chainAssetCache: Map by lazy { - allChains.associateWith { chain -> + Chain.entries.associateWith { chain -> val wrapper = uniffi.gemstone.chainAssetWrapper(chain.string) ChainAsset( asset = wrapper.asset.toDTO(), @@ -29,16 +26,10 @@ private val chainAssetCache: Map by lazy { } } -private val chainConfigCache by lazy { - allChains.associateWith { chain -> config.getChainConfig(chain.string) } -} - private fun Chain.chainAsset(): ChainAsset { return chainAssetCache[this] ?: throw IllegalArgumentException("Unsupported chain: $string") } -private fun Chain.chainConfig() = chainConfigCache[this] ?: throw IllegalArgumentException("Unsupported chain: $string") - fun Chain.assetType(): AssetType? { return when (this) { Chain.OpBNB, @@ -115,19 +106,19 @@ fun Chain.toEVM(): EVMChain? { return EVMChain.entries.firstOrNull { it.string == string } } -fun Chain.getReserveBalance(): BigInteger = chainConfig().accountActivationFee?.toBigInteger() ?: BigInteger.ZERO +fun Chain.getReserveBalance(): BigInteger = Config().getChainConfig(this.string).accountActivationFee?.toBigInteger() ?: BigInteger.ZERO -fun Chain.getTokenActivationFee(): BigInteger = chainConfig().tokenActivationFee?.toBigInteger() ?: BigInteger.ZERO +fun Chain.getTokenActivationFee(): BigInteger = Config().getChainConfig(this.string).tokenActivationFee?.toBigInteger() ?: BigInteger.ZERO -fun Chain.getReserveBalanceUrl(): String? = chainConfig().accountActivationFeeUrl +fun Chain.getReserveBalanceUrl(): String? = Config().getChainConfig(this.string).accountActivationFeeUrl -fun Chain.getMinimumAccountBalance(): Long = chainConfig().minimumAccountBalance?.toLong() ?: 0L +fun Chain.getMinimumAccountBalance(): Long = Config().getChainConfig(this.string).minimumAccountBalance?.toLong() ?: 0L -fun Chain.isStakeSupported(): Boolean = chainConfig().isStakeSupported +fun Chain.isStakeSupported(): Boolean = Config().getChainConfig(this.string).isStakeSupported -fun Chain.isNftSupported(): Boolean = chainConfig().isNftSupported +fun Chain.isNftSupported(): Boolean = Config().getChainConfig(this.string).isNftSupported -fun Chain.isDefiSupported(): Boolean = chainConfig().isDefiSupported +fun Chain.isDefiSupported(): Boolean = Config().getChainConfig(this.string).isDefiSupported fun Chain.asset(): Asset { return chainAsset().asset @@ -137,7 +128,11 @@ fun Chain.networkName(): String { return chainAsset().networkName } -fun Chain.Companion.find(value: String): Chain? = allChains.firstOrNull { it.string == value } +fun Chain.Companion.findByString(value: String): Chain? { + return Chain.entries.firstOrNull { it.string == value } +} + +fun Chain.Companion.available() = Chain.entries.toSet() fun List.filter(query: String): List { return if (query.isBlank()) this else filter { chain -> @@ -209,12 +204,12 @@ fun Chain.toChainType(): ChainType { fun Chain.getNetworkId(): String { - return chainConfig().networkId + return Config().getChainConfig(string).networkId } fun Chain.isSwapSupport(): Boolean { return try { - chainConfig().isSwapSupported + Config().getChainConfig(string).isSwapSupported } catch (_: Throwable) { false } @@ -223,10 +218,10 @@ fun Chain.isSwapSupport(): Boolean { fun Chain.Companion.swapSupport() = Chain.entries.filter { it.isSwapSupport() } fun Chain.feeUnitType() = FeeUnitType.entries.firstOrNull { - it.string == chainConfig().feeUnitType + it.string == Config().getChainConfig(string).feeUnitType } -fun Chain.isMemoSupport() = chainConfig().isMemoSupported +fun Chain.isMemoSupport() = Config().getChainConfig(string).isMemoSupported fun Chain.isValidAddress(address: String): Boolean = validateAddress(checksumAddress(address), string) @@ -235,7 +230,7 @@ fun Chain.checksumAddress(address: String): String = gemstoneChecksumAddress(add fun Chain.isPrivateKeyImportSupported(): Boolean = supportsPrivateKeyImport(string) fun uniffi.gemstone.Chain.toChain(): Chain? { - return Chain.find(this) + return Chain.entries.firstOrNull { it.string == this } } val Chain.Companion.referralChain: Chain get() = Chain.Ethereum diff --git a/android/gemcore/src/main/kotlin/com/gemwallet/android/ext/StakeChain.kt b/android/gemcore/src/main/kotlin/com/gemwallet/android/ext/StakeChain.kt index 8a57684b21..64393a976c 100644 --- a/android/gemcore/src/main/kotlin/com/gemwallet/android/ext/StakeChain.kt +++ b/android/gemcore/src/main/kotlin/com/gemwallet/android/ext/StakeChain.kt @@ -53,4 +53,4 @@ fun StakeChain.toGemStakeChain(): GemStakeChain = when (this) { StakeChain.Tron -> GemStakeChain.TRON StakeChain.Aptos -> GemStakeChain.APTOS StakeChain.HyperCore -> GemStakeChain.HYPER_CORE -} +} \ No newline at end of file diff --git a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/TransactionId.kt b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/TransactionId.kt index 8c03a6bcd0..9eeabd570c 100644 --- a/android/gemcore/src/main/kotlin/com/wallet/core/primitives/TransactionId.kt +++ b/android/gemcore/src/main/kotlin/com/wallet/core/primitives/TransactionId.kt @@ -1,6 +1,5 @@ package com.wallet.core.primitives -import com.gemwallet.android.ext.find import com.gemwallet.android.serializer.TransactionIdSerializer import kotlinx.serialization.Serializable @@ -16,7 +15,7 @@ data class TransactionId( return null } - val chain = Chain.find(components[0]) ?: return null + val chain = Chain.entries.firstOrNull { it.string == components[0] } ?: return null return TransactionId(chain, components[1]) } } diff --git a/android/gemcore/src/test/kotlin/com/gemwallet/android/ext/ChainExtTest.kt b/android/gemcore/src/test/kotlin/com/gemwallet/android/ext/ChainExtTest.kt index 44998eae4b..d78ecf15fb 100644 --- a/android/gemcore/src/test/kotlin/com/gemwallet/android/ext/ChainExtTest.kt +++ b/android/gemcore/src/test/kotlin/com/gemwallet/android/ext/ChainExtTest.kt @@ -16,7 +16,7 @@ class ChainExtTest { assertEquals(AssetType.ERC20, Chain.SeiEvm.assetType()) assertEquals(EVMChain.SeiEvm, Chain.SeiEvm.toEVM()) assertEquals(ChainType.Ethereum, Chain.SeiEvm.toChainType()) - assertEquals("file:///android_asset/chains/icons/seievm.svg", Chain.SeiEvm.getIconUrl()) + assertEquals("file:///android_asset/chains/icons/sei.svg", Chain.SeiEvm.getIconUrl()) } @Test diff --git a/android/gemcore/src/testFixtures/kotlin/com/gemwallet/android/testkit/GemstoneTestKeys.kt b/android/gemcore/src/testFixtures/kotlin/com/gemwallet/android/testkit/GemstoneTestKeys.kt index d8b52f6778..250b900ff9 100644 --- a/android/gemcore/src/testFixtures/kotlin/com/gemwallet/android/testkit/GemstoneTestKeys.kt +++ b/android/gemcore/src/testFixtures/kotlin/com/gemwallet/android/testkit/GemstoneTestKeys.kt @@ -1,7 +1,6 @@ package com.gemwallet.android.testkit import android.content.Context -import com.gemwallet.android.ext.find import com.gemwallet.android.ext.words import com.wallet.core.primitives.Account import com.wallet.core.primitives.Chain @@ -50,6 +49,6 @@ private fun GemKeystoreAccount.toAccount(): Account { } private fun String.toPrimitiveChain(): Chain { - return Chain.find(this) + return Chain.entries.firstOrNull { it.string == this } ?: throw IllegalArgumentException("Unsupported chain: $this") } diff --git a/core/crates/primitives/src/chain.rs b/core/crates/primitives/src/chain.rs index ee1b5c4675..e53c0463ff 100644 --- a/core/crates/primitives/src/chain.rs +++ b/core/crates/primitives/src/chain.rs @@ -169,7 +169,7 @@ impl Chain { } pub fn stakeable() -> Vec { - Self::all().into_iter().filter(|chain| chain.is_stake_supported()).collect() + Self::all().into_iter().filter(|x| x.is_stake_supported()).collect() } pub fn perpetual_chains() -> Vec { @@ -186,6 +186,13 @@ mod tests { assert!(!Chain::Mayachain.is_swap_supported()); } + #[test] + fn test_all_excludes_legacy_sei() { + let chains = Chain::all(); + assert!(!chains.contains(&Chain::Sei)); + assert!(chains.contains(&Chain::SeiEvm)); + } + #[test] fn test_robinhood_swap_supported() { assert!(Chain::Robinhood.is_swap_supported()); diff --git a/ios/Packages/PrimitivesComponents/Sources/Types/ChainImage.swift b/ios/Packages/PrimitivesComponents/Sources/Types/ChainImage.swift index 33067b3e7a..47ce4426fc 100644 --- a/ios/Packages/PrimitivesComponents/Sources/Types/ChainImage.swift +++ b/ios/Packages/PrimitivesComponents/Sources/Types/ChainImage.swift @@ -48,7 +48,7 @@ public struct ChainImage: Sendable { case .gnosis: Images.Chains.gnosis case .celestia: Images.Chains.celestia case .injective: Images.Chains.injective - case .seiEvm: Images.Chains.seievm + case .seiEvm: Images.Chains.sei case .manta: Images.Chains.manta case .noble: Images.Chains.noble case .mantle: Images.Chains.mantle diff --git a/ios/Packages/Store/Sources/Migrations.swift b/ios/Packages/Store/Sources/Migrations.swift index 5ef8f612c6..37528eee5e 100644 --- a/ios/Packages/Store/Sources/Migrations.swift +++ b/ios/Packages/Store/Sources/Migrations.swift @@ -5,8 +5,6 @@ import GRDB import Primitives struct Migrations { - static let sei = "sei" - var migrator = DatabaseMigrator() init( @@ -25,60 +23,27 @@ struct Migrations { try? db.execute(sql: "DELETE FROM \(AssetRecord.databaseTableName) WHERE chain = ?", arguments: [chain]) } - static func removeChains(_ db: Database, chains: Set) throws { - for chain in chains { - let assetIdPattern = "\(chain)\\_%" - let quotedChain = "\"\(chain)\"" - let quotedAssetIdPrefix = "\"\(chain)_" - - try db.execute( - sql: "DELETE FROM \(WalletConnectionRecord.databaseTableName) WHERE instr(chains, ?) > 0", - arguments: [quotedChain], - ) - try db.execute( - sql: "DELETE FROM \(NotificationRecord.databaseTableName) WHERE instr(item, ?) > 0 OR instr(item, ?) > 0", - arguments: [quotedChain, quotedAssetIdPrefix], - ) - try db.execute( - sql: "UPDATE \(AssetRecord.databaseTableName) SET associations = '[]' WHERE instr(associations, ?) > 0 OR instr(associations, ?) > 0", - arguments: [quotedChain, quotedAssetIdPrefix], - ) - try db.execute( - sql: "DELETE FROM \(ContactAddressRecord.databaseTableName) WHERE chain = ?", - arguments: [chain], - ) - try db.execute( - sql: "DELETE FROM \(TransactionAssetAssociationRecord.databaseTableName) WHERE assetId = ? OR assetId LIKE ? ESCAPE '\\'", - arguments: [chain, assetIdPattern], - ) - try db.execute( - sql: "DELETE FROM \(TransactionRecord.databaseTableName) WHERE chain = ? OR assetId = ? OR assetId LIKE ? ESCAPE '\\' OR feeAssetId = ? OR feeAssetId LIKE ? ESCAPE '\\'", - arguments: [chain, chain, assetIdPattern, chain, assetIdPattern], - ) - try db.execute( - sql: "DELETE FROM \(PriceRecord.databaseTableName) WHERE assetId = ? OR assetId LIKE ? ESCAPE '\\'", - arguments: [chain, assetIdPattern], - ) - try db.execute( - sql: "DELETE FROM \(PriceAlertRecord.databaseTableName) WHERE assetId = ? OR assetId LIKE ? ESCAPE '\\'", - arguments: [chain, assetIdPattern], - ) - try db.execute( - sql: "DELETE FROM \(RecentActivityRecord.databaseTableName) WHERE assetId = ? OR assetId LIKE ? ESCAPE '\\' OR toAssetId = ? OR toAssetId LIKE ? ESCAPE '\\'", - arguments: [chain, assetIdPattern, chain, assetIdPattern], - ) - try db.execute( - sql: "DELETE FROM \(BannerRecord.databaseTableName) WHERE chain = ? OR assetId = ? OR assetId LIKE ? ESCAPE '\\'", - arguments: [chain, chain, assetIdPattern], - ) - try db.execute(sql: "DELETE FROM \(NFTAssetRecord.databaseTableName) WHERE chain = ?", arguments: [chain]) - try db.execute(sql: "DELETE FROM \(NFTCollectionRecord.databaseTableName) WHERE chain = ?", arguments: [chain]) - try db.execute(sql: "DELETE FROM \(NodeSelectedRecord.databaseTableName) WHERE chain = ?", arguments: [chain]) - try db.execute(sql: "DELETE FROM \(NodeRecord.databaseTableName) WHERE chain = ?", arguments: [chain]) - try db.execute(sql: "DELETE FROM \(AddressRecord.databaseTableName) WHERE chain = ?", arguments: [chain]) - try db.execute(sql: "DELETE FROM \(AccountRecord.databaseTableName) WHERE chain = ?", arguments: [chain]) - try db.execute(sql: "DELETE FROM \(AssetRecord.databaseTableName) WHERE chain = ?", arguments: [chain]) - } + static func removeChain(_ db: Database, chain: String) throws { + let quotedChain = "\"\(chain)\"" + let quotedAssetIdPrefix = "\"\(chain)_" + + try db.execute( + sql: "DELETE FROM \(WalletConnectionRecord.databaseTableName) WHERE instr(chains, ?) > 0", + arguments: [quotedChain], + ) + try db.execute( + sql: "DELETE FROM \(NotificationRecord.databaseTableName) WHERE instr(item, ?) > 0 OR instr(item, ?) > 0", + arguments: [quotedChain, quotedAssetIdPrefix], + ) + try db.execute( + sql: "UPDATE \(AssetRecord.databaseTableName) SET associations = '[]' WHERE instr(associations, ?) > 0 OR instr(associations, ?) > 0", + arguments: [quotedChain, quotedAssetIdPrefix], + ) + try db.execute( + sql: "DELETE FROM \(ContactAddressRecord.databaseTableName) WHERE chain = ?", + arguments: [chain], + ) + try db.execute(sql: "DELETE FROM \(AssetRecord.databaseTableName) WHERE chain = ?", arguments: [chain]) } private static func clearTables(_ db: Database, tableNames: [String]) throws { @@ -571,8 +536,8 @@ struct Migrations { } } - migrator.registerMigration("Remove Sei chain") { db in - try Self.removeChains(db, chains: [Self.sei]) + migrator.registerMigration("Remove Sei chain", foreignKeyChecks: .immediate) { db in + try Self.removeChain(db, chain: "sei") } try migrator.migrate(dbQueue) diff --git a/ios/Packages/Store/Tests/StoreTests/MigrationsTests.swift b/ios/Packages/Store/Tests/StoreTests/MigrationsTests.swift index 8081dc3784..2906e64db8 100644 --- a/ios/Packages/Store/Tests/StoreTests/MigrationsTests.swift +++ b/ios/Packages/Store/Tests/StoreTests/MigrationsTests.swift @@ -63,30 +63,41 @@ struct MigrationsTests { } @Test - func removeChainsRemovesSeiReferences() throws { + func removeChainRemovesSeiReferencesAndPreservesSeiEvm() throws { let store = DB.mock() - let chain = Migrations.sei try store.dbQueue.write { db in - try seedChain(db, chain: chain) - - try Migrations.removeChains(db, chains: [chain]) - - #expect( - try String.fetchOne( - db, - sql: """ - SELECT 'assets=' || (SELECT COUNT(*) FROM assets WHERE chain = '\(chain)') || - ',accounts=' || (SELECT COUNT(*) FROM wallets_accounts WHERE chain = '\(chain)') || - ',connections=' || (SELECT COUNT(*) FROM wallets_connections WHERE instr(chains, '"\(chain)"') > 0) || - ',notifications=' || (SELECT COUNT(*) FROM notifications WHERE instr(item, '"\(chain)"') > 0) - """, - ) == "assets=0,accounts=0,connections=0,notifications=0", + try seedChain(db, chain: "sei", associations: "[]") + try seedChain(db, chain: "seievm", associations: "[{\"assetId\":\"sei\"}]") + try db.execute( + sql: "INSERT INTO contacts (id, name, createdAt, updatedAt) VALUES ('contact', 'Contact', 0, 0)", ) + try db.execute( + sql: "INSERT INTO contacts_addresses (id, contactId, address, chain) VALUES ('sei-contact', 'contact', 'sei-address', 'sei'), ('seievm-contact', 'contact', 'seievm-address', 'seievm')", + ) + } + + var migrator = DatabaseMigrator() + migrator.registerMigration("Remove chain", foreignKeyChecks: .immediate) { db in + try Migrations.removeChain(db, chain: "sei") + } + try migrator.migrate(store.dbQueue) + + try store.dbQueue.read { (db: Database) throws in + #expect(try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM assets WHERE chain = 'sei'") == 0) + #expect(try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM assets WHERE chain = 'seievm'") == 1) + #expect(try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM wallets_accounts WHERE chain = 'sei'") == 0) + #expect(try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM wallets_accounts WHERE chain = 'seievm'") == 1) + #expect(try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM wallets") == 2) + #expect(try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM wallets_connections") == 1) + #expect(try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM notifications") == 1) + #expect(try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM contacts_addresses") == 1) + #expect(try String.fetchOne(db, sql: "SELECT associations FROM assets WHERE id = 'seievm'") == "[]") + #expect(try Row.fetchAll(db, sql: "PRAGMA foreign_key_check").isEmpty) } } - private func seedChain(_ db: Database, chain: String) throws { + private func seedChain(_ db: Database, chain: String, associations: String) throws { let walletId = "wallet-\(chain)" try db.execute( sql: "INSERT INTO wallets (id, name, type, `index`, `order`, isPinned) VALUES (?, ?, 'single', 0, 0, 0)", @@ -95,7 +106,7 @@ struct MigrationsTests { try db.execute( sql: "INSERT INTO assets (id, chain, name, symbol, decimals, type, isEnabled, isBuyable, isSellable, isSwappable, isStakeable, isEarnable, rank, hasImage, associations) " + "VALUES (?, ?, ?, ?, 6, 'native', 1, 0, 0, 0, 0, 0, 1, 0, ?)", - arguments: [chain, chain, chain, chain, "[]"], + arguments: [chain, chain, chain, chain, associations], ) try db.execute( sql: "INSERT INTO wallets_accounts (walletId, chain, address, `index`, derivationPath) VALUES (?, ?, ?, 0, '')", diff --git a/ios/Packages/Style/Sources/Images.swift b/ios/Packages/Style/Sources/Images.swift index af640790e9..a1c004294e 100644 --- a/ios/Packages/Style/Sources/Images.swift +++ b/ios/Packages/Style/Sources/Images.swift @@ -41,7 +41,7 @@ public enum Images { public static let optimism = Image(.optimism) public static let osmosis = Image(.osmosis) public static let polygon = Image(.polygon) - public static let seievm = Image(.seievm) + public static let sei = Image(.sei) public static let smartchain = Image(.smartchain) public static let solana = Image(.solana) public static let sui = Image(.sui) @@ -260,7 +260,7 @@ public enum Images { (Images.Chains.optimism, "Optimism"), (Images.Chains.osmosis, "Osmosis"), (Images.Chains.polygon, "Polygon"), - (Images.Chains.seievm, "Sei EVM"), + (Images.Chains.sei, "Sei"), (Images.Chains.smartchain, "Smart Chain"), (Images.Chains.solana, "Solana"), (Images.Chains.sui, "Sui"), diff --git a/ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/seievm.imageset/Contents.json b/ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/sei.imageset/Contents.json similarity index 85% rename from ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/seievm.imageset/Contents.json rename to ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/sei.imageset/Contents.json index 78271a15b1..67883e8a19 100644 --- a/ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/seievm.imageset/Contents.json +++ b/ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/sei.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "seievm.svg", + "filename" : "sei.svg", "idiom" : "universal" } ], diff --git a/ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/seievm.imageset/seievm.svg b/ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/sei.imageset/sei.svg similarity index 100% rename from ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/seievm.imageset/seievm.svg rename to ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/sei.imageset/sei.svg From 98e4fd0f05809bdd1a64da94548152ac081055a2 Mon Sep 17 00:00:00 2001 From: gemcoder21 <104884878+gemcoder21@users.noreply.github.com> Date: Sat, 15 Aug 2026 23:16:55 +0000 Subject: [PATCH 4/4] =?UTF-8?q?Rename=20sei=20=E2=86=92=20seievm;=20add=20?= =?UTF-8?q?DB=20chain=20removal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename the 'sei' chain assets and references to 'seievm', update icon mappings, and centralize chain removal logic. Key changes: - Added SupportSQLiteDatabase.removeChain helper to delete all DB refs for a chain using GLOB JSON-safe patterns. - Migration_85_86 now calls db.removeChain("sei"). - Updated Android IconUrlGeneration and tests to use seievm asset name. - Renamed Android/iOS chain icon assets and updated Images/ChainImage mapping. - Adjusted migration tests to assert new wallets_connections/in_app_notifications ids. This ensures consistent handling of the Sei EVM chain and safer JSON matching during removals. --- .../chains/icons/{sei.svg => seievm.svg} | 0 .../service/store/Migration_85_86Test.kt | 4 +- .../service/store/database/di/ChainRemoval.kt | 78 +++++++++++++++++++ .../store/database/di/Migration_85_86.kt | 77 +----------------- .../domains/asset/IconUrlGeneration.kt | 8 +- .../com/gemwallet/android/ext/ChainExtTest.kt | 1 - .../Sources/Types/ChainImage.swift | 2 +- ios/Packages/Store/Sources/Migrations.swift | 15 ++-- ios/Packages/Style/Sources/Images.swift | 4 +- .../Contents.json | 2 +- .../sei.svg => seievm.imageset/seievm.svg} | 0 11 files changed, 92 insertions(+), 99 deletions(-) rename android/app/src/main/assets/chains/icons/{sei.svg => seievm.svg} (100%) create mode 100644 android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/ChainRemoval.kt rename ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/{sei.imageset => seievm.imageset}/Contents.json (85%) rename ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/{sei.imageset/sei.svg => seievm.imageset/seievm.svg} (100%) diff --git a/android/app/src/main/assets/chains/icons/sei.svg b/android/app/src/main/assets/chains/icons/seievm.svg similarity index 100% rename from android/app/src/main/assets/chains/icons/sei.svg rename to android/app/src/main/assets/chains/icons/seievm.svg diff --git a/android/data/services/store/src/androidTest/kotlin/com/gemwallet/android/service/store/Migration_85_86Test.kt b/android/data/services/store/src/androidTest/kotlin/com/gemwallet/android/service/store/Migration_85_86Test.kt index aa9c2084e0..c6d0c2731a 100644 --- a/android/data/services/store/src/androidTest/kotlin/com/gemwallet/android/service/store/Migration_85_86Test.kt +++ b/android/data/services/store/src/androidTest/kotlin/com/gemwallet/android/service/store/Migration_85_86Test.kt @@ -46,10 +46,10 @@ class Migration_85_86Test { assertEquals(0, db.count("accounts", "chain = 'sei'")) assertEquals(1, db.count("accounts", "chain = 'seievm'")) assertEquals(1, db.count("wallets")) - assertEquals(0, db.count("wallets_connections", "instr(chains, '\"sei\"') > 0")) assertEquals(1, db.count("wallets_connections")) - assertEquals(0, db.count("in_app_notifications", "instr(item, '\"sei\"') > 0")) + assertEquals("seievm-connection", db.string("SELECT id FROM wallets_connections")) assertEquals(1, db.count("in_app_notifications")) + assertEquals("seievm-notification", db.string("SELECT id FROM in_app_notifications")) assertEquals(0, db.count("contacts_addresses", "chain = 'sei'")) assertEquals(1, db.count("contacts_addresses", "chain = 'seievm'")) assertEquals(1, db.count("transactions")) diff --git a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/ChainRemoval.kt b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/ChainRemoval.kt new file mode 100644 index 0000000000..f6a15e482b --- /dev/null +++ b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/ChainRemoval.kt @@ -0,0 +1,78 @@ +package com.gemwallet.android.data.service.store.database.di + +import androidx.sqlite.db.SupportSQLiteDatabase + +internal fun SupportSQLiteDatabase.removeChain(chain: String) { + val assetIdPattern = "${chain}\\_%" + fun deleteAssetReferences(table: String, column: String) { + execSQL( + "DELETE FROM $table WHERE $column = ? OR $column LIKE ? ESCAPE '\\'", + arrayOf(chain, assetIdPattern), + ) + } + + execSQL("DELETE FROM wallets_connections WHERE instr(chains, '\"' || ? || '\"') > 0", arrayOf(chain)) + execSQL( + "DELETE FROM in_app_notifications WHERE " + + "instr(item, '\"' || ? || '\"') > 0 OR instr(item, '\"' || ? || '_') > 0", + arrayOf(chain, chain), + ) + execSQL( + "UPDATE asset SET associations = '[]' WHERE " + + "instr(associations, '\"' || ? || '\"') > 0 OR instr(associations, '\"' || ? || '_') > 0", + arrayOf(chain, chain), + ) + execSQL("DELETE FROM contacts_addresses WHERE chain = ?", arrayOf(chain)) + execSQL( + "DELETE FROM tx_swap_metadata WHERE " + + "from_asset_id = ? OR from_asset_id LIKE ? ESCAPE '\\' OR " + + "to_asset_id = ? OR to_asset_id LIKE ? ESCAPE '\\' OR " + + "tx_id IN (SELECT id FROM transactions WHERE " + + "assetId = ? OR assetId LIKE ? ESCAPE '\\' OR " + + "feeAssetId = ? OR feeAssetId LIKE ? ESCAPE '\\')", + arrayOf(chain, assetIdPattern, chain, assetIdPattern, chain, assetIdPattern, chain, assetIdPattern), + ) + execSQL( + "DELETE FROM transactions WHERE assetId = ? OR assetId LIKE ? ESCAPE '\\' OR " + + "feeAssetId = ? OR feeAssetId LIKE ? ESCAPE '\\'", + arrayOf(chain, assetIdPattern, chain, assetIdPattern), + ) + execSQL( + "DELETE FROM search WHERE assetId = ? OR assetId LIKE ? ESCAPE '\\' OR " + + "perpetualId IN (SELECT id FROM perpetuals WHERE assetId = ? OR assetId LIKE ? ESCAPE '\\')", + arrayOf(chain, assetIdPattern, chain, assetIdPattern), + ) + execSQL( + "DELETE FROM perpetuals_positions WHERE assetId = ? OR assetId LIKE ? ESCAPE '\\' OR " + + "perpetualId IN (SELECT id FROM perpetuals WHERE assetId = ? OR assetId LIKE ? ESCAPE '\\')", + arrayOf(chain, assetIdPattern, chain, assetIdPattern), + ) + deleteAssetReferences("perpetuals", "assetId") + deleteAssetReferences("stake_delegations", "assetId") + deleteAssetReferences("stake_validators", "assetId") + execSQL( + "DELETE FROM nft_assets_associations WHERE asset_id IN (SELECT id FROM nft_assets WHERE chain = ?)", + arrayOf(chain), + ) + execSQL("DELETE FROM nft_assets WHERE chain = ?", arrayOf(chain)) + execSQL("DELETE FROM nft_collections WHERE chain = ?", arrayOf(chain)) + deleteAssetReferences("balances", "asset_id") + deleteAssetReferences("asset_links", "asset_id") + deleteAssetReferences("asset_market", "asset_id") + deleteAssetReferences("fiat_transactions", "assetId") + deleteAssetReferences("prices", "asset_id") + deleteAssetReferences("price_alerts", "assetId") + execSQL( + "DELETE FROM recent_assets WHERE asset_id = ? OR asset_id LIKE ? ESCAPE '\\' OR " + + "to_asset_id = ? OR to_asset_id LIKE ? ESCAPE '\\'", + arrayOf(chain, assetIdPattern, chain, assetIdPattern), + ) + execSQL( + "DELETE FROM banners WHERE chain = ? OR asset_id = ? OR asset_id LIKE ? ESCAPE '\\'", + arrayOf(chain, chain, assetIdPattern), + ) + execSQL("DELETE FROM nodes WHERE chain = ?", arrayOf(chain)) + execSQL("DELETE FROM addresses WHERE chain = ?", arrayOf(chain)) + execSQL("DELETE FROM accounts WHERE chain = ?", arrayOf(chain)) + execSQL("DELETE FROM asset WHERE chain = ?", arrayOf(chain)) +} diff --git a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_85_86.kt b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_85_86.kt index 8c95f76909..1b32a416a8 100644 --- a/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_85_86.kt +++ b/android/data/services/store/src/main/kotlin/com/gemwallet/android/data/service/store/database/di/Migration_85_86.kt @@ -5,81 +5,6 @@ import androidx.sqlite.db.SupportSQLiteDatabase object Migration_85_86 : Migration(85, 86) { override fun migrate(db: SupportSQLiteDatabase) { - removeChain(db, "sei") + db.removeChain("sei") } } - -private fun removeChain(db: SupportSQLiteDatabase, chain: String) { - val assetIdPattern = "${chain}\\_%" - val quotedChain = "\"$chain\"" - val quotedAssetIdPrefix = "\"${chain}_" - fun deleteAssetReferences(table: String, column: String) { - db.execSQL( - "DELETE FROM $table WHERE $column = ? OR $column LIKE ? ESCAPE '\\'", - arrayOf(chain, assetIdPattern), - ) - } - - db.execSQL("DELETE FROM wallets_connections WHERE instr(chains, ?) > 0", arrayOf(quotedChain)) - db.execSQL( - "DELETE FROM in_app_notifications WHERE instr(item, ?) > 0 OR instr(item, ?) > 0", - arrayOf(quotedChain, quotedAssetIdPrefix), - ) - db.execSQL( - "UPDATE asset SET associations = '[]' WHERE instr(associations, ?) > 0 OR instr(associations, ?) > 0", - arrayOf(quotedChain, quotedAssetIdPrefix), - ) - db.execSQL("DELETE FROM contacts_addresses WHERE chain = ?", arrayOf(chain)) - db.execSQL( - "DELETE FROM tx_swap_metadata WHERE " + - "from_asset_id = ? OR from_asset_id LIKE ? ESCAPE '\\' OR " + - "to_asset_id = ? OR to_asset_id LIKE ? ESCAPE '\\' OR " + - "tx_id IN (SELECT id FROM transactions WHERE " + - "assetId = ? OR assetId LIKE ? ESCAPE '\\' OR " + - "feeAssetId = ? OR feeAssetId LIKE ? ESCAPE '\\')", - arrayOf(chain, assetIdPattern, chain, assetIdPattern, chain, assetIdPattern, chain, assetIdPattern), - ) - db.execSQL( - "DELETE FROM transactions WHERE assetId = ? OR assetId LIKE ? ESCAPE '\\' OR " + - "feeAssetId = ? OR feeAssetId LIKE ? ESCAPE '\\'", - arrayOf(chain, assetIdPattern, chain, assetIdPattern), - ) - db.execSQL( - "DELETE FROM search WHERE assetId = ? OR assetId LIKE ? ESCAPE '\\' OR " + - "perpetualId IN (SELECT id FROM perpetuals WHERE assetId = ? OR assetId LIKE ? ESCAPE '\\')", - arrayOf(chain, assetIdPattern, chain, assetIdPattern), - ) - db.execSQL( - "DELETE FROM perpetuals_positions WHERE assetId = ? OR assetId LIKE ? ESCAPE '\\' OR " + - "perpetualId IN (SELECT id FROM perpetuals WHERE assetId = ? OR assetId LIKE ? ESCAPE '\\')", - arrayOf(chain, assetIdPattern, chain, assetIdPattern), - ) - deleteAssetReferences("perpetuals", "assetId") - deleteAssetReferences("stake_delegations", "assetId") - deleteAssetReferences("stake_validators", "assetId") - db.execSQL( - "DELETE FROM nft_assets_associations WHERE asset_id IN (SELECT id FROM nft_assets WHERE chain = ?)", - arrayOf(chain), - ) - db.execSQL("DELETE FROM nft_assets WHERE chain = ?", arrayOf(chain)) - db.execSQL("DELETE FROM nft_collections WHERE chain = ?", arrayOf(chain)) - deleteAssetReferences("balances", "asset_id") - deleteAssetReferences("asset_links", "asset_id") - deleteAssetReferences("asset_market", "asset_id") - deleteAssetReferences("fiat_transactions", "assetId") - deleteAssetReferences("prices", "asset_id") - deleteAssetReferences("price_alerts", "assetId") - db.execSQL( - "DELETE FROM recent_assets WHERE asset_id = ? OR asset_id LIKE ? ESCAPE '\\' OR " + - "to_asset_id = ? OR to_asset_id LIKE ? ESCAPE '\\'", - arrayOf(chain, assetIdPattern, chain, assetIdPattern), - ) - db.execSQL( - "DELETE FROM banners WHERE chain = ? OR asset_id = ? OR asset_id LIKE ? ESCAPE '\\'", - arrayOf(chain, chain, assetIdPattern), - ) - db.execSQL("DELETE FROM nodes WHERE chain = ?", arrayOf(chain)) - db.execSQL("DELETE FROM addresses WHERE chain = ?", arrayOf(chain)) - db.execSQL("DELETE FROM accounts WHERE chain = ?", arrayOf(chain)) - db.execSQL("DELETE FROM asset WHERE chain = ?", arrayOf(chain)) -} diff --git a/android/gemcore/src/main/kotlin/com/gemwallet/android/domains/asset/IconUrlGeneration.kt b/android/gemcore/src/main/kotlin/com/gemwallet/android/domains/asset/IconUrlGeneration.kt index 5370f147b1..f67322ee0c 100644 --- a/android/gemcore/src/main/kotlin/com/gemwallet/android/domains/asset/IconUrlGeneration.kt +++ b/android/gemcore/src/main/kotlin/com/gemwallet/android/domains/asset/IconUrlGeneration.kt @@ -19,13 +19,7 @@ import uniffi.gemstone.SwapperProvider //fun Int.getDrawableUri() = "android.resource://com.gemwallet.android/drawable/$this" -fun Chain.getIconUrl(): String { - val icon = when (this) { - Chain.SeiEvm -> "sei" - else -> string - } - return "file:///android_asset/chains/icons/${icon}.svg" -} +fun Chain.getIconUrl(): String = "file:///android_asset/chains/icons/${string}.svg" fun AssetId.getIconUrl(): String = when { tokenId.isNullOrEmpty() -> when (chain) { diff --git a/android/gemcore/src/test/kotlin/com/gemwallet/android/ext/ChainExtTest.kt b/android/gemcore/src/test/kotlin/com/gemwallet/android/ext/ChainExtTest.kt index d78ecf15fb..92801afd71 100644 --- a/android/gemcore/src/test/kotlin/com/gemwallet/android/ext/ChainExtTest.kt +++ b/android/gemcore/src/test/kotlin/com/gemwallet/android/ext/ChainExtTest.kt @@ -16,7 +16,6 @@ class ChainExtTest { assertEquals(AssetType.ERC20, Chain.SeiEvm.assetType()) assertEquals(EVMChain.SeiEvm, Chain.SeiEvm.toEVM()) assertEquals(ChainType.Ethereum, Chain.SeiEvm.toChainType()) - assertEquals("file:///android_asset/chains/icons/sei.svg", Chain.SeiEvm.getIconUrl()) } @Test diff --git a/ios/Packages/PrimitivesComponents/Sources/Types/ChainImage.swift b/ios/Packages/PrimitivesComponents/Sources/Types/ChainImage.swift index 47ce4426fc..33067b3e7a 100644 --- a/ios/Packages/PrimitivesComponents/Sources/Types/ChainImage.swift +++ b/ios/Packages/PrimitivesComponents/Sources/Types/ChainImage.swift @@ -48,7 +48,7 @@ public struct ChainImage: Sendable { case .gnosis: Images.Chains.gnosis case .celestia: Images.Chains.celestia case .injective: Images.Chains.injective - case .seiEvm: Images.Chains.sei + case .seiEvm: Images.Chains.seievm case .manta: Images.Chains.manta case .noble: Images.Chains.noble case .mantle: Images.Chains.mantle diff --git a/ios/Packages/Store/Sources/Migrations.swift b/ios/Packages/Store/Sources/Migrations.swift index 37528eee5e..c6c690f791 100644 --- a/ios/Packages/Store/Sources/Migrations.swift +++ b/ios/Packages/Store/Sources/Migrations.swift @@ -24,20 +24,17 @@ struct Migrations { } static func removeChain(_ db: Database, chain: String) throws { - let quotedChain = "\"\(chain)\"" - let quotedAssetIdPrefix = "\"\(chain)_" - try db.execute( - sql: "DELETE FROM \(WalletConnectionRecord.databaseTableName) WHERE instr(chains, ?) > 0", - arguments: [quotedChain], + sql: "DELETE FROM \(WalletConnectionRecord.databaseTableName) WHERE instr(chains, '\"' || ? || '\"') > 0", + arguments: [chain], ) try db.execute( - sql: "DELETE FROM \(NotificationRecord.databaseTableName) WHERE instr(item, ?) > 0 OR instr(item, ?) > 0", - arguments: [quotedChain, quotedAssetIdPrefix], + sql: "DELETE FROM \(NotificationRecord.databaseTableName) WHERE instr(item, '\"' || ? || '\"') > 0 OR instr(item, '\"' || ? || '_') > 0", + arguments: [chain, chain], ) try db.execute( - sql: "UPDATE \(AssetRecord.databaseTableName) SET associations = '[]' WHERE instr(associations, ?) > 0 OR instr(associations, ?) > 0", - arguments: [quotedChain, quotedAssetIdPrefix], + sql: "UPDATE \(AssetRecord.databaseTableName) SET associations = '[]' WHERE instr(associations, '\"' || ? || '\"') > 0 OR instr(associations, '\"' || ? || '_') > 0", + arguments: [chain, chain], ) try db.execute( sql: "DELETE FROM \(ContactAddressRecord.databaseTableName) WHERE chain = ?", diff --git a/ios/Packages/Style/Sources/Images.swift b/ios/Packages/Style/Sources/Images.swift index a1c004294e..af640790e9 100644 --- a/ios/Packages/Style/Sources/Images.swift +++ b/ios/Packages/Style/Sources/Images.swift @@ -41,7 +41,7 @@ public enum Images { public static let optimism = Image(.optimism) public static let osmosis = Image(.osmosis) public static let polygon = Image(.polygon) - public static let sei = Image(.sei) + public static let seievm = Image(.seievm) public static let smartchain = Image(.smartchain) public static let solana = Image(.solana) public static let sui = Image(.sui) @@ -260,7 +260,7 @@ public enum Images { (Images.Chains.optimism, "Optimism"), (Images.Chains.osmosis, "Osmosis"), (Images.Chains.polygon, "Polygon"), - (Images.Chains.sei, "Sei"), + (Images.Chains.seievm, "Sei EVM"), (Images.Chains.smartchain, "Smart Chain"), (Images.Chains.solana, "Solana"), (Images.Chains.sui, "Sui"), diff --git a/ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/sei.imageset/Contents.json b/ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/seievm.imageset/Contents.json similarity index 85% rename from ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/sei.imageset/Contents.json rename to ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/seievm.imageset/Contents.json index 67883e8a19..78271a15b1 100644 --- a/ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/sei.imageset/Contents.json +++ b/ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/seievm.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "sei.svg", + "filename" : "seievm.svg", "idiom" : "universal" } ], diff --git a/ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/sei.imageset/sei.svg b/ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/seievm.imageset/seievm.svg similarity index 100% rename from ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/sei.imageset/sei.svg rename to ios/Packages/Style/Sources/Resources/Assets.xcassets/chains/seievm.imageset/seievm.svg