Initial commit: Pezkuwi Wallet Android

Complete rebrand of Nova Wallet for Pezkuwichain ecosystem.

## Features
- Full Pezkuwichain support (HEZ & PEZ tokens)
- Polkadot ecosystem compatibility
- Staking, Governance, DeFi, NFTs
- XCM cross-chain transfers
- Hardware wallet support (Ledger, Polkadot Vault)
- WalletConnect v2
- Push notifications

## Languages
- English, Turkish, Kurmanci (Kurdish), Spanish, French, German, Russian, Japanese, Chinese, Korean, Portuguese, Vietnamese

Based on Nova Wallet by Novasama Technologies GmbH
© Dijital Kurdistan Tech Institute 2026
This commit is contained in:
2026-01-23 01:31:12 +03:00
commit 31c8c5995f
7621 changed files with 425838 additions and 0 deletions
@@ -0,0 +1,106 @@
package io.novafoundation.nova.core_db.dao
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.novafoundation.nova.core_db.AppDatabase
import io.novafoundation.nova.core_db.model.AssetLocal
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class AssetsDaoTest : DaoTest<AssetDao>(AppDatabase::assetDao) {
private val chainDao by dao<ChainDao>()
private val metaAccountDao by dao<MetaAccountDao>()
private val currencyDao by dao<CurrencyDao>()
private val assetDao by dao<AssetDao>()
private var metaId: Long = 0
private val chainId = "0"
private val testChain = createTestChain(chainId)
private val asset = testChain.assets.first()
private val assetId = asset.id
@Before
fun setupDb() = runBlocking {
metaId = metaAccountDao.insertMetaAccount(testMetaAccount())
chainDao.addChain(testChain)
}
@Test
fun shouldDeleteAssetAfterChainIsDeleted() = runBlocking {
dao.insertAsset(AssetLocal.createEmpty(assetId = assetId, chainId = chainId, metaId))
chainDao.removeChain(testChain)
val assets = dao.getSupportedAssets(metaId)
assert(assets.isEmpty())
}
@Test
fun testRetrievingAssetsByMetaId() = runBlocking {
currencyDao.insert(createCurrency(selected = true))
val assetWithToken = dao.getAssetWithToken(metaId, chainId, assetId)
assert(assetWithToken != null)
}
@Test
fun testRetrievingAssetsByMetaIdWithoutCurrency() = runBlocking {
currencyDao.insert(createCurrency(selected = false))
val assetWithToken = dao.getAssetWithToken(metaId, chainId, assetId)
assert(assetWithToken == null)
}
@Test
fun testRetrievingSyncedAssets() = runBlocking {
assetDao.insertAsset(AssetLocal.createEmpty(assetId, chainId, metaId))
currencyDao.insert(createCurrency(selected = true))
val assetWithToken = dao.getSyncedAssets(metaId)
assert(assetWithToken.isNotEmpty())
}
@Test
fun testRetrievingSyncedAssetsWithoutCurrency() = runBlocking {
assetDao.insertAsset(AssetLocal.createEmpty(assetId, chainId, metaId))
currencyDao.insert(createCurrency(selected = false))
val assetsWithTokens = dao.getSyncedAssets(metaId)
assert(assetsWithTokens.isEmpty())
}
@Test
fun testRetrievingSyncedAssetsWithoutAssetBalance() = runBlocking {
currencyDao.insert(createCurrency(selected = false))
val assetsWithTokens = dao.getSyncedAssets(metaId)
assert(assetsWithTokens.isEmpty())
}
@Test
fun testRetrievingSupportedAssets() = runBlocking {
currencyDao.insert(createCurrency(selected = true))
val assetsWithTokens = dao.getSupportedAssets(metaId)
assert(assetsWithTokens.isNotEmpty())
}
@Test
fun testRetrievingSupportedAssetsWithoutCurrency() = runBlocking {
currencyDao.insert(createCurrency(selected = false))
val assetsWithTokens = dao.getSupportedAssets(metaId)
assert(assetsWithTokens.isEmpty())
}
}
@@ -0,0 +1,164 @@
package io.novafoundation.nova.core_db.dao
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.novafoundation.nova.common.utils.CollectionDiffer
import io.novafoundation.nova.core_db.AppDatabase
import io.novafoundation.nova.core_db.model.chain.JoinedChainInfo
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ChainDaoTest : DaoTest<ChainDao>(AppDatabase::chainDao) {
@Test
fun shouldInsertWholeChain() = runBlocking {
val chainInfo = createTestChain("0x00")
dao.addChain(chainInfo)
val chainsFromDb = dao.getJoinChainInfo()
assertEquals(1, chainsFromDb.size)
val chainFromDb = chainsFromDb.first()
assertEquals(chainInfo.assets.size, chainFromDb.assets.size)
assertEquals(chainInfo.nodes.size, chainFromDb.nodes.size)
}
@Test
fun shouldDeleteChainWithCascade() = runBlocking {
val chainInfo = createTestChain("0x00")
dao.addChain(chainInfo)
dao.removeChain(chainInfo)
val assetsCursor = db.query("SELECT * FROM chain_assets", emptyArray())
assertEquals(0, assetsCursor.count)
val nodesCursor = db.query("SELECT * FROM chain_nodes", emptyArray())
assertEquals(0, nodesCursor.count)
}
@Test
fun shouldNotDeleteRuntimeCacheEntryAfterChainUpdate() = runBlocking {
val chainInfo = createTestChain("0x00")
dao.addChain(chainInfo)
dao.updateRemoteRuntimeVersionIfChainExists(chainInfo.chain.id, runtimeVersion = 1, transactionVersion = 1)
dao.updateChain(chainInfo)
val runtimeEntry = dao.runtimeInfo(chainInfo.chain.id)
assertNotNull(runtimeEntry)
}
@Test
fun shouldDeleteRemovedNestedFields() = runBlocking {
val chainInfo = createTestChain("0x00", nodesCount = 3, assetsCount = 3)
dao.addChain(chainInfo)
dao.applyDiff(
chainDiff = updatedDiff(chainInfo.chain),
assetsDiff = CollectionDiffer.Diff(
added = emptyList(),
updated = emptyList(),
removed = chainInfo.assets.takeLast(1),
all = chainInfo.assets
),
nodesDiff = CollectionDiffer.Diff(
added = emptyList(),
updated = emptyList(),
removed = chainInfo.nodes.takeLast(1),
all = chainInfo.nodes
),
explorersDiff = emptyDiff(),
externalApisDiff = emptyDiff(),
nodeSelectionPreferencesDiff = emptyDiff()
)
val chainFromDb2 = dao.getJoinChainInfo().first()
assertEquals(2, chainFromDb2.nodes.size)
assertEquals(2, chainFromDb2.assets.size)
}
@Test
fun shouldUpdate() = runBlocking {
val toBeRemoved = listOf(
createTestChain("to be removed 1"),
createTestChain("to be removed 2"),
)
val stayTheSame = listOf(
createTestChain("stay the same")
)
val chainsInitial = listOf(createTestChain("to be changed")) + stayTheSame + toBeRemoved
dao.addChains(chainsInitial)
val added = listOf(createTestChain("to be added"))
val updated = listOf(createTestChain("to be changed", "new name"))
val expectedResult = stayTheSame + added + updated
dao.applyDiff(
chainDiff = CollectionDiffer.Diff(
added = added.map(JoinedChainInfo::chain),
updated = updated.map(JoinedChainInfo::chain),
removed = toBeRemoved.map(JoinedChainInfo::chain),
all = emptyList()
),
assetsDiff = emptyDiff(),
nodesDiff = emptyDiff(),
explorersDiff = emptyDiff(),
externalApisDiff = emptyDiff(),
nodeSelectionPreferencesDiff = emptyDiff()
)
val chainsFromDb = dao.getJoinChainInfo()
assertEquals(expectedResult.size, chainsFromDb.size)
expectedResult.forEach { expected ->
val tryFind = chainsFromDb.firstOrNull { actual -> expected.chain.id == actual.chain.id && expected.chain.name == actual.chain.name }
assertNotNull("Did not find ${expected.chain.id} in result set", tryFind)
}
}
@Test
fun shouldUpdateRuntimeVersions() {
runBlocking {
val chainId = "0x00"
dao.addChain(createTestChain(chainId))
dao.updateRemoteRuntimeVersionIfChainExists(chainId, 1, transactionVersion = 1)
checkRuntimeVersions(remote = 1, synced = 0)
dao.updateSyncedRuntimeVersion(chainId, 1, localMigratorVersion = 1)
checkRuntimeVersions(remote = 1, synced = 1)
dao.updateRemoteRuntimeVersionIfChainExists(chainId, 2, transactionVersion = 1)
checkRuntimeVersions(remote = 2, synced = 1)
}
}
private suspend fun checkRuntimeVersions(remote: Int, synced: Int) {
val runtimeInfo = dao.runtimeInfo("0x00")
requireNotNull(runtimeInfo)
assertEquals(runtimeInfo.remoteVersion, remote)
assertEquals(runtimeInfo.syncedVersion, synced)
}
}
@@ -0,0 +1,36 @@
package io.novafoundation.nova.core_db.dao
import android.content.Context
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import io.novafoundation.nova.core_db.AppDatabase
import org.junit.After
import org.junit.Before
import java.io.IOException
abstract class DaoTest<D : Any>(private val daoFetcher: (AppDatabase) -> D) {
protected lateinit var dao: D
protected lateinit var db: AppDatabase
@Before
fun createDb() {
val context = ApplicationProvider.getApplicationContext<Context>()
db = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java)
.build()
dao = daoFetcher(db)
}
@After
@Throws(IOException::class)
fun closeDb() {
db.close()
}
protected inline fun <reified T> dao(): Lazy<T> = lazy {
val method = db.javaClass.declaredMethods.first { it.returnType == T::class.java }
method.invoke(db) as T
}
}
@@ -0,0 +1,207 @@
package io.novafoundation.nova.core_db.dao
import io.novafoundation.nova.common.utils.CollectionDiffer
import io.novafoundation.nova.core.model.CryptoType
import io.novafoundation.nova.core_db.model.CurrencyLocal
import io.novafoundation.nova.core_db.model.chain.AssetSourceLocal
import io.novafoundation.nova.core_db.model.chain.ChainAssetLocal
import io.novafoundation.nova.core_db.model.chain.ChainExplorerLocal
import io.novafoundation.nova.core_db.model.chain.ChainExternalApiLocal
import io.novafoundation.nova.core_db.model.chain.ChainLocal
import io.novafoundation.nova.core_db.model.chain.ChainNodeLocal
import io.novafoundation.nova.core_db.model.chain.JoinedChainInfo
import io.novafoundation.nova.core_db.model.chain.NodeSelectionPreferencesLocal
import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal
import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal
fun createTestChain(
id: String,
name: String = id,
nodesCount: Int = 3,
assetsCount: Int = 2,
): JoinedChainInfo {
val chain = chainOf(id, name)
val nodes = with(chain) {
(1..nodesCount).map {
nodeOf("link${it}")
}
}
val assets = with(chain) {
(1..assetsCount).map {
assetOf(assetId = it, symbol = it.toString())
}
}
val explorers = emptyList<ChainExplorerLocal>()
val externalApis = emptyList<ChainExternalApiLocal>()
return JoinedChainInfo(
chain,
NodeSelectionPreferencesLocal(chain.id, autoBalanceEnabled = true, selectedNodeUrl = null),
nodes,
assets,
explorers,
externalApis
)
}
fun chainOf(
id: String,
name: String = id,
) = ChainLocal(
id = id,
parentId = null,
name = name,
icon = "Test",
types = null,
prefix = 0,
legacyPrefix = null,
isTestNet = false,
isEthereumBased = false,
hasCrowdloans = false,
additional = "",
governance = "governance",
connectionState = ChainLocal.ConnectionStateLocal.FULL_SYNC,
pushSupport = true,
supportProxy = false,
swap = "",
hasSubstrateRuntime = true,
nodeSelectionStrategy = ChainLocal.AutoBalanceStrategyLocal.ROUND_ROBIN,
source = ChainLocal.Source.CUSTOM,
customFee = "",
multisigSupport = true
)
fun ChainLocal.nodeOf(
link: String,
) = ChainNodeLocal(
name = "Test",
url = link,
chainId = id,
orderId = 0,
source = ChainNodeLocal.Source.CUSTOM,
)
fun ChainLocal.assetOf(
assetId: Int,
symbol: String,
) = ChainAssetLocal(
name = "Test",
chainId = id,
symbol = symbol,
id = assetId,
precision = 10,
priceId = null,
staking = "test",
icon = "test",
type = "test",
buyProviders = "test",
sellProviders = "test",
typeExtras = null,
enabled = true,
source = AssetSourceLocal.DEFAULT
)
suspend fun ChainDao.addChains(chains: List<JoinedChainInfo>) {
applyDiff(
chainDiff = addedDiff(chains.map(JoinedChainInfo::chain)),
assetsDiff = addedDiff(chains.flatMap(JoinedChainInfo::assets)),
nodesDiff = addedDiff(chains.flatMap(JoinedChainInfo::nodes)),
explorersDiff = addedDiff(chains.flatMap(JoinedChainInfo::explorers)),
externalApisDiff = addedDiff(chains.flatMap(JoinedChainInfo::externalApis)),
nodeSelectionPreferencesDiff = emptyDiff()
)
}
suspend fun ChainDao.addChain(joinedChainInfo: JoinedChainInfo) = addChains(listOf(joinedChainInfo))
suspend fun ChainDao.removeChain(joinedChainInfo: JoinedChainInfo) {
applyDiff(
chainDiff = removedDiff(joinedChainInfo.chain),
assetsDiff = removedDiff(joinedChainInfo.assets),
nodesDiff = removedDiff(joinedChainInfo.nodes),
explorersDiff = removedDiff(joinedChainInfo.explorers),
externalApisDiff = removedDiff(joinedChainInfo.externalApis),
nodeSelectionPreferencesDiff = emptyDiff()
)
}
suspend fun ChainDao.updateChain(joinedChainInfo: JoinedChainInfo) {
applyDiff(
chainDiff = updatedDiff(joinedChainInfo.chain),
assetsDiff = updatedDiff(joinedChainInfo.assets),
nodesDiff = updatedDiff(joinedChainInfo.nodes),
explorersDiff = updatedDiff(joinedChainInfo.explorers),
externalApisDiff = updatedDiff(joinedChainInfo.externalApis),
nodeSelectionPreferencesDiff = emptyDiff()
)
}
fun <T> addedDiff(elements: List<T>) = CollectionDiffer.Diff(
added = elements,
updated = emptyList(),
removed = emptyList(),
all = elements
)
fun <T> updatedDiff(elements: List<T>) = CollectionDiffer.Diff(
added = emptyList(),
updated = elements,
removed = emptyList(),
all = elements
)
fun <T> updatedDiff(element: T) = updatedDiff(listOf(element))
fun <T> addedDiff(element: T) = addedDiff(listOf(element))
fun <T> removedDiff(element: T) = removedDiff(listOf(element))
fun <T> removedDiff(elements: List<T>) = CollectionDiffer.Diff(
added = emptyList(),
updated = emptyList(),
removed = elements,
all = elements
)
fun <T> emptyDiff() = CollectionDiffer.Diff<T>(emptyList(), emptyList(), emptyList(), emptyList())
fun testMetaAccount(name: String = "Test") = MetaAccountLocal(
substratePublicKey = byteArrayOf(),
substrateCryptoType = CryptoType.SR25519,
ethereumPublicKey = null,
name = name,
isSelected = false,
substrateAccountId = byteArrayOf(),
ethereumAddress = null,
position = 0,
type = MetaAccountLocal.Type.WATCH_ONLY,
globallyUniqueId = "",
parentMetaId = 1,
status = MetaAccountLocal.Status.ACTIVE,
typeExtras = null
)
fun testChainAccount(
metaId: Long,
chainId: String,
accountId: ByteArray = byteArrayOf()
) = ChainAccountLocal(
metaId = metaId,
chainId = chainId,
publicKey = byteArrayOf(),
cryptoType = CryptoType.SR25519,
accountId = accountId
)
fun createCurrency(symbol: String = "$", selected: Boolean = true): CurrencyLocal {
return CurrencyLocal(
code = "USD",
name = "Dollar",
symbol = symbol,
category = CurrencyLocal.Category.FIAT,
popular = true,
id = 0,
coingeckoId = "usd",
selected = selected
)
}
@@ -0,0 +1,75 @@
package io.novafoundation.nova.core_db.dao
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.novafoundation.nova.core_db.AppDatabase
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
private const val CHAIN_ID = "1"
@RunWith(AndroidJUnit4::class)
class MetaAccountDaoTest : DaoTest<MetaAccountDao>(AppDatabase::metaAccountDao) {
private val chainDao by dao<ChainDao>()
@Before
fun insertChain() = runBlocking {
chainDao.addChain(createTestChain(id = CHAIN_ID))
}
@Test
fun shouldInsertMetaAccount() {
runBlocking {
dao.insertMetaAccount(testMetaAccount())
dao.insertMetaAccount(testMetaAccount())
val accountsFromDb = dao.getMetaAccounts()
assertEquals(2, accountsFromDb.size)
val isIdAutoGenerated = accountsFromDb.withIndex().all { (index, account) ->
account.id == index + 1L
}
assertTrue("Id should be autogenerated", isIdAutoGenerated)
}
}
@Test
fun shouldInsertAndRetrieveChainAccounts() {
runBlocking {
val metaId = dao.insertMetaAccount(testMetaAccount())
assertNotEquals(-1, metaId)
dao.insertChainAccount(testChainAccount(metaId, CHAIN_ID))
val joinedMetaAccountInfo = dao.getJoinedMetaAccountInfo(metaId)
assertEquals(1, joinedMetaAccountInfo.chainAccounts.size)
}
}
@Test
fun shouldReplaceChainAccounts() {
runBlocking {
val metaId = dao.insertMetaAccount(testMetaAccount())
val newAccountId = byteArrayOf(1)
dao.insertChainAccount(testChainAccount(metaId, CHAIN_ID, accountId = byteArrayOf(0)))
dao.insertChainAccount(testChainAccount(metaId, CHAIN_ID, accountId = newAccountId))
val chainAccounts = dao.getJoinedMetaAccountInfo(metaId).chainAccounts
assertEquals(1, chainAccounts.size)
assertArrayEquals(newAccountId, chainAccounts.single().accountId)
}
}
}
@@ -0,0 +1,70 @@
package io.novafoundation.nova.core_db.dao
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.novafoundation.nova.core_db.AppDatabase
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class TokenDaoTest : DaoTest<TokenDao>(AppDatabase::tokenDao) {
private val currencyDao by dao<CurrencyDao>()
private val tokenSymbol = "$"
@Test
fun getTokenWhenCurrencySelected() = runBlocking {
currencyDao.insert(createCurrency(tokenSymbol, true))
val tokenWithCurrency = dao.getTokenWithCurrency(tokenSymbol)
assert(tokenWithCurrency != null)
assert(tokenWithCurrency?.token == null)
}
@Test
fun getTokenWhenCurrencyNotSelected() = runBlocking {
currencyDao.insert(createCurrency(tokenSymbol, false))
val token = dao.getTokenWithCurrency(tokenSymbol)
assert(token == null)
}
@Test
fun getTokensWhenCurrencySelected() = runBlocking {
currencyDao.insert(createCurrency(tokenSymbol, true))
val tokensWithCurrencies = dao.getTokensWithCurrency(listOf(tokenSymbol))
assert(tokensWithCurrencies.isNotEmpty())
}
@Test
fun getTokensWhenCurrencyNotSelected() = runBlocking {
currencyDao.insert(createCurrency(tokenSymbol, false))
val tokensWithCurrencies = dao.getTokensWithCurrency(listOf(tokenSymbol))
assert(tokensWithCurrencies.isEmpty())
}
@Test
fun shouldInsertTokenWithDefaultCurrency() = runBlocking {
currencyDao.insert(createCurrency(tokenSymbol, true))
dao.insertTokenWithSelectedCurrency(tokenSymbol)
val tokenWithCurrency = dao.getTokenWithCurrency(tokenSymbol)
assert(tokenWithCurrency != null)
}
@Test
fun shouldInsertTokenWithoutCurrency() = runBlocking {
currencyDao.insert(createCurrency(tokenSymbol, false))
dao.insertTokenWithSelectedCurrency(tokenSymbol)
val tokenWithCurrency = dao.getTokenWithCurrency(tokenSymbol)
assert(tokenWithCurrency == null)
}
}
@@ -0,0 +1,64 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.Room
import androidx.room.migration.Migration
import androidx.room.testing.MigrationTestHelper
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import io.novafoundation.nova.core_db.AppDatabase
import kotlinx.coroutines.runBlocking
import org.junit.Rule
import org.junit.runner.RunWith
private const val DB_TEST_NAME = "test-db"
@RunWith(AndroidJUnit4::class)
abstract class BaseMigrationTest {
@get:Rule
val migrationHelper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
AppDatabase::class.java.canonicalName,
FrameworkSQLiteOpenHelperFactory()
)
protected fun runMigrationTest(
from: Int,
to: Int,
vararg migrations: Migration,
preMigrateBlock: (SupportSQLiteDatabase) -> Unit = {},
postMigrateBlock: suspend (AppDatabase) -> Unit = {}
) {
runBlocking {
val db = migrationHelper.createDatabase(DB_TEST_NAME, from)
preMigrateBlock(db)
val validateDroppedTables = true
migrationHelper.runMigrationsAndValidate(DB_TEST_NAME, to, validateDroppedTables, *migrations)
postMigrateBlock(getMigratedRoomDatabase(*migrations))
}
}
protected fun validateSchema(
from: Int,
to: Int,
vararg migrations: Migration,
) = runMigrationTest(from, to, *migrations)
private fun getMigratedRoomDatabase(vararg migrations: Migration): AppDatabase {
val database: AppDatabase = Room.databaseBuilder(ApplicationProvider.getApplicationContext(),
AppDatabase::class.java, DB_TEST_NAME)
.addMigrations(*migrations)
.build()
migrationHelper.closeWhenFinished(database)
return database
}
}
@@ -0,0 +1,195 @@
package io.novafoundation.nova.core_db.migrations
import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
import io.novafoundation.nova.core_db.AppDatabase
import io.novafoundation.nova.core_db.converters.CryptoTypeConverters
import io.novafoundation.nova.core_db.dao.assetOf
import io.novafoundation.nova.core_db.dao.chainOf
import io.novafoundation.nova.core_db.dao.testMetaAccount
import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal
import org.junit.Assert.assertEquals
import org.junit.Test
import java.math.BigInteger
private class OldAsset(
val metaId: Long,
val chainId: String,
val tokenSymbol: String,
val freeInPlanks: Int
)
class BetterChainDiffingTest_8_9 : BaseMigrationTest() {
private val cryptoTypeConverters = CryptoTypeConverters()
var meta1Id: Long = -1
var meta2Id: Long = -1
val chain1Id = "1"
val chain2Id = "2"
private lateinit var assetsOld: List<OldAsset>
@Test
fun validateMigration() = runMigrationTest(
from = 8,
to = 9,
BetterChainDiffing_8_9,
preMigrateBlock = ::preMigrate,
postMigrateBlock = ::postMigrate
)
private fun preMigrate(db: SupportSQLiteDatabase) {
db.beginTransaction()
db.insertChain(chain1Id, assetSymbols = listOf("A", "B", "C"))
db.insertChain(chain2Id, assetSymbols = listOf("C", "D", "E"))
meta1Id = db.insertMetaAccount(name = "1")
meta2Id = db.insertMetaAccount(name = "2")
assetsOld = listOf(
OldAsset(meta1Id, chain1Id, tokenSymbol = "A", freeInPlanks = 1),
OldAsset(meta1Id, chain1Id, tokenSymbol = "B", freeInPlanks = 2),
OldAsset(meta1Id, chain1Id, tokenSymbol = "C", freeInPlanks = 3),
OldAsset(meta1Id, chain2Id, tokenSymbol = "C", freeInPlanks = 4),
OldAsset(meta1Id, chain2Id, tokenSymbol = "D", freeInPlanks = 5),
OldAsset(meta1Id, chain2Id, tokenSymbol = "E", freeInPlanks = 6),
OldAsset(meta2Id, chain1Id, tokenSymbol = "A", freeInPlanks = 11),
OldAsset(meta2Id, chain1Id, tokenSymbol = "C", freeInPlanks = 13),
)
assetsOld.forEach { db.insertAsset(it) }
db.setTransactionSuccessful()
db.endTransaction()
}
private suspend fun postMigrate(db: AppDatabase) {
val assetsForMeta1 = db.assetDao().getSupportedAssets(meta1Id)
val symbolToAssetIdMapping = mapOf(
(chain1Id to "A") to 0,
(chain1Id to "B") to 1,
(chain1Id to "C") to 2,
(chain2Id to "C") to 0,
(chain2Id to "D") to 1,
(chain2Id to "E") to 2,
)
assetsForMeta1.forEach {
val actualChainId = it.asset!!.chainId
val actualTokenSymbol = it.token!!.tokenSymbol
val assetIdExpected = symbolToAssetIdMapping[actualChainId to actualTokenSymbol]
assertEquals(assetIdExpected, it.asset!!.assetId)
val expectedOldAsset = assetsOld.first { it.chainId == actualChainId && it.metaId == meta1Id && it.tokenSymbol == actualTokenSymbol }
assertEquals(expectedOldAsset.freeInPlanks.toBigInteger(), it.asset!!.freeInPlanks)
}
}
private fun SupportSQLiteDatabase.insertMetaAccount(
name: String
): Long {
val metaAccount = testMetaAccount(name)
val contentValues = ContentValues().apply {
put(MetaAccountLocal.Table.Column.SUBSTRATE_PUBKEY, metaAccount.substratePublicKey)
put(MetaAccountLocal.Table.Column.SUBSTRATE_ACCOUNT_ID, metaAccount.substrateAccountId)
put(MetaAccountLocal.Table.Column.ETHEREUM_ADDRESS, metaAccount.ethereumAddress)
put(MetaAccountLocal.Table.Column.ETHEREUM_PUBKEY, metaAccount.ethereumPublicKey)
put(MetaAccountLocal.Table.Column.NAME, metaAccount.name)
put(MetaAccountLocal.Table.Column.SUBSTRATE_CRYPTO_TYPE, cryptoTypeConverters.from(metaAccount.substrateCryptoType))
put(MetaAccountLocal.Table.Column.IS_SELECTED, metaAccount.isSelected)
put(MetaAccountLocal.Table.Column.POSITION, metaAccount.position)
}
return insert(MetaAccountLocal.TABLE_NAME, 0, contentValues)
}
private fun SupportSQLiteDatabase.insertChain(
id: String,
assetSymbols: List<String>
) {
val chain = chainOf(id)
val contentValues = ContentValues().apply {
put("parentId", chain.parentId)
put("name", chain.name)
put("additional", chain.additional)
put("id", chain.id)
put("icon", chain.icon)
// types
putNull("url")
putNull("overridesCommon")
// externalApi
putNull("staking_url")
putNull("staking_type")
putNull("history_type")
putNull("history_url")
putNull("crowdloans_url")
putNull("crowdloans_type")
put("prefix", chain.prefix)
put("isEthereumBased", chain.isEthereumBased)
put("isTestNet", chain.isTestNet)
put("hasCrowdloans", chain.hasCrowdloans)
}
insert("chains", 0, contentValues)
val assets = assetSymbols.mapIndexed { index, symbol ->
chain.assetOf(assetId = index, symbol)
}
assets.forEach {
val contentValues = ContentValues().apply {
put("id", it.id)
put("chainId", it.chainId)
put("name", it.name)
put("symbol", it.symbol)
put("priceId", it.priceId)
put("staking", it.staking)
put("precision", it.precision)
put("icon", it.icon)
put("type", it.type)
put("typeExtras", it.typeExtras)
put("buyProviders", it.buyProviders)
}
insert("chain_assets", 0, contentValues)
}
}
private fun SupportSQLiteDatabase.insertAsset(oldAsset: OldAsset) {
val tokenContentValues = ContentValues().apply {
put("symbol", oldAsset.tokenSymbol)
putNull("dollarRate")
putNull("recentRateChange")
}
insert("tokens", SQLiteDatabase.CONFLICT_REPLACE, tokenContentValues)
val assetContentValues = ContentValues().apply {
put("tokenSymbol", oldAsset.tokenSymbol)
put("chainId", oldAsset.chainId)
put("metaId", oldAsset.metaId)
val amountZero = BigInteger.ZERO.toString()
put("freeInPlanks", oldAsset.freeInPlanks.toString())
put("frozenInPlanks", amountZero)
put("reservedInPlanks", amountZero)
put("bondedInPlanks", amountZero)
put("redeemableInPlanks", amountZero)
put("unbondingInPlanks", amountZero)
}
insert("assets", 0, assetContentValues)
}
}
+1
View File
@@ -0,0 +1 @@
<manifest />
@@ -0,0 +1,341 @@
package io.novafoundation.nova.core_db
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import io.novafoundation.nova.core_db.converters.AssetConverters
import io.novafoundation.nova.core_db.converters.ChainConverters
import io.novafoundation.nova.core_db.converters.CryptoTypeConverters
import io.novafoundation.nova.core_db.converters.CurrencyConverters
import io.novafoundation.nova.core_db.converters.ExternalApiConverters
import io.novafoundation.nova.core_db.converters.ExternalBalanceTypeConverters
import io.novafoundation.nova.core_db.converters.LongMathConverters
import io.novafoundation.nova.core_db.converters.MetaAccountTypeConverters
import io.novafoundation.nova.core_db.converters.NetworkTypeConverters
import io.novafoundation.nova.core_db.converters.NftConverters
import io.novafoundation.nova.core_db.converters.OperationConverters
import io.novafoundation.nova.core_db.converters.ProxyAccountConverters
import io.novafoundation.nova.core_db.dao.AccountDao
import io.novafoundation.nova.core_db.dao.AccountStakingDao
import io.novafoundation.nova.core_db.dao.AssetDao
import io.novafoundation.nova.core_db.dao.BrowserHostSettingsDao
import io.novafoundation.nova.core_db.dao.BrowserTabsDao
import io.novafoundation.nova.core_db.dao.ChainAssetDao
import io.novafoundation.nova.core_db.dao.ChainDao
import io.novafoundation.nova.core_db.dao.CoinPriceDao
import io.novafoundation.nova.core_db.dao.ContributionDao
import io.novafoundation.nova.core_db.dao.CurrencyDao
import io.novafoundation.nova.core_db.dao.DappAuthorizationDao
import io.novafoundation.nova.core_db.dao.ExternalBalanceDao
import io.novafoundation.nova.core_db.dao.FavouriteDAppsDao
import io.novafoundation.nova.core_db.dao.GiftsDao
import io.novafoundation.nova.core_db.dao.GovernanceDAppsDao
import io.novafoundation.nova.core_db.dao.HoldsDao
import io.novafoundation.nova.core_db.dao.LockDao
import io.novafoundation.nova.core_db.dao.MetaAccountDao
import io.novafoundation.nova.core_db.dao.MultisigOperationsDao
import io.novafoundation.nova.core_db.dao.NftDao
import io.novafoundation.nova.core_db.dao.NodeDao
import io.novafoundation.nova.core_db.dao.OperationDao
import io.novafoundation.nova.core_db.dao.PhishingAddressDao
import io.novafoundation.nova.core_db.dao.PhishingSitesDao
import io.novafoundation.nova.core_db.dao.StakingDashboardDao
import io.novafoundation.nova.core_db.dao.StakingRewardPeriodDao
import io.novafoundation.nova.core_db.dao.StakingTotalRewardDao
import io.novafoundation.nova.core_db.dao.StorageDao
import io.novafoundation.nova.core_db.dao.TinderGovDao
import io.novafoundation.nova.core_db.dao.TokenDao
import io.novafoundation.nova.core_db.dao.WalletConnectSessionsDao
import io.novafoundation.nova.core_db.migrations.AddAdditionalFieldToChains_12_13
import io.novafoundation.nova.core_db.migrations.AddBalanceHolds_60_61
import io.novafoundation.nova.core_db.migrations.AddBalanceModesToAssets_51_52
import io.novafoundation.nova.core_db.migrations.AddBrowserHostSettings_34_35
import io.novafoundation.nova.core_db.migrations.AddBrowserTabs_64_65
import io.novafoundation.nova.core_db.migrations.AddBuyProviders_7_8
import io.novafoundation.nova.core_db.migrations.AddChainColor_4_5
import io.novafoundation.nova.core_db.migrations.AddChainForeignKeyForProxy_63_64
import io.novafoundation.nova.core_db.migrations.AddConnectionStateToChains_53_54
import io.novafoundation.nova.core_db.migrations.AddFieldsToContributions
import io.novafoundation.nova.core_db.migrations.AddContributions_23_24
import io.novafoundation.nova.core_db.migrations.AddCurrencies_18_19
import io.novafoundation.nova.core_db.migrations.AddDAppAuthorizations_1_2
import io.novafoundation.nova.core_db.migrations.AddEnabledColumnToChainAssets_30_31
import io.novafoundation.nova.core_db.migrations.AddEventIdToOperation_47_48
import io.novafoundation.nova.core_db.migrations.AddExternalBalances_45_46
import io.novafoundation.nova.core_db.migrations.AddExtrinsicContentField_37_38
import io.novafoundation.nova.core_db.migrations.AddFavoriteDAppsOrdering_65_66
import io.novafoundation.nova.core_db.migrations.AddFavouriteDApps_9_10
import io.novafoundation.nova.core_db.migrations.AddFungibleNfts_55_56
import io.novafoundation.nova.core_db.migrations.AddGifts_71_72
import io.novafoundation.nova.core_db.migrations.AddGloballyUniqueIdToMetaAccounts_58_59
import io.novafoundation.nova.core_db.migrations.AddGovernanceDapps_25_26
import io.novafoundation.nova.core_db.migrations.AddGovernanceExternalApiToChain_27_28
import io.novafoundation.nova.core_db.migrations.AddGovernanceFlagToChains_24_25
import io.novafoundation.nova.core_db.migrations.AddGovernanceNetworkToExternalApi_33_34
import io.novafoundation.nova.core_db.migrations.AddLegacyAddressPrefix_66_67
import io.novafoundation.nova.core_db.migrations.AddLocalMigratorVersionToChainRuntimes_57_58
import io.novafoundation.nova.core_db.migrations.AddLocks_22_23
import io.novafoundation.nova.core_db.migrations.AddMetaAccountType_14_15
import io.novafoundation.nova.core_db.migrations.AddMultisigCalls_69_70
import io.novafoundation.nova.core_db.migrations.AddMultisigSupportFlag_70_71
import io.novafoundation.nova.core_db.migrations.AddNfts_5_6
import io.novafoundation.nova.core_db.migrations.AddNodeSelectionStrategyField_38_39
import io.novafoundation.nova.core_db.migrations.AddPoolIdToOperations_46_47
import io.novafoundation.nova.core_db.migrations.AddProxyAccount_54_55
import io.novafoundation.nova.core_db.migrations.AddRewardAccountToStakingDashboard_43_44
import io.novafoundation.nova.core_db.migrations.AddRuntimeFlagToChains_36_37
import io.novafoundation.nova.core_db.migrations.AddSellProviders_67_68
import io.novafoundation.nova.core_db.migrations.AddSitePhishing_6_7
import io.novafoundation.nova.core_db.migrations.AddSourceToLocalAsset_28_29
import io.novafoundation.nova.core_db.migrations.AddStakingDashboardItems_41_42
import io.novafoundation.nova.core_db.migrations.AddStakingTypeToTotalRewards_44_45
import io.novafoundation.nova.core_db.migrations.AddSwapOption_48_49
import io.novafoundation.nova.core_db.migrations.AddTransactionVersionToRuntime_50_51
import io.novafoundation.nova.core_db.migrations.AddTransferApisTable_29_30
import io.novafoundation.nova.core_db.migrations.AddTypeExtrasToMetaAccount_68_69
import io.novafoundation.nova.core_db.migrations.AddVersioningToGovernanceDapps_32_33
import io.novafoundation.nova.core_db.migrations.AddWalletConnectSessions_39_40
import io.novafoundation.nova.core_db.migrations.AssetTypes_2_3
import io.novafoundation.nova.core_db.migrations.BetterChainDiffing_8_9
import io.novafoundation.nova.core_db.migrations.ChainNetworkManagement_59_60
import io.novafoundation.nova.core_db.migrations.ChainNetworkManagement_61_62
import io.novafoundation.nova.core_db.migrations.ChainPushSupport_56_57
import io.novafoundation.nova.core_db.migrations.ChangeAsset_3_4
import io.novafoundation.nova.core_db.migrations.ChangeChainNodes_20_21
import io.novafoundation.nova.core_db.migrations.ChangeDAppAuthorization_10_11
import io.novafoundation.nova.core_db.migrations.ChangeSessionTopicToParing_52_53
import io.novafoundation.nova.core_db.migrations.ChangeTokens_19_20
import io.novafoundation.nova.core_db.migrations.ExtractExternalApiToSeparateTable_35_36
import io.novafoundation.nova.core_db.migrations.FixBrokenForeignKeys_31_32
import io.novafoundation.nova.core_db.migrations.FixMigrationConflicts_13_14
import io.novafoundation.nova.core_db.migrations.GovernanceFlagToEnum_26_27
import io.novafoundation.nova.core_db.migrations.NullableSubstrateAccountId_21_22
import io.novafoundation.nova.core_db.migrations.NullableSubstratePublicKey_15_16
import io.novafoundation.nova.core_db.migrations.RefactorOperations_49_50
import io.novafoundation.nova.core_db.migrations.RemoveChainForeignKeyFromChainAccount_11_12
import io.novafoundation.nova.core_db.migrations.RemoveColorFromChains_17_18
import io.novafoundation.nova.core_db.migrations.StakingRewardPeriods_42_43
import io.novafoundation.nova.core_db.migrations.TinderGovBasket_62_63
import io.novafoundation.nova.core_db.migrations.TransferFiatAmount_40_41
import io.novafoundation.nova.core_db.migrations.WatchOnlyChainAccounts_16_17
import io.novafoundation.nova.core_db.model.AccountLocal
import io.novafoundation.nova.core_db.model.AccountStakingLocal
import io.novafoundation.nova.core_db.model.AssetLocal
import io.novafoundation.nova.core_db.model.BalanceHoldLocal
import io.novafoundation.nova.core_db.model.BalanceLockLocal
import io.novafoundation.nova.core_db.model.BrowserHostSettingsLocal
import io.novafoundation.nova.core_db.model.BrowserTabLocal
import io.novafoundation.nova.core_db.model.CoinPriceLocal
import io.novafoundation.nova.core_db.model.ContributionLocal
import io.novafoundation.nova.core_db.model.CurrencyLocal
import io.novafoundation.nova.core_db.model.DappAuthorizationLocal
import io.novafoundation.nova.core_db.model.ExternalBalanceLocal
import io.novafoundation.nova.core_db.model.FavouriteDAppLocal
import io.novafoundation.nova.core_db.model.GiftLocal
import io.novafoundation.nova.core_db.model.GovernanceDAppLocal
import io.novafoundation.nova.core_db.model.MultisigOperationCallLocal
import io.novafoundation.nova.core_db.model.NftLocal
import io.novafoundation.nova.core_db.model.NodeLocal
import io.novafoundation.nova.core_db.model.PhishingAddressLocal
import io.novafoundation.nova.core_db.model.PhishingSiteLocal
import io.novafoundation.nova.core_db.model.StakingDashboardItemLocal
import io.novafoundation.nova.core_db.model.StakingRewardPeriodLocal
import io.novafoundation.nova.core_db.model.StorageEntryLocal
import io.novafoundation.nova.core_db.model.TinderGovBasketItemLocal
import io.novafoundation.nova.core_db.model.TinderGovVotingPowerLocal
import io.novafoundation.nova.core_db.model.TokenLocal
import io.novafoundation.nova.core_db.model.TotalRewardLocal
import io.novafoundation.nova.core_db.model.WalletConnectPairingLocal
import io.novafoundation.nova.core_db.model.chain.ChainAssetLocal
import io.novafoundation.nova.core_db.model.chain.ChainExplorerLocal
import io.novafoundation.nova.core_db.model.chain.ChainExternalApiLocal
import io.novafoundation.nova.core_db.model.chain.ChainLocal
import io.novafoundation.nova.core_db.model.chain.ChainNodeLocal
import io.novafoundation.nova.core_db.model.chain.ChainRuntimeInfoLocal
import io.novafoundation.nova.core_db.model.chain.NodeSelectionPreferencesLocal
import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal
import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal
import io.novafoundation.nova.core_db.model.chain.account.ProxyAccountLocal
import io.novafoundation.nova.core_db.model.operation.DirectRewardTypeLocal
import io.novafoundation.nova.core_db.model.operation.ExtrinsicTypeLocal
import io.novafoundation.nova.core_db.model.operation.OperationBaseLocal
import io.novafoundation.nova.core_db.model.operation.PoolRewardTypeLocal
import io.novafoundation.nova.core_db.model.operation.SwapTypeLocal
import io.novafoundation.nova.core_db.model.operation.TransferTypeLocal
@Database(
version = 73,
entities = [
AccountLocal::class,
NodeLocal::class,
AssetLocal::class,
TokenLocal::class,
PhishingAddressLocal::class,
StorageEntryLocal::class,
AccountStakingLocal::class,
TotalRewardLocal::class,
OperationBaseLocal::class,
TransferTypeLocal::class,
DirectRewardTypeLocal::class,
PoolRewardTypeLocal::class,
ExtrinsicTypeLocal::class,
SwapTypeLocal::class,
ChainLocal::class,
ChainNodeLocal::class,
ChainAssetLocal::class,
ChainRuntimeInfoLocal::class,
ChainExplorerLocal::class,
ChainExternalApiLocal::class,
MetaAccountLocal::class,
ChainAccountLocal::class,
DappAuthorizationLocal::class,
NftLocal::class,
PhishingSiteLocal::class,
FavouriteDAppLocal::class,
CurrencyLocal::class,
BalanceLockLocal::class,
ContributionLocal::class,
GovernanceDAppLocal::class,
BrowserHostSettingsLocal::class,
WalletConnectPairingLocal::class,
CoinPriceLocal::class,
StakingDashboardItemLocal::class,
StakingRewardPeriodLocal::class,
ExternalBalanceLocal::class,
ProxyAccountLocal::class,
BalanceHoldLocal::class,
NodeSelectionPreferencesLocal::class,
TinderGovBasketItemLocal::class,
TinderGovVotingPowerLocal::class,
BrowserTabLocal::class,
MultisigOperationCallLocal::class,
GiftLocal::class
],
)
@TypeConverters(
LongMathConverters::class,
NetworkTypeConverters::class,
OperationConverters::class,
CryptoTypeConverters::class,
NftConverters::class,
MetaAccountTypeConverters::class,
CurrencyConverters::class,
AssetConverters::class,
ExternalApiConverters::class,
ChainConverters::class,
ExternalBalanceTypeConverters::class,
ProxyAccountConverters::class,
)
abstract class AppDatabase : RoomDatabase() {
companion object {
private var instance: AppDatabase? = null
@Synchronized
fun get(
context: Context
): AppDatabase {
if (instance == null) {
instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"app.db"
)
.addMigrations(AddDAppAuthorizations_1_2, AssetTypes_2_3, ChangeAsset_3_4)
.addMigrations(AddChainColor_4_5, AddNfts_5_6, AddSitePhishing_6_7, AddBuyProviders_7_8, BetterChainDiffing_8_9)
.addMigrations(AddFavouriteDApps_9_10, ChangeDAppAuthorization_10_11, RemoveChainForeignKeyFromChainAccount_11_12)
.addMigrations(AddAdditionalFieldToChains_12_13, FixMigrationConflicts_13_14, AddMetaAccountType_14_15)
.addMigrations(NullableSubstratePublicKey_15_16, WatchOnlyChainAccounts_16_17, RemoveColorFromChains_17_18)
.addMigrations(AddCurrencies_18_19, ChangeTokens_19_20, ChangeChainNodes_20_21)
.addMigrations(NullableSubstrateAccountId_21_22, AddLocks_22_23, AddContributions_23_24)
.addMigrations(AddGovernanceFlagToChains_24_25, AddGovernanceDapps_25_26, GovernanceFlagToEnum_26_27)
.addMigrations(AddGovernanceExternalApiToChain_27_28)
.addMigrations(AddSourceToLocalAsset_28_29, AddTransferApisTable_29_30, AddEnabledColumnToChainAssets_30_31)
.addMigrations(FixBrokenForeignKeys_31_32, AddVersioningToGovernanceDapps_32_33)
.addMigrations(AddGovernanceNetworkToExternalApi_33_34, AddBrowserHostSettings_34_35)
.addMigrations(ExtractExternalApiToSeparateTable_35_36, AddRuntimeFlagToChains_36_37)
.addMigrations(AddExtrinsicContentField_37_38, AddNodeSelectionStrategyField_38_39)
.addMigrations(AddWalletConnectSessions_39_40, TransferFiatAmount_40_41)
.addMigrations(AddStakingDashboardItems_41_42, StakingRewardPeriods_42_43)
.addMigrations(AddRewardAccountToStakingDashboard_43_44, AddStakingTypeToTotalRewards_44_45, AddExternalBalances_45_46)
.addMigrations(AddPoolIdToOperations_46_47, AddEventIdToOperation_47_48, AddSwapOption_48_49)
.addMigrations(RefactorOperations_49_50, AddTransactionVersionToRuntime_50_51, AddBalanceModesToAssets_51_52)
.addMigrations(ChangeSessionTopicToParing_52_53, AddConnectionStateToChains_53_54, AddProxyAccount_54_55)
.addMigrations(AddFungibleNfts_55_56, ChainPushSupport_56_57)
.addMigrations(AddLocalMigratorVersionToChainRuntimes_57_58, AddGloballyUniqueIdToMetaAccounts_58_59)
.addMigrations(ChainNetworkManagement_59_60, AddBalanceHolds_60_61, ChainNetworkManagement_61_62)
.addMigrations(TinderGovBasket_62_63, AddChainForeignKeyForProxy_63_64, AddBrowserTabs_64_65)
.addMigrations(AddFavoriteDAppsOrdering_65_66, AddLegacyAddressPrefix_66_67, AddSellProviders_67_68)
.addMigrations(AddTypeExtrasToMetaAccount_68_69, AddMultisigCalls_69_70, AddMultisigSupportFlag_70_71)
.addMigrations(AddGifts_71_72, AddFieldsToContributions)
.build()
}
return instance!!
}
}
abstract fun nodeDao(): NodeDao
abstract fun userDao(): AccountDao
abstract fun assetDao(): AssetDao
abstract fun operationDao(): OperationDao
abstract fun phishingAddressesDao(): PhishingAddressDao
abstract fun storageDao(): StorageDao
abstract fun tokenDao(): TokenDao
abstract fun accountStakingDao(): AccountStakingDao
abstract fun stakingTotalRewardDao(): StakingTotalRewardDao
abstract fun chainDao(): ChainDao
abstract fun chainAssetDao(): ChainAssetDao
abstract fun metaAccountDao(): MetaAccountDao
abstract fun dAppAuthorizationDao(): DappAuthorizationDao
abstract fun nftDao(): NftDao
abstract fun phishingSitesDao(): PhishingSitesDao
abstract fun favouriteDAppsDao(): FavouriteDAppsDao
abstract fun currencyDao(): CurrencyDao
abstract fun lockDao(): LockDao
abstract fun contributionDao(): ContributionDao
abstract fun governanceDAppsDao(): GovernanceDAppsDao
abstract fun browserHostSettingsDao(): BrowserHostSettingsDao
abstract fun walletConnectSessionsDao(): WalletConnectSessionsDao
abstract fun stakingDashboardDao(): StakingDashboardDao
abstract fun coinPriceDao(): CoinPriceDao
abstract fun stakingRewardPeriodDao(): StakingRewardPeriodDao
abstract fun externalBalanceDao(): ExternalBalanceDao
abstract fun holdsDao(): HoldsDao
abstract fun tinderGovDao(): TinderGovDao
abstract fun browserTabsDao(): BrowserTabsDao
abstract fun multisigOperationsDao(): MultisigOperationsDao
abstract fun giftsDao(): GiftsDao
}
@@ -0,0 +1,39 @@
package io.novafoundation.nova.core_db.converters
import androidx.room.TypeConverter
import io.novafoundation.nova.core_db.model.AssetLocal.EDCountingModeLocal
import io.novafoundation.nova.core_db.model.AssetLocal.TransferableModeLocal
import io.novafoundation.nova.core_db.model.chain.AssetSourceLocal
class AssetConverters {
@TypeConverter
fun fromCategory(type: AssetSourceLocal): String {
return type.name
}
@TypeConverter
fun toCategory(name: String): AssetSourceLocal {
return enumValueOf(name)
}
@TypeConverter
fun fromTransferableMode(mode: TransferableModeLocal): Int {
return mode.ordinal
}
@TypeConverter
fun toTransferableMode(index: Int): TransferableModeLocal {
return TransferableModeLocal.values()[index]
}
@TypeConverter
fun fromEdCountingMode(mode: EDCountingModeLocal): Int {
return mode.ordinal
}
@TypeConverter
fun toEdCountingMode(index: Int): EDCountingModeLocal {
return EDCountingModeLocal.values()[index]
}
}
@@ -0,0 +1,25 @@
package io.novafoundation.nova.core_db.converters
import androidx.room.TypeConverter
import io.novafoundation.nova.common.utils.enumValueOfOrNull
import io.novafoundation.nova.core_db.model.chain.ChainLocal.ConnectionStateLocal
import io.novafoundation.nova.core_db.model.chain.ChainLocal.AutoBalanceStrategyLocal
class ChainConverters {
@TypeConverter
fun fromNodeStrategy(strategy: AutoBalanceStrategyLocal): String = strategy.name
@TypeConverter
fun toNodeStrategy(name: String): AutoBalanceStrategyLocal {
return enumValueOfOrNull<AutoBalanceStrategyLocal>(name) ?: AutoBalanceStrategyLocal.UNKNOWN
}
@TypeConverter
fun fromConnection(connectionState: ConnectionStateLocal): String = connectionState.name
@TypeConverter
fun toConnection(name: String): ConnectionStateLocal {
return enumValueOfOrNull<ConnectionStateLocal>(name) ?: ConnectionStateLocal.LIGHT_SYNC
}
}
@@ -0,0 +1,13 @@
package io.novafoundation.nova.core_db.converters
import androidx.room.TypeConverter
import io.novafoundation.nova.core.model.CryptoType
class CryptoTypeConverters {
@TypeConverter
fun from(cryptoType: CryptoType?): String? = cryptoType?.name
@TypeConverter
fun to(name: String?): CryptoType? = name?.let { enumValueOf<CryptoType>(it) }
}
@@ -0,0 +1,17 @@
package io.novafoundation.nova.core_db.converters
import androidx.room.TypeConverter
import io.novafoundation.nova.core_db.model.CurrencyLocal
class CurrencyConverters {
@TypeConverter
fun fromCategory(type: CurrencyLocal.Category): String {
return type.name
}
@TypeConverter
fun toCategory(name: String): CurrencyLocal.Category {
return enumValueOf(name)
}
}
@@ -0,0 +1,18 @@
package io.novafoundation.nova.core_db.converters
import androidx.room.TypeConverter
import io.novafoundation.nova.common.utils.enumValueOfOrNull
import io.novafoundation.nova.core_db.model.chain.ChainExternalApiLocal.SourceType
class ExternalApiConverters {
@TypeConverter
fun fromApiType(apiType: SourceType): String {
return apiType.name
}
@TypeConverter
fun toApiType(raw: String): SourceType {
return enumValueOfOrNull<SourceType>(raw) ?: SourceType.UNKNOWN
}
}
@@ -0,0 +1,17 @@
package io.novafoundation.nova.core_db.converters
import androidx.room.TypeConverter
import io.novafoundation.nova.core_db.model.ExternalBalanceLocal
class ExternalBalanceTypeConverters {
@TypeConverter
fun fromType(type: ExternalBalanceLocal.Type): String {
return type.name
}
@TypeConverter
fun toType(name: String): ExternalBalanceLocal.Type {
return ExternalBalanceLocal.Type.valueOf(name)
}
}
@@ -0,0 +1,38 @@
package io.novafoundation.nova.core_db.converters
import androidx.room.TypeConverter
import java.math.BigDecimal
import java.math.BigInteger
class LongMathConverters {
@TypeConverter
fun fromBigDecimal(balance: BigDecimal?): String? {
return balance?.toString()
}
@TypeConverter
fun toBigDecimal(balance: String?): BigDecimal? {
return balance?.let { BigDecimal(it) }
}
@TypeConverter
fun fromBigInteger(balance: BigInteger?): String? {
return balance?.toString()
}
@TypeConverter
fun toBigInteger(balance: String?): BigInteger? {
return balance?.let {
// When using aggregates like SUM in SQL queries, SQLite might return the result in a scientific notation especially if aggregation is done
// BigInteger, which is stored as a string and SQLite casts it to REAL which causing the scientific notation on big numbers
// This can be avoided by adjusting the query but we keep the fallback to BigDecimal parsing here anyways to avoid unpleasant crashes
// It doesn't bring much impact since try-catch doesn't have an overhead unless the exception is thrown
try {
BigInteger(it)
} catch (e: NumberFormatException) {
BigDecimal(it).toBigInteger()
}
}
}
}
@@ -0,0 +1,17 @@
package io.novafoundation.nova.core_db.converters
import androidx.room.TypeConverter
import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal
class MetaAccountTypeConverters {
@TypeConverter
fun fromEnum(type: MetaAccountLocal.Type): String {
return type.name
}
@TypeConverter
fun toEnum(name: String): MetaAccountLocal.Type {
return MetaAccountLocal.Type.valueOf(name)
}
}
@@ -0,0 +1,17 @@
package io.novafoundation.nova.core_db.converters
import androidx.room.TypeConverter
import io.novafoundation.nova.core.model.Node
class NetworkTypeConverters {
@TypeConverter
fun fromNetworkType(networkType: Node.NetworkType): Int {
return networkType.ordinal
}
@TypeConverter
fun toNetworkType(ordinal: Int): Node.NetworkType {
return Node.NetworkType.values()[ordinal]
}
}
@@ -0,0 +1,27 @@
package io.novafoundation.nova.core_db.converters
import androidx.room.TypeConverter
import io.novafoundation.nova.core_db.model.NftLocal
class NftConverters {
@TypeConverter
fun fromNftType(type: NftLocal.Type): String {
return type.name
}
@TypeConverter
fun toNftType(name: String): NftLocal.Type {
return enumValueOf(name)
}
@TypeConverter
fun fromNftIssuanceType(type: NftLocal.IssuanceType): String {
return type.name
}
@TypeConverter
fun toNftIssuanceType(name: String): NftLocal.IssuanceType {
return enumValueOf(name)
}
}
@@ -0,0 +1,26 @@
package io.novafoundation.nova.core_db.converters
import androidx.room.TypeConverter
import io.novafoundation.nova.core_db.model.operation.ExtrinsicTypeLocal
import io.novafoundation.nova.core_db.model.operation.OperationBaseLocal
class OperationConverters {
@TypeConverter
fun fromOperationSource(source: OperationBaseLocal.Source) = source.ordinal
@TypeConverter
fun toOperationSource(ordinal: Int) = OperationBaseLocal.Source.values()[ordinal]
@TypeConverter
fun fromOperationStatus(status: OperationBaseLocal.Status) = status.ordinal
@TypeConverter
fun toOperationStatus(ordinal: Int) = OperationBaseLocal.Status.values()[ordinal]
@TypeConverter
fun fromExtrinsicContentType(type: ExtrinsicTypeLocal.ContentType) = type.name
@TypeConverter
fun toExtrinsicContentType(name: String): ExtrinsicTypeLocal.ContentType = enumValueOf(name)
}
@@ -0,0 +1,16 @@
package io.novafoundation.nova.core_db.converters
import androidx.room.TypeConverter
import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal
class ProxyAccountConverters {
@TypeConverter
fun fromStatusType(type: MetaAccountLocal.Status): String {
return type.name
}
@TypeConverter
fun toStatusType(name: String): MetaAccountLocal.Status {
return MetaAccountLocal.Status.valueOf(name)
}
}
@@ -0,0 +1,47 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import io.novafoundation.nova.core.model.Node
import io.novafoundation.nova.core_db.model.AccountLocal
import kotlinx.coroutines.flow.Flow
@Dao
abstract class AccountDao {
@Query("select * from users order by networkType, position")
abstract fun accountsFlow(): Flow<List<AccountLocal>>
@Query("select * from users order by networkType, position")
abstract suspend fun getAccounts(): List<AccountLocal>
@Query("select * from users where address = :address")
abstract suspend fun getAccount(address: String): AccountLocal?
@Insert(onConflict = OnConflictStrategy.ABORT)
abstract suspend fun insert(account: AccountLocal): Long
@Query("DELETE FROM users where address = :address")
abstract suspend fun remove(address: String)
@Update
abstract suspend fun updateAccount(account: AccountLocal)
@Update
abstract suspend fun updateAccounts(accounts: List<AccountLocal>)
@Query("SELECT COALESCE(MAX(position), 0) + 1 from users")
abstract suspend fun getNextPosition(): Int
@Query("select * from users where networkType = :networkType")
abstract suspend fun getAccountsByNetworkType(networkType: Int): List<AccountLocal>
@Query("select * from users where (address LIKE '%' || :query || '%') AND networkType = :networkType")
abstract suspend fun getAccounts(query: String, networkType: Node.NetworkType): List<AccountLocal>
@Query("SELECT EXISTS(SELECT * FROM users WHERE address = :accountAddress)")
abstract suspend fun accountExists(accountAddress: String): Boolean
}
@@ -0,0 +1,34 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import io.novafoundation.nova.core_db.model.AccountStakingLocal
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
private const val SELECT_QUERY = """
SELECT * FROM account_staking_accesses
WHERE accountId = :accountId AND chainId = :chainId AND chainAssetId = :chainAssetId
"""
@Dao
abstract class AccountStakingDao {
@Query(SELECT_QUERY)
abstract suspend fun get(chainId: String, chainAssetId: Int, accountId: ByteArray): AccountStakingLocal
@Query(SELECT_QUERY)
protected abstract fun observeInternal(chainId: String, chainAssetId: Int, accountId: ByteArray): Flow<AccountStakingLocal?>
fun observeDistinct(chainId: String, chainAssetId: Int, accountId: ByteArray): Flow<AccountStakingLocal> {
return observeInternal(chainId, chainAssetId, accountId)
.filterNotNull()
.distinctUntilChanged()
}
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insert(accountStaking: AccountStakingLocal)
}
@@ -0,0 +1,130 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import io.novafoundation.nova.common.utils.flowOfAll
import io.novafoundation.nova.common.utils.mapToSet
import io.novafoundation.nova.core_db.model.AssetAndChainId
import io.novafoundation.nova.core_db.model.AssetLocal
import io.novafoundation.nova.core_db.model.AssetWithToken
import kotlinx.coroutines.flow.Flow
private const val RETRIEVE_ASSET_SQL_META_ID = """
SELECT *, ca.chainId as ca_chainId, ca.id as ca_assetId FROM chain_assets AS ca
LEFT JOIN assets AS a ON a.assetId = ca.id AND a.chainId = ca.chainId AND a.metaId = :metaId
INNER JOIN currencies as currency ON currency.selected = 1
LEFT JOIN tokens AS t ON ca.symbol = t.tokenSymbol AND currency.id = t.currencyId
WHERE ca.chainId = :chainId AND ca.id = :assetId
"""
private const val RETRIEVE_SYNCED_ACCOUNT_ASSETS_QUERY = """
SELECT *, ca.chainId as ca_chainId, ca.id as ca_assetId FROM assets AS a
INNER JOIN chain_assets AS ca ON a.assetId = ca.id AND a.chainId = ca.chainId
INNER JOIN currencies as currency ON currency.selected = 1
LEFT JOIN tokens AS t ON ca.symbol = t.tokenSymbol AND currency.id = t.currencyId
WHERE a.metaId = :metaId
"""
private const val RETRIEVE_SUPPORTED_ACCOUNT_ASSETS_QUERY = """
SELECT *, ca.chainId as ca_chainId, ca.id as ca_assetId FROM chain_assets AS ca
LEFT JOIN assets AS a ON a.assetId = ca.id AND a.chainId = ca.chainId AND a.metaId = :metaId
INNER JOIN currencies as currency ON currency.selected = 1
LEFT JOIN tokens AS t ON ca.symbol = t.tokenSymbol AND currency.id = t.currencyId
"""
private const val RETRIEVE_ASSETS_SQL_META_ID = """
SELECT *, ca.chainId as ca_chainId, ca.id as ca_assetId FROM chain_assets AS ca
LEFT JOIN assets AS a ON a.assetId = ca.id AND a.chainId = ca.chainId AND a.metaId = :metaId
INNER JOIN currencies as currency ON currency.selected = 1
LEFT JOIN tokens AS t ON ca.symbol = t.tokenSymbol AND currency.id = t.currencyId
WHERE ca.chainId || ':' || ca.id in (:joinedChainAndAssetIds)
"""
interface AssetReadOnlyCache {
fun observeSyncedAssets(metaId: Long): Flow<List<AssetWithToken>>
suspend fun getSyncedAssets(metaId: Long): List<AssetWithToken>
fun observeSupportedAssets(metaId: Long): Flow<List<AssetWithToken>>
suspend fun getSupportedAssets(metaId: Long): List<AssetWithToken>
fun observeAsset(metaId: Long, chainId: String, assetId: Int): Flow<AssetWithToken>
fun observeAssetOrNull(metaId: Long, chainId: String, assetId: Int): Flow<AssetWithToken?>
fun observeAssets(metaId: Long, assetIds: Collection<AssetAndChainId>): Flow<List<AssetWithToken>>
suspend fun getAssetWithToken(metaId: Long, chainId: String, assetId: Int): AssetWithToken?
suspend fun getAsset(metaId: Long, chainId: String, assetId: Int): AssetLocal?
suspend fun getAssetsInChain(metaId: Long, chainId: String): List<AssetLocal>
suspend fun getAllAssets(): List<AssetLocal>
suspend fun getAssetsById(id: Int): List<AssetLocal>
}
@Dao
abstract class AssetDao : AssetReadOnlyCache {
@Query(RETRIEVE_SYNCED_ACCOUNT_ASSETS_QUERY)
abstract override fun observeSyncedAssets(metaId: Long): Flow<List<AssetWithToken>>
@Query(RETRIEVE_SYNCED_ACCOUNT_ASSETS_QUERY)
abstract override suspend fun getSyncedAssets(metaId: Long): List<AssetWithToken>
@Query(RETRIEVE_SUPPORTED_ACCOUNT_ASSETS_QUERY)
abstract override fun observeSupportedAssets(metaId: Long): Flow<List<AssetWithToken>>
@Query(RETRIEVE_SUPPORTED_ACCOUNT_ASSETS_QUERY)
abstract override suspend fun getSupportedAssets(metaId: Long): List<AssetWithToken>
@Query(RETRIEVE_ASSET_SQL_META_ID)
abstract override fun observeAsset(metaId: Long, chainId: String, assetId: Int): Flow<AssetWithToken>
@Query(RETRIEVE_ASSET_SQL_META_ID)
abstract override fun observeAssetOrNull(metaId: Long, chainId: String, assetId: Int): Flow<AssetWithToken?>
@Query(RETRIEVE_ASSET_SQL_META_ID)
abstract override suspend fun getAssetWithToken(metaId: Long, chainId: String, assetId: Int): AssetWithToken?
@Query("SELECT * FROM assets WHERE metaId = :metaId AND chainId = :chainId AND assetId = :assetId")
abstract override suspend fun getAsset(metaId: Long, chainId: String, assetId: Int): AssetLocal?
@Query("SELECT * FROM assets WHERE metaId = :metaId AND chainId = :chainId")
abstract override suspend fun getAssetsInChain(metaId: Long, chainId: String): List<AssetLocal>
@Query("SELECT * FROM assets")
abstract override suspend fun getAllAssets(): List<AssetLocal>
@Query("SELECT * FROM assets WHERE assetId IS :id")
abstract override suspend fun getAssetsById(id: Int): List<AssetLocal>
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insertAsset(asset: AssetLocal)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insertAssets(assets: List<AssetLocal>)
@Delete(entity = AssetLocal::class)
abstract suspend fun clearAssets(assetIds: List<ClearAssetsParams>)
@Query(RETRIEVE_ASSETS_SQL_META_ID)
protected abstract fun observeJoinedAssets(metaId: Long, joinedChainAndAssetIds: Set<String>): Flow<List<AssetWithToken>>
override fun observeAssets(metaId: Long, assetIds: Collection<AssetAndChainId>): Flow<List<AssetWithToken>> {
return flowOfAll {
val joinedChainAndAssetIds = assetIds.mapToSet { (chainId, assetId) -> "$chainId:$assetId" }
observeJoinedAssets(metaId, joinedChainAndAssetIds)
}
}
}
class ClearAssetsParams(val chainId: String, val assetId: Int)
@@ -0,0 +1,23 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import io.novafoundation.nova.core_db.model.BrowserHostSettingsLocal
@Dao
abstract class BrowserHostSettingsDao {
@Query("SELECT * FROM browser_host_settings")
abstract suspend fun getBrowserAllHostSettings(): List<BrowserHostSettingsLocal>
@Query("SELECT * FROM browser_host_settings WHERE hostUrl = :host")
abstract suspend fun getBrowserHostSettings(host: String): BrowserHostSettingsLocal?
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insertBrowserHostSettings(settings: BrowserHostSettingsLocal)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insertBrowserHostSettings(settings: List<BrowserHostSettingsLocal>)
}
@@ -0,0 +1,44 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import io.novafoundation.nova.core_db.model.BrowserTabLocal
import kotlinx.coroutines.flow.Flow
@Dao
abstract class BrowserTabsDao {
@Query("SELECT id FROM browser_tabs WHERE metaId = :metaId")
abstract fun getTabIdsFor(metaId: Long): List<String>
@Query("SELECT * FROM browser_tabs WHERE metaId = :metaId ORDER BY creationTime DESC")
abstract fun observeTabsByMetaId(metaId: Long): Flow<List<BrowserTabLocal>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insertTab(tab: BrowserTabLocal)
@Transaction
open suspend fun removeTabsByMetaId(metaId: Long): List<String> {
val tabIds = getTabIdsFor(metaId)
removeTabsByIds(tabIds)
return tabIds
}
@Query("DELETE FROM browser_tabs WHERE id = :tabId")
abstract suspend fun removeTab(tabId: String)
@Query("DELETE FROM browser_tabs WHERE id IN (:tabIds)")
abstract suspend fun removeTabsByIds(tabIds: List<String>)
@Query("UPDATE browser_tabs SET pageName = :pageName, pageIconPath = :pageIconPath, pagePicturePath = :pagePicturePath WHERE id = :tabId")
abstract suspend fun updatePageSnapshot(tabId: String, pageName: String?, pageIconPath: String?, pagePicturePath: String?)
@Query("UPDATE browser_tabs SET currentUrl = :url WHERE id = :tabId")
abstract fun updateCurrentUrl(tabId: String, url: String)
@Query("UPDATE browser_tabs SET dappMetadata_iconLink = :dappIconUrl WHERE id = :tabId")
abstract fun updateKnownDAppMetadata(tabId: String, dappIconUrl: String?)
}
@@ -0,0 +1,50 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import io.novafoundation.nova.common.utils.CollectionDiffer
import io.novafoundation.nova.core_db.model.chain.AssetSourceLocal
import io.novafoundation.nova.core_db.model.chain.ChainAssetLocal
typealias FullAssetIdLocal = Pair<String, Int>
@Dao
abstract class ChainAssetDao {
@Transaction
open suspend fun updateAssets(diff: CollectionDiffer.Diff<ChainAssetLocal>) {
insertAssets(diff.newOrUpdated)
deleteChainAssets(diff.removed)
}
@Query("SELECT * FROM chain_assets WHERE id = :id AND chainId = :chainId")
abstract suspend fun getAsset(id: Int, chainId: String): ChainAssetLocal?
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insertAsset(asset: ChainAssetLocal)
@Query("SELECT * FROM chain_assets WHERE source = :source")
abstract suspend fun getAssetsBySource(source: AssetSourceLocal): List<ChainAssetLocal>
@Insert(onConflict = OnConflictStrategy.REPLACE)
protected abstract suspend fun insertAssets(assets: List<ChainAssetLocal>)
@Query("UPDATE chain_assets SET enabled = :enabled WHERE chainId = :chainId AND id = :assetId")
protected abstract suspend fun setAssetEnabled(enabled: Boolean, chainId: String, assetId: Int)
@Query("SELECT * FROM chain_assets WHERE enabled=1")
abstract suspend fun getEnabledAssets(): List<ChainAssetLocal>
@Update(entity = ChainAssetLocal::class)
abstract suspend fun setAssetsEnabled(params: List<SetAssetEnabledParams>)
@Delete
protected abstract suspend fun deleteChainAssets(assets: List<ChainAssetLocal>)
}
class SetAssetEnabledParams(val enabled: Boolean, val chainId: String, val id: Int)
@@ -0,0 +1,245 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import io.novafoundation.nova.common.utils.CollectionDiffer
import io.novafoundation.nova.core_db.model.chain.ChainAssetLocal
import io.novafoundation.nova.core_db.model.chain.ChainExplorerLocal
import io.novafoundation.nova.core_db.model.chain.ChainExternalApiLocal
import io.novafoundation.nova.core_db.model.chain.ChainLocal
import io.novafoundation.nova.core_db.model.chain.ChainNodeLocal
import io.novafoundation.nova.core_db.model.chain.ChainRuntimeInfoLocal
import io.novafoundation.nova.core_db.model.chain.JoinedChainInfo
import io.novafoundation.nova.core_db.model.chain.NodeSelectionPreferencesLocal
import kotlinx.coroutines.flow.Flow
@Dao
abstract class ChainDao {
@Transaction
open suspend fun applyDiff(
chainDiff: CollectionDiffer.Diff<ChainLocal>,
assetsDiff: CollectionDiffer.Diff<ChainAssetLocal>,
nodesDiff: CollectionDiffer.Diff<ChainNodeLocal>,
explorersDiff: CollectionDiffer.Diff<ChainExplorerLocal>,
externalApisDiff: CollectionDiffer.Diff<ChainExternalApiLocal>,
nodeSelectionPreferencesDiff: CollectionDiffer.Diff<NodeSelectionPreferencesLocal>
) {
deleteChains(chainDiff.removed)
deleteChainAssets(assetsDiff.removed)
deleteChainNodes(nodesDiff.removed)
deleteChainExplorers(explorersDiff.removed)
deleteExternalApis(externalApisDiff.removed)
deleteNodePreferences(nodeSelectionPreferencesDiff.removed)
addChains(chainDiff.added)
addChainAssets(assetsDiff.added)
addChainNodes(nodesDiff.added)
addChainExplorers(explorersDiff.added)
addExternalApis(externalApisDiff.added)
addNodePreferences(nodeSelectionPreferencesDiff.added)
updateChains(chainDiff.updated)
updateChainAssets(assetsDiff.updated)
updateChainNodes(nodesDiff.updated)
updateChainExplorers(explorersDiff.updated)
updateExternalApis(externalApisDiff.updated)
updateNodePreferences(nodeSelectionPreferencesDiff.added)
}
@Transaction
open suspend fun addChainOrUpdate(
chain: ChainLocal,
assets: List<ChainAssetLocal>,
nodes: List<ChainNodeLocal>,
explorers: List<ChainExplorerLocal>,
externalApis: List<ChainExternalApiLocal>,
nodeSelectionPreferences: NodeSelectionPreferencesLocal
) {
addChainOrUpdate(chain)
addChainAssetsOrUpdate(assets)
addChainNodesOrUpdate(nodes)
addChainExplorersOrUpdate(explorers)
addExternalApisOrUpdate(externalApis)
addNodePreferencesOrUpdate(nodeSelectionPreferences)
}
@Transaction
open suspend fun editChain(
chainId: String,
assetId: Int,
chainName: String,
symbol: String,
explorer: ChainExplorerLocal?,
priceId: String?
) {
updateChainName(chainId, chainName)
updateAssetToken(chainId, assetId, symbol, priceId)
addChainExplorersOrUpdate(listOfNotNull(explorer))
}
// ------ Delete --------
@Delete
protected abstract suspend fun deleteChains(chains: List<ChainLocal>)
@Delete
protected abstract suspend fun deleteChainNodes(nodes: List<ChainNodeLocal>)
@Delete
protected abstract suspend fun deleteChainAssets(assets: List<ChainAssetLocal>)
@Delete
protected abstract suspend fun deleteChainExplorers(explorers: List<ChainExplorerLocal>)
@Delete
protected abstract suspend fun deleteExternalApis(apis: List<ChainExternalApiLocal>)
@Delete
protected abstract suspend fun deleteNodePreferences(apis: List<NodeSelectionPreferencesLocal>)
// ------ Add --------
@Insert(onConflict = OnConflictStrategy.ABORT)
protected abstract suspend fun addChains(chains: List<ChainLocal>)
@Insert(onConflict = OnConflictStrategy.ABORT)
protected abstract suspend fun addChainNodes(nodes: List<ChainNodeLocal>)
@Insert(onConflict = OnConflictStrategy.ABORT)
protected abstract suspend fun addChainAssets(assets: List<ChainAssetLocal>)
@Insert(onConflict = OnConflictStrategy.ABORT)
protected abstract suspend fun addChainExplorers(explorers: List<ChainExplorerLocal>)
@Insert(onConflict = OnConflictStrategy.ABORT)
protected abstract suspend fun addExternalApis(apis: List<ChainExternalApiLocal>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun addChainOrUpdate(node: ChainLocal)
@Insert(onConflict = OnConflictStrategy.ABORT)
abstract suspend fun addChainNode(node: ChainNodeLocal)
@Insert(onConflict = OnConflictStrategy.ABORT)
protected abstract suspend fun addNodePreferences(model: List<NodeSelectionPreferencesLocal>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
protected abstract suspend fun addNodePreferencesOrUpdate(model: NodeSelectionPreferencesLocal)
@Insert(onConflict = OnConflictStrategy.REPLACE)
protected abstract suspend fun addChainNodesOrUpdate(nodes: List<ChainNodeLocal>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
protected abstract suspend fun addChainAssetsOrUpdate(assets: List<ChainAssetLocal>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
protected abstract suspend fun addChainExplorersOrUpdate(explorers: List<ChainExplorerLocal>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
protected abstract suspend fun addExternalApisOrUpdate(apis: List<ChainExternalApiLocal>)
// ------ Update -----
@Update
protected abstract suspend fun updateChains(chains: List<ChainLocal>)
@Update
protected abstract suspend fun updateChainNodes(nodes: List<ChainNodeLocal>)
@Update
protected abstract suspend fun updateChainAssets(assets: List<ChainAssetLocal>)
@Update
protected abstract suspend fun updateChainExplorers(explorers: List<ChainExplorerLocal>)
@Update
protected abstract suspend fun updateExternalApis(apis: List<ChainExternalApiLocal>)
@Update
protected abstract suspend fun updateNodePreferences(apis: List<NodeSelectionPreferencesLocal>)
// ------- Queries ------
@Query("SELECT * FROM chains")
@Transaction
abstract suspend fun getJoinChainInfo(): List<JoinedChainInfo>
@Query("SELECT id FROM chains")
@Transaction
abstract suspend fun getAllChainIds(): List<String>
@Query("SELECT * FROM chains")
@Transaction
abstract fun joinChainInfoFlow(): Flow<List<JoinedChainInfo>>
@Query("SELECT orderId FROM chain_nodes WHERE chainId = :chainId ORDER BY orderId DESC LIMIT 1")
abstract suspend fun getLastChainNodeOrderId(chainId: String): Int
@Query("SELECT EXISTS(SELECT * FROM chains WHERE id = :chainId)")
abstract suspend fun chainExists(chainId: String): Boolean
@Query("SELECT * FROM chain_runtimes WHERE chainId = :chainId")
abstract suspend fun runtimeInfo(chainId: String): ChainRuntimeInfoLocal?
@Query("SELECT * FROM chain_runtimes")
abstract suspend fun allRuntimeInfos(): List<ChainRuntimeInfoLocal>
@Query("UPDATE chain_runtimes SET syncedVersion = :syncedVersion, localMigratorVersion = :localMigratorVersion WHERE chainId = :chainId")
abstract suspend fun updateSyncedRuntimeVersion(chainId: String, syncedVersion: Int, localMigratorVersion: Int)
@Query("UPDATE chains SET connectionState = :connectionState WHERE id = :chainId")
abstract suspend fun setConnectionState(chainId: String, connectionState: ChainLocal.ConnectionStateLocal)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun setNodePreferences(model: NodeSelectionPreferencesLocal)
@Transaction
open suspend fun updateRemoteRuntimeVersionIfChainExists(
chainId: String,
runtimeVersion: Int,
transactionVersion: Int,
) {
if (!chainExists(chainId)) return
if (isRuntimeInfoExists(chainId)) {
updateRemoteRuntimeVersionUnsafe(chainId, runtimeVersion, transactionVersion)
} else {
val runtimeInfoLocal = ChainRuntimeInfoLocal(
chainId,
syncedVersion = 0,
remoteVersion = runtimeVersion,
transactionVersion = transactionVersion,
localMigratorVersion = 1
)
insertRuntimeInfo(runtimeInfoLocal)
}
}
@Query("UPDATE chain_nodes SET url = :newUrl, name = :name WHERE chainId = :chainId AND url = :oldUrl")
abstract suspend fun updateChainNode(chainId: String, oldUrl: String, newUrl: String, name: String)
@Query("UPDATE chain_runtimes SET remoteVersion = :remoteVersion, transactionVersion = :transactionVersion WHERE chainId = :chainId")
protected abstract suspend fun updateRemoteRuntimeVersionUnsafe(chainId: String, remoteVersion: Int, transactionVersion: Int)
@Query("SELECT EXISTS (SELECT * FROM chain_runtimes WHERE chainId = :chainId)")
protected abstract suspend fun isRuntimeInfoExists(chainId: String): Boolean
@Insert(onConflict = OnConflictStrategy.REPLACE)
protected abstract suspend fun insertRuntimeInfo(runtimeInfoLocal: ChainRuntimeInfoLocal)
@Query("UPDATE chains SET name = :name WHERE id = :chainId")
abstract suspend fun updateChainName(chainId: String, name: String)
@Query("UPDATE chain_assets SET symbol = :symbol, priceId = :priceId WHERE chainId = :chainId and id == :assetId")
abstract suspend fun updateAssetToken(chainId: String, assetId: Int, symbol: String, priceId: String?)
@Query("DELETE FROM chains WHERE id = :chainId")
abstract suspend fun deleteChain(chainId: String)
@Query("DELETE FROM chain_nodes WHERE chainId = :chainId AND url = :url")
abstract suspend fun deleteNode(chainId: String, url: String)
}
@@ -0,0 +1,61 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import io.novafoundation.nova.core_db.model.CoinPriceLocal
@Dao
interface CoinPriceDao {
@Query(
"""
SELECT * FROM coin_prices
WHERE priceId = :priceId AND currencyId = :currencyId
AND timestamp <= :timestamp
ORDER BY timestamp DESC LIMIT 1
"""
)
suspend fun getFloorCoinPriceAtTime(priceId: String, currencyId: String, timestamp: Long): CoinPriceLocal?
@Query(
"""
SELECT EXISTS(
SELECT * FROM coin_prices
WHERE priceId = :priceId AND currencyId = :currencyId
AND timestamp >= :timestamp
ORDER BY timestamp ASC LIMIT 1
)
"""
)
suspend fun hasCeilingCoinPriceAtTime(priceId: String, currencyId: String, timestamp: Long): Boolean
@Query(
"""
SELECT * FROM coin_prices
WHERE priceId = :priceId AND currencyId = :currencyId
AND timestamp BETWEEN :fromTimestamp AND :toTimestamp
ORDER BY timestamp ASC
"""
)
suspend fun getCoinPriceRange(priceId: String, currencyId: String, fromTimestamp: Long, toTimestamp: Long): List<CoinPriceLocal>
@Transaction
suspend fun updateCoinPrices(priceId: String, currencyId: String, coinRates: List<CoinPriceLocal>) {
deleteCoinPrices(priceId, currencyId)
setCoinPrices(coinRates)
}
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun setCoinPrices(coinPrices: List<CoinPriceLocal>)
@Query(
"""
DELETE FROM coin_prices
WHERE priceId = :priceId AND currencyId = :currencyId
"""
)
fun deleteCoinPrices(priceId: String, currencyId: String)
}
@@ -0,0 +1,49 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import io.novafoundation.nova.common.utils.CollectionDiffer
import io.novafoundation.nova.core_db.model.ContributionLocal
import kotlinx.coroutines.flow.Flow
@Dao
abstract class ContributionDao {
@Transaction
open suspend fun updateContributions(contributions: CollectionDiffer.Diff<ContributionLocal>) {
insertContributions(contributions.added)
updateContributions(contributions.updated)
deleteContributions(contributions.removed)
}
@Query("SELECT * FROM contributions WHERE metaId = :metaId AND chainId = :chainId AND assetId = :assetId")
abstract fun observeContributions(metaId: Long, chainId: String, assetId: Int): Flow<List<ContributionLocal>>
@Query("SELECT * FROM contributions WHERE metaId = :metaId")
abstract fun observeContributions(metaId: Long): Flow<List<ContributionLocal>>
@Query("SELECT * FROM contributions WHERE metaId = :metaId AND chainId = :chainId AND assetId = :assetId AND sourceId = :sourceId")
abstract suspend fun getContributions(metaId: Long, chainId: String, assetId: Int, sourceId: String): List<ContributionLocal>
@Query("DELETE FROM contributions WHERE chainId = :chainId AND assetId = :assetId")
abstract suspend fun deleteContributions(chainId: String, assetId: Int)
@Delete
protected abstract suspend fun deleteContributions(contributions: List<ContributionLocal>)
@Update(onConflict = OnConflictStrategy.REPLACE)
protected abstract suspend fun updateContributions(contributions: List<ContributionLocal>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
protected abstract suspend fun insertContributions(contributions: List<ContributionLocal>)
@Delete(entity = ContributionLocal::class)
abstract suspend fun deleteAssetContributions(params: List<DeleteAssetContributionsParams>)
}
class DeleteAssetContributionsParams(val chainId: String, val assetId: Int)
@@ -0,0 +1,61 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import io.novafoundation.nova.common.utils.CollectionDiffer
import io.novafoundation.nova.core_db.model.CurrencyLocal
import kotlinx.coroutines.flow.Flow
private const val RETRIEVE_CURRENCIES = "SELECT * FROM currencies"
private const val RETRIEVE_SELECTED_CURRENCY = "SELECT * FROM currencies WHERE selected = 1"
@Dao
abstract class CurrencyDao {
@Transaction
open suspend fun updateCurrencies(currencies: CollectionDiffer.Diff<CurrencyLocal>) {
deleteCurrencies(currencies.removed)
insertCurrencies(currencies.added)
updateCurrencies(currencies.updated)
if (getSelectedCurrency() == null) {
selectCurrency(0)
}
}
@Query("SELECT * FROM currencies WHERE id = 0")
abstract fun getFirst(): CurrencyLocal
@Query(RETRIEVE_CURRENCIES)
abstract suspend fun getCurrencies(): List<CurrencyLocal>
@Query(RETRIEVE_CURRENCIES)
abstract fun observeCurrencies(): Flow<List<CurrencyLocal>>
@Query(RETRIEVE_SELECTED_CURRENCY)
abstract suspend fun getSelectedCurrency(): CurrencyLocal?
@Query(RETRIEVE_SELECTED_CURRENCY)
abstract fun observeSelectCurrency(): Flow<CurrencyLocal>
@Query("UPDATE currencies SET selected = (id = :currencyId)")
abstract fun selectCurrency(currencyId: Int)
@Insert(onConflict = OnConflictStrategy.ABORT)
abstract suspend fun insert(currency: CurrencyLocal)
@Delete
protected abstract suspend fun deleteCurrencies(currencies: List<CurrencyLocal>)
@Insert(onConflict = OnConflictStrategy.ABORT)
protected abstract suspend fun insertCurrencies(currencies: List<CurrencyLocal>)
@Update
protected abstract suspend fun updateCurrencies(currencies: List<CurrencyLocal>)
}
@@ -0,0 +1,24 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import io.novafoundation.nova.core_db.model.DappAuthorizationLocal
import kotlinx.coroutines.flow.Flow
@Dao
interface DappAuthorizationDao {
@Query("SELECT * FROM dapp_authorizations WHERE baseUrl = :baseUrl AND metaId = :metaId")
suspend fun getAuthorization(baseUrl: String, metaId: Long): DappAuthorizationLocal?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun updateAuthorization(dappAuthorization: DappAuthorizationLocal)
@Query("UPDATE dapp_authorizations SET authorized = 0 WHERE baseUrl = :baseUrl AND metaId = :metaId")
suspend fun removeAuthorization(baseUrl: String, metaId: Long)
@Query("SELECT * FROM dapp_authorizations WHERE metaId = :metaId")
fun observeAuthorizations(metaId: Long): Flow<List<DappAuthorizationLocal>>
}
@@ -0,0 +1,66 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import io.novafoundation.nova.core_db.model.AggregatedExternalBalanceLocal
import io.novafoundation.nova.core_db.model.ExternalBalanceLocal
import io.novasama.substrate_sdk_android.hash.isPositive
import kotlinx.coroutines.flow.Flow
@Dao
interface ExternalBalanceDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertExternalBalance(externalBalance: ExternalBalanceLocal)
@Query("DELETE FROM externalBalances WHERE metaId = :metaId AND chainId = :chainId AND assetId = :assetId AND type = :type AND subtype = :subtype")
suspend fun removeExternalBalance(
metaId: Long,
chainId: String,
assetId: Int,
type: ExternalBalanceLocal.Type,
subtype: String?,
)
@Delete(entity = ExternalBalanceLocal::class)
suspend fun deleteAssetExternalBalances(params: List<ExternalBalanceAssetDeleteParams>)
@Query(
"""
SELECT chainId, assetId, type, SUM(amount) as aggregatedAmount
FROM externalBalances
WHERE metaId = :metaId
GROUP BY chainId, assetId, type
"""
)
fun observeAggregatedExternalBalances(metaId: Long): Flow<List<AggregatedExternalBalanceLocal>>
@Query(
"""
SELECT chainId, assetId, type, SUM(amount) as aggregatedAmount
FROM externalBalances
WHERE metaId = :metaId AND chainId = :chainId AND assetId = :assetId
GROUP BY type
"""
)
fun observeChainAggregatedExternalBalances(metaId: Long, chainId: String, assetId: Int): Flow<List<AggregatedExternalBalanceLocal>>
}
suspend fun ExternalBalanceDao.updateExternalBalance(externalBalance: ExternalBalanceLocal) {
if (externalBalance.amount.isPositive()) {
insertExternalBalance(externalBalance)
} else {
removeExternalBalance(
metaId = externalBalance.metaId,
chainId = externalBalance.chainId,
assetId = externalBalance.assetId,
type = externalBalance.type,
subtype = externalBalance.subtype
)
}
}
class ExternalBalanceAssetDeleteParams(val chainId: String, val assetId: Int)
@@ -0,0 +1,34 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import io.novafoundation.nova.core_db.model.FavouriteDAppLocal
import kotlinx.coroutines.flow.Flow
@Dao
interface FavouriteDAppsDao {
@Query("SELECT * FROM favourite_dapps")
fun observeFavouriteDApps(): Flow<List<FavouriteDAppLocal>>
@Query("SELECT * FROM favourite_dapps")
suspend fun getFavouriteDApps(): List<FavouriteDAppLocal>
@Query("SELECT EXISTS(SELECT * FROM favourite_dapps WHERE url = :dAppUrl)")
fun observeIsFavourite(dAppUrl: String): Flow<Boolean>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertFavouriteDApp(dApp: FavouriteDAppLocal)
@Query("DELETE FROM favourite_dapps WHERE url = :dAppUrl")
suspend fun deleteFavouriteDApp(dAppUrl: String)
@Update(onConflict = OnConflictStrategy.REPLACE)
suspend fun updateFavourites(dapps: List<FavouriteDAppLocal>)
@Query("SELECT MAX(orderingIndex) FROM favourite_dapps")
suspend fun getMaxOrderingIndex(): Int
}
@@ -0,0 +1,32 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import io.novafoundation.nova.core_db.model.GiftLocal
import kotlinx.coroutines.flow.Flow
@Dao
interface GiftsDao {
@Query("SELECT * from gifts WHERE id = :id")
suspend fun getGiftById(id: Long): GiftLocal
@Query("SELECT * from gifts")
suspend fun getAllGifts(): List<GiftLocal>
@Query("SELECT * from gifts WHERE id = :id")
fun observeGiftById(id: Long): Flow<GiftLocal>
@Query("SELECT * from gifts")
fun observeAllGifts(): Flow<List<GiftLocal>>
@Query("SELECT * from gifts WHERE chainId = :chainId AND assetId = :assetId")
fun observeGiftsByAsset(chainId: String, assetId: Int): Flow<List<GiftLocal>>
@Insert
suspend fun createNewGift(giftLocal: GiftLocal): Long
@Query("UPDATE gifts SET status = :status WHERE id = :id")
suspend fun setGiftState(id: Long, status: GiftLocal.Status)
}
@@ -0,0 +1,41 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import io.novafoundation.nova.common.utils.CollectionDiffer
import io.novafoundation.nova.core_db.model.GovernanceDAppLocal
import kotlinx.coroutines.flow.Flow
@Dao
abstract class GovernanceDAppsDao {
@Transaction
open suspend fun update(newDapps: List<GovernanceDAppLocal>) {
val oldDapps = getAll()
val dappDiffs = CollectionDiffer.findDiff(newDapps, oldDapps, false)
deleteDapps(dappDiffs.removed)
updateDapps(dappDiffs.updated)
insertDapps(dappDiffs.added)
}
@Query("SELECT * FROM governance_dapps")
abstract fun getAll(): List<GovernanceDAppLocal>
@Query("SELECT * FROM governance_dapps WHERE chainId = :chainId")
abstract fun observeChainDapps(chainId: String): Flow<List<GovernanceDAppLocal>>
@Delete
abstract suspend fun deleteDapps(dapps: List<GovernanceDAppLocal>)
@Insert(onConflict = OnConflictStrategy.ABORT)
abstract suspend fun insertDapps(dapps: List<GovernanceDAppLocal>)
@Update
abstract suspend fun updateDapps(dapps: List<GovernanceDAppLocal>)
}
@@ -0,0 +1,37 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import io.novafoundation.nova.core_db.model.BalanceHoldLocal
import kotlinx.coroutines.flow.Flow
@Dao
abstract class HoldsDao {
@Transaction
open suspend fun updateHolds(
holds: List<BalanceHoldLocal>,
metaId: Long,
chainId: String,
chainAssetId: Int
) {
deleteHolds(metaId, chainId, chainAssetId)
insert(holds)
}
@Insert(onConflict = OnConflictStrategy.REPLACE)
protected abstract fun insert(holds: List<BalanceHoldLocal>)
@Query("DELETE FROM holds WHERE metaId = :metaId AND chainId = :chainId AND assetId = :chainAssetId")
protected abstract fun deleteHolds(metaId: Long, chainId: String, chainAssetId: Int)
@Query("SELECT * FROM holds WHERE metaId = :metaId")
abstract fun observeHoldsForMetaAccount(metaId: Long): Flow<List<BalanceHoldLocal>>
@Query("SELECT * FROM holds WHERE metaId = :metaId AND chainId = :chainId AND assetId = :chainAssetId")
abstract fun observeBalanceHolds(metaId: Long, chainId: String, chainAssetId: Int): Flow<List<BalanceHoldLocal>>
}
@@ -0,0 +1,53 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import io.novafoundation.nova.core_db.model.BalanceLockLocal
import kotlinx.coroutines.flow.Flow
@Dao
abstract class LockDao {
@Transaction
open suspend fun updateLocks(
locks: List<BalanceLockLocal>,
metaId: Long,
chainId: String,
chainAssetId: Int
) {
deleteLocks(metaId, chainId, chainAssetId)
insert(locks)
}
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun insert(locks: List<BalanceLockLocal>)
@Query("DELETE FROM locks WHERE metaId = :metaId AND chainId = :chainId AND assetId = :chainAssetId")
abstract fun deleteLocks(metaId: Long, chainId: String, chainAssetId: Int)
@Query("SELECT * FROM locks WHERE metaId = :metaId")
abstract fun observeLocksForMetaAccount(metaId: Long): Flow<List<BalanceLockLocal>>
@Query("SELECT * FROM locks WHERE metaId = :metaId AND chainId = :chainId AND assetId = :chainAssetId")
abstract fun observeBalanceLocks(metaId: Long, chainId: String, chainAssetId: Int): Flow<List<BalanceLockLocal>>
@Query("SELECT * FROM locks WHERE metaId = :metaId AND chainId = :chainId AND assetId = :chainAssetId")
abstract suspend fun getBalanceLocks(metaId: Long, chainId: String, chainAssetId: Int): List<BalanceLockLocal>
@Query(
"""
SELECT * FROM locks
WHERE metaId = :metaId AND chainId = :chainId AND assetId = :chainAssetId
ORDER BY amount DESC
LIMIT 1
"""
)
abstract suspend fun getBiggestBalanceLock(metaId: Long, chainId: String, chainAssetId: Int): BalanceLockLocal?
@Query("SELECT * FROM locks WHERE metaId = :metaId AND chainId = :chainId AND assetId = :chainAssetId AND type = :lockId")
abstract fun observeBalanceLock(metaId: Long, chainId: String, chainAssetId: Int, lockId: String): Flow<BalanceLockLocal?>
}
@@ -0,0 +1,309 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal
import io.novafoundation.nova.core_db.model.chain.account.MetaAccountIdWithType
import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal
import io.novafoundation.nova.core_db.model.chain.account.MetaAccountPositionUpdate
import io.novafoundation.nova.core_db.model.chain.account.ProxyAccountLocal
import io.novafoundation.nova.core_db.model.chain.account.RelationJoinedMetaAccountInfo
import io.novasama.substrate_sdk_android.runtime.AccountId
import kotlinx.coroutines.flow.Flow
import org.intellij.lang.annotations.Language
import java.math.BigDecimal
import java.math.BigInteger
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
/**
* Fetch meta account where either
* 1. chain account for specified chain is present and its accountId matches
* 2. chain account for specified is missing but one of base accountIds matches
*
* Note that if both chain account and base accounts are present than we should filter out entries where chain account matches but base accounts does not
*/
@Language("RoomSql")
private const val FIND_BY_ADDRESS_WHERE_CLAUSE = """
LEFT JOIN chain_accounts as c ON m.id = c.metaId AND c.chainId = :chainId
WHERE
(c.accountId IS NOT NULL AND c.accountId = :accountId)
OR (c.accountId IS NULL AND (substrateAccountId = :accountId OR ethereumAddress = :accountId))
ORDER BY (CASE WHEN isSelected THEN 0 ELSE 1 END)
"""
@Language("RoomSql")
private const val FIND_ACCOUNT_BY_ADDRESS_QUERY = """
SELECT * FROM meta_accounts as m
$FIND_BY_ADDRESS_WHERE_CLAUSE
"""
@Language("RoomSql")
private const val FIND_NAME_BY_ADDRESS_QUERY = """
SELECT name FROM meta_accounts as m
$FIND_BY_ADDRESS_WHERE_CLAUSE
"""
@Language("RoomSql")
private const val META_ACCOUNTS_WITH_BALANCE_PART = """
SELECT
m.id,
a.freeInPlanks,
a.reservedInPlanks,
(SELECT SUM(amountInPlanks) FROM contributions WHERE chainId = a.chainId AND assetId = a.assetId AND metaId = m.id) offChainBalance,
ca.precision,
t.rate
FROM meta_accounts as m
INNER JOIN assets as a ON a.metaId = m.id
INNER JOIN chain_assets AS ca ON a.assetId = ca.id AND a.chainId = ca.chainId
INNER JOIN currencies as currency ON currency.selected = 1
INNER JOIN tokens as t ON t.tokenSymbol = ca.symbol AND t.currencyId = currency.id
"""
@Language("RoomSql")
private const val META_ACCOUNTS_WITH_BALANCE_QUERY = """
$META_ACCOUNTS_WITH_BALANCE_PART
ORDER BY m.position
"""
@Language("RoomSql")
private const val META_ACCOUNT_WITH_BALANCE_QUERY = """
$META_ACCOUNTS_WITH_BALANCE_PART
WHERE m.id == :metaId
"""
@Dao
interface MetaAccountDao {
@Transaction
suspend fun insertProxiedMetaAccount(
metaAccount: MetaAccountLocal,
chainAccount: (metaId: Long) -> ChainAccountLocal,
proxyAccount: (metaId: Long) -> ProxyAccountLocal
): Long {
val metaId = insertMetaAccount(metaAccount)
insertChainAccount(chainAccount(metaId))
insertProxy(proxyAccount(metaId))
return metaId
}
@Transaction
suspend fun runInTransaction(action: suspend () -> Unit) {
action()
}
@Insert
suspend fun insertMetaAccount(metaAccount: MetaAccountLocal): Long
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun updateMetaAccount(metaAccount: MetaAccountLocal): Long
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertChainAccount(chainAccount: ChainAccountLocal)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertChainAccounts(chainAccounts: List<ChainAccountLocal>)
@Delete
suspend fun deleteChainAccounts(chainAccounts: List<ChainAccountLocal>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertProxy(proxyLocal: ProxyAccountLocal)
@Query("SELECT * FROM meta_accounts")
suspend fun getMetaAccounts(): List<MetaAccountLocal>
@Query("SELECT * FROM meta_accounts WHERE id IN (:metaIds)")
suspend fun getMetaAccountsByIds(metaIds: List<Long>): List<RelationJoinedMetaAccountInfo>
@Query("SELECT * FROM meta_accounts WHERE id = :id")
suspend fun getMetaAccount(id: Long): MetaAccountLocal?
@Query("SELECT COUNT(*) FROM meta_accounts WHERE status = :status")
@Transaction
suspend fun getMetaAccountsQuantityByStatus(status: MetaAccountLocal.Status): Int
@Query("SELECT * FROM meta_accounts WHERE status = :status")
@Transaction
suspend fun getMetaAccountsByStatus(status: MetaAccountLocal.Status): List<RelationJoinedMetaAccountInfo>
@Query("SELECT * FROM meta_accounts")
@Transaction
suspend fun getFullMetaAccounts(): List<RelationJoinedMetaAccountInfo>
@Query("SELECT * FROM meta_accounts")
fun getJoinedMetaAccountsInfoFlow(): Flow<List<RelationJoinedMetaAccountInfo>>
@Query("SELECT * FROM meta_accounts WHERE status = :status")
fun getJoinedMetaAccountsInfoByStatusFlow(status: MetaAccountLocal.Status): Flow<List<RelationJoinedMetaAccountInfo>>
@Query("SELECT id FROM meta_accounts WHERE status = :status")
fun getMetaAccountsIdsByStatus(status: MetaAccountLocal.Status): List<Long>
@Query(META_ACCOUNTS_WITH_BALANCE_QUERY)
fun metaAccountsWithBalanceFlow(): Flow<List<MetaAccountWithBalanceLocal>>
@Query(META_ACCOUNT_WITH_BALANCE_QUERY)
fun metaAccountWithBalanceFlow(metaId: Long): Flow<List<MetaAccountWithBalanceLocal>>
@Query("UPDATE meta_accounts SET isSelected = (id = :metaId)")
suspend fun selectMetaAccount(metaId: Long)
@Update(entity = MetaAccountLocal::class)
suspend fun updatePositions(updates: List<MetaAccountPositionUpdate>)
@Query("SELECT * FROM meta_accounts WHERE id = :metaId")
@Transaction
suspend fun getJoinedMetaAccountInfo(metaId: Long): RelationJoinedMetaAccountInfo
@Query("SELECT type FROM meta_accounts WHERE id = :metaId")
suspend fun getMetaAccountType(metaId: Long): MetaAccountLocal.Type?
@Query("SELECT * FROM meta_accounts WHERE isSelected = 1")
@Transaction
fun selectedMetaAccountInfoFlow(): Flow<RelationJoinedMetaAccountInfo?>
@Query("SELECT * FROM meta_accounts WHERE id = :metaId")
@Transaction
fun metaAccountInfoFlow(metaId: Long): Flow<RelationJoinedMetaAccountInfo?>
@Query("SELECT EXISTS ($FIND_ACCOUNT_BY_ADDRESS_QUERY)")
fun isMetaAccountExists(accountId: AccountId, chainId: String): Boolean
@Query(FIND_ACCOUNT_BY_ADDRESS_QUERY)
@Transaction
fun getMetaAccountInfo(accountId: AccountId, chainId: String): RelationJoinedMetaAccountInfo?
@Query(FIND_NAME_BY_ADDRESS_QUERY)
fun metaAccountNameFor(accountId: AccountId, chainId: String): String?
@Query("UPDATE meta_accounts SET name = :newName WHERE id = :metaId")
suspend fun updateName(metaId: Long, newName: String)
@Query(
"""
WITH RECURSIVE accounts_to_delete AS (
SELECT id, parentMetaId, type FROM meta_accounts WHERE id IN (:metaIds)
UNION ALL
SELECT m.id, m.parentMetaId, m.type
FROM meta_accounts m
JOIN accounts_to_delete r ON m.parentMetaId = r.id
)
SELECT id, type FROM accounts_to_delete
"""
)
suspend fun findAffectedMetaIdsOnDelete(metaIds: List<Long>): List<MetaAccountIdWithType>
@Query("DELETE FROM meta_accounts WHERE id IN (:ids)")
suspend fun deleteByIds(ids: List<Long>)
@Transaction
suspend fun delete(vararg metaId: Long): List<MetaAccountIdWithType> {
val affectingMetaAccounts = findAffectedMetaIdsOnDelete(metaId.toList())
if (affectingMetaAccounts.isNotEmpty()) {
val ids = affectingMetaAccounts.map { it.id }
deleteByIds(ids)
}
return affectingMetaAccounts
}
@Transaction
suspend fun delete(metaIds: List<Long>): List<MetaAccountIdWithType> {
return delete(*metaIds.toLongArray())
}
@Query("SELECT COALESCE(MAX(position), 0) + 1 FROM meta_accounts")
suspend fun nextAccountPosition(): Int
@Query("SELECT * FROM meta_accounts WHERE isSelected = 1")
suspend fun selectedMetaAccount(): RelationJoinedMetaAccountInfo?
@Query("SELECT EXISTS(SELECT id FROM meta_accounts WHERE type = :type)")
fun hasMetaAccountsCountOfTypeFlow(type: MetaAccountLocal.Type): Flow<Boolean>
@Query("SELECT * FROM meta_accounts WHERE type = :type")
fun observeMetaAccountsByTypeFlow(type: MetaAccountLocal.Type): Flow<List<RelationJoinedMetaAccountInfo>>
@Query(
"""
DELETE FROM meta_accounts
WHERE id IN (
SELECT proxiedMetaId
FROM proxy_accounts
WHERE chainId = :chainId
)
"""
)
fun deleteProxiedMetaAccountsByChain(chainId: String)
@Transaction
suspend fun insertMetaAndChainAccounts(
metaAccount: MetaAccountLocal,
createChainAccounts: suspend (metaId: Long) -> List<ChainAccountLocal>
): Long {
val metaId = insertMetaAccount(metaAccount)
insertChainAccounts(createChainAccounts(metaId))
return metaId
}
@Query("SELECT EXISTS(SELECT * FROM meta_accounts WHERE status = :status)")
suspend fun hasMetaAccountsByStatus(status: MetaAccountLocal.Status): Boolean
@Query("SELECT EXISTS(SELECT * FROM meta_accounts WHERE type = :type)")
suspend fun hasMetaAccountsByType(type: MetaAccountLocal.Type): Boolean
@Query("SELECT EXISTS(SELECT * FROM meta_accounts WHERE id IN (:metaIds) AND type = :type)")
suspend fun hasMetaAccountsByType(metaIds: Set<Long>, type: MetaAccountLocal.Type): Boolean
@Query("UPDATE meta_accounts SET status = :status WHERE id IN (:metaIds)")
suspend fun changeAccountsStatus(metaIds: List<Long>, status: MetaAccountLocal.Status)
@Query("DELETE FROM meta_accounts WHERE status = :status ")
fun removeMetaAccountsByStatus(status: MetaAccountLocal.Status)
}
suspend inline fun <T : Any> MetaAccountDao.withTransaction(crossinline action: suspend () -> T): T {
var result: T? = null
runInTransaction {
result = action()
}
return result!!
}
@OptIn(ExperimentalContracts::class)
suspend fun MetaAccountDao.updateMetaAccount(metaId: Long, updateClosure: (MetaAccountLocal) -> MetaAccountLocal) {
contract {
callsInPlace(updateClosure, InvocationKind.EXACTLY_ONCE)
}
val metaAccount = requireNotNull(getMetaAccount(metaId)) {
"Meta account $metaId was not found"
}
val updated = updateClosure(metaAccount)
require(updated.id == metaId) {
"Cannot modify metaId"
}
updateMetaAccount(updated)
}
class MetaAccountWithBalanceLocal(
val id: Long,
val freeInPlanks: BigInteger,
val reservedInPlanks: BigInteger,
val offChainBalance: BigInteger?,
val precision: Int,
val rate: BigDecimal?
)
@@ -0,0 +1,21 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import io.novafoundation.nova.core_db.model.MultisigOperationCallLocal
import kotlinx.coroutines.flow.Flow
@Dao
abstract class MultisigOperationsDao {
@Query("SELECT * FROM multisig_operation_call")
abstract fun observeOperations(): Flow<List<MultisigOperationCallLocal>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insertOperation(operation: MultisigOperationCallLocal)
@Query("DELETE FROM multisig_operation_call WHERE metaId = :metaId AND chainId = :chainId AND callHash NOT IN (:excludedCallHashes)")
abstract fun removeOperationsExclude(metaId: Long, chainId: String, excludedCallHashes: List<String>)
}
@@ -0,0 +1,65 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import io.novafoundation.nova.common.utils.CollectionDiffer
import io.novafoundation.nova.core_db.model.NftLocal
import kotlinx.coroutines.flow.Flow
@Dao
abstract class NftDao {
@Query("SELECT * FROM nfts WHERE metaId = :metaId")
abstract fun nftsFlow(metaId: Long): Flow<List<NftLocal>>
@Query("SELECT * FROM nfts WHERE metaId = :metaId AND type = :type AND chainId = :chainId")
abstract suspend fun getNfts(chainId: String, metaId: Long, type: NftLocal.Type): List<NftLocal>
@Delete
protected abstract suspend fun deleteNfts(nfts: List<NftLocal>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
protected abstract suspend fun insertNfts(nfts: List<NftLocal>)
@Update
protected abstract suspend fun updateNft(nft: NftLocal)
@Query("SELECT * FROM nfts WHERE identifier = :nftIdentifier")
abstract suspend fun getNft(nftIdentifier: String): NftLocal
@Query("SELECT type FROM nfts WHERE identifier = :nftIdentifier")
abstract suspend fun getNftType(nftIdentifier: String): NftLocal.Type
@Query("UPDATE nfts SET wholeDetailsLoaded = 1 WHERE identifier = :nftIdentifier")
abstract suspend fun markFullSynced(nftIdentifier: String)
@Transaction
open suspend fun insertNftsDiff(
nftType: NftLocal.Type,
chainId: String,
metaId: Long,
newNfts: List<NftLocal>,
forceOverwrite: Boolean
) {
val oldNfts = getNfts(chainId, metaId, nftType)
val diff = CollectionDiffer.findDiff(newNfts, oldNfts, forceUseNewItems = forceOverwrite)
deleteNfts(diff.removed)
insertNfts(diff.newOrUpdated)
}
@Transaction
open suspend fun updateNft(nftIdentifier: String, update: (NftLocal) -> NftLocal) {
val nft = getNft(nftIdentifier)
val updated = update(nft)
updateNft(updated)
}
}
@@ -0,0 +1,68 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import io.novafoundation.nova.core_db.model.NodeLocal
import kotlinx.coroutines.flow.Flow
@Dao
abstract class NodeDao {
@Query("select * from nodes")
abstract fun nodesFlow(): Flow<List<NodeLocal>>
@Query("select * from nodes")
abstract suspend fun getNodes(): List<NodeLocal>
@Query("select * from nodes where link = :link")
abstract suspend fun getNode(link: String): NodeLocal
@Query("select * from nodes where id = :id")
abstract suspend fun getNodeById(id: Int): NodeLocal
@Query("select count(*) from nodes where link = :nodeHost")
abstract suspend fun getNodesCountByHost(nodeHost: String): Int
@Query("select exists (select * from nodes where link = :nodeHost)")
abstract suspend fun checkNodeExists(nodeHost: String): Boolean
@Query("DELETE FROM nodes where link = :link")
abstract suspend fun remove(link: String)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insert(nodes: List<NodeLocal>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insert(nodes: NodeLocal): Long
@Query("update nodes set name = :newName, link = :newHost, networkType = :networkType where id = :id")
abstract suspend fun updateNode(id: Int, newName: String, newHost: String, networkType: Int)
@Query("SELECT * from nodes where isDefault = 1 AND networkType = :networkType")
abstract suspend fun getDefaultNodeFor(networkType: Int): NodeLocal
@Query("select * from nodes limit 1")
abstract suspend fun getFirstNode(): NodeLocal
@Query("delete from nodes where id = :nodeId")
abstract suspend fun deleteNode(nodeId: Int)
@Query("UPDATE nodes SET isActive = 1 WHERE id = :newActiveNodeId")
protected abstract suspend fun makeActive(newActiveNodeId: Int)
@Query("UPDATE nodes SET isActive = 0 WHERE isActive = 1")
protected abstract suspend fun inactiveCurrentNode()
@Query("SELECT * FROM nodes WHERE isActive = 1")
abstract fun activeNodeFlow(): Flow<NodeLocal?>
@Transaction
open suspend fun switchActiveNode(newNodeId: Int) {
inactiveCurrentNode()
makeActive(newNodeId)
}
}
@@ -0,0 +1,188 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import io.novafoundation.nova.common.utils.mapNotNullToSet
import io.novafoundation.nova.core_db.model.operation.DirectRewardTypeLocal
import io.novafoundation.nova.core_db.model.operation.ExtrinsicTypeLocal
import io.novafoundation.nova.core_db.model.operation.OperationBaseLocal
import io.novafoundation.nova.core_db.model.operation.OperationJoin
import io.novafoundation.nova.core_db.model.operation.OperationLocal
import io.novafoundation.nova.core_db.model.operation.OperationTypeLocal
import io.novafoundation.nova.core_db.model.operation.PoolRewardTypeLocal
import io.novafoundation.nova.core_db.model.operation.SwapTypeLocal
import io.novafoundation.nova.core_db.model.operation.TransferTypeLocal
import kotlinx.coroutines.flow.Flow
private const val ID_FILTER = "address = :address AND chainId = :chainId AND assetId = :chainAssetId"
@Dao
abstract class OperationDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insertOperationBase(operation: OperationBaseLocal)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insertTransferType(type: TransferTypeLocal)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insertDirectRewardType(type: DirectRewardTypeLocal)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insertPoolRewardType(type: PoolRewardTypeLocal)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insertExtrinsicType(type: ExtrinsicTypeLocal)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insertSwapType(type: SwapTypeLocal)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insertOperationsBase(operations: List<OperationBaseLocal>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insertTransferTypes(types: List<TransferTypeLocal>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insertDirectRewardTypes(types: List<DirectRewardTypeLocal>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insertPoolRewardTypes(types: List<PoolRewardTypeLocal>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insertExtrinsicTypes(types: List<ExtrinsicTypeLocal>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insertSwapTypes(types: List<SwapTypeLocal>)
@Transaction
open suspend fun insert(operation: OperationLocal) {
insertOperationBase(operation.base)
insertOperationType(operation.type)
}
@Transaction
open suspend fun insertAll(operations: List<OperationLocal>) {
insertAllInternal(operations)
}
@Query(
"""
SELECT
o.assetId as o_assetId, o.chainId o_chainId, o.id as o_id, o.address as o_address, o.time o_time, o.status as o_status, o.source o_source, o.hash as o_hash,
t.amount as t_amount, t.fee as t_fee, t.sender as t_sender, t.receiver as t_receiver,
e.contentType as e_contentType, e.module as e_module, e.call as e_call, e.fee as e_fee,
rd.isReward as rd_isReward, rd.amount as rd_amount, rd.validator as rd_validator, rd.era as rd_era, rd.eventId as rd_eventId,
rp.isReward as rp_isReward, rp.amount as rp_amount, rp.poolId as rp_poolId, rp.eventId as rp_eventId,
s.fee_chainId as s_fee_chainId, s.fee_assetId as s_fee_assetId, s.fee_amount as s_fee_amount,
s.assetIn_chainId as s_assetIn_chainId, s.assetIn_assetId as s_assetIn_assetId, s.assetIn_amount as s_assetIn_amount,
s.assetOut_chainId as s_assetOut_chainId, s.assetOut_assetId as s_assetOut_assetId, s.assetOut_amount as s_assetOut_amount
FROM operations as o
LEFT JOIN operation_transfers as t ON t.operationId = o.id AND t.assetId = o.assetId AND t.chainId = o.chainId AND t.address = o.address
LEFT JOIN operation_extrinsics as e ON e.operationId = o.id AND e.assetId = o.assetId AND e.chainId = o.chainId AND e.address = o.address
LEFT JOIN operation_rewards_direct as rd ON rd.operationId = o.id AND rd.assetId = o.assetId AND rd.chainId = o.chainId AND rd.address = o.address
LEFT JOIN operation_rewards_pool as rp ON rp.operationId = o.id AND rp.assetId = o.assetId AND rp.chainId = o.chainId AND rp.address = o.address
LEFT JOIN operation_swaps as s ON s.operationId = o.id AND s.assetId = o.assetId AND s.chainId = o.chainId AND s.address = o.address
WHERE o.address = :address AND o.chainId = :chainId AND o.assetId = :chainAssetId
ORDER BY (case when o.status = :statusUp then 0 else 1 end), o.time DESC
"""
)
abstract fun observe(
address: String,
chainId: String,
chainAssetId: Int,
statusUp: OperationBaseLocal.Status = OperationBaseLocal.Status.PENDING
): Flow<List<OperationJoin>>
@Query(
"""
SELECT * FROM operation_transfers
WHERE address = :address AND chainId = :chainId AND assetId = :chainAssetId AND operationId = :operationId
"""
)
abstract suspend fun getTransferType(
operationId: String,
address: String,
chainId: String,
chainAssetId: Int
): TransferTypeLocal?
@Transaction
open suspend fun insertFromRemote(
accountAddress: String,
chainId: String,
chainAssetId: Int,
operations: List<OperationLocal>
) {
clearBySource(accountAddress, chainId, chainAssetId, OperationBaseLocal.Source.REMOTE)
val operationsWithHashes = operations.mapNotNullToSet { it.base.hash }
if (operationsWithHashes.isNotEmpty()) {
clearByHashes(accountAddress, chainId, chainAssetId, operationsWithHashes)
}
val oldestTime = operations.minOfOrNull { it.base.time }
oldestTime?.let {
clearOld(accountAddress, chainId, chainAssetId, oldestTime)
}
insertAllInternal(operations)
}
@Query("DELETE FROM operations WHERE $ID_FILTER AND source = :source")
protected abstract suspend fun clearBySource(
address: String,
chainId: String,
chainAssetId: Int,
source: OperationBaseLocal.Source
): Int
@Query("DELETE FROM operations WHERE time < :minTime AND $ID_FILTER")
protected abstract suspend fun clearOld(
address: String,
chainId: String,
chainAssetId: Int,
minTime: Long
): Int
@Query("DELETE FROM operations WHERE $ID_FILTER AND hash in (:hashes)")
protected abstract suspend fun clearByHashes(
address: String,
chainId: String,
chainAssetId: Int,
hashes: Set<String>
): Int
private suspend fun insertOperationType(type: OperationTypeLocal) {
when (type) {
is ExtrinsicTypeLocal -> insertExtrinsicType(type)
is DirectRewardTypeLocal -> insertDirectRewardType(type)
is PoolRewardTypeLocal -> insertPoolRewardType(type)
is SwapTypeLocal -> insertSwapType(type)
is TransferTypeLocal -> insertTransferType(type)
else -> {}
}
}
private suspend fun insertAllInternal(operations: List<OperationLocal>) {
insertOperationsBase(operations.map { it.base })
insertOperationTypes(operations.map { it.type })
}
private suspend fun insertOperationTypes(types: List<OperationTypeLocal>) {
val transfers = types.filterIsInstance<TransferTypeLocal>()
val extrinsics = types.filterIsInstance<ExtrinsicTypeLocal>()
val directRewards = types.filterIsInstance<DirectRewardTypeLocal>()
val poolRewards = types.filterIsInstance<PoolRewardTypeLocal>()
val swaps = types.filterIsInstance<SwapTypeLocal>()
insertTransferTypes(transfers)
insertExtrinsicTypes(extrinsics)
insertDirectRewardTypes(directRewards)
insertPoolRewardTypes(poolRewards)
insertSwapTypes(swaps)
}
}
@@ -0,0 +1,20 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import io.novafoundation.nova.core_db.model.PhishingAddressLocal
@Dao
interface PhishingAddressDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(addresses: List<PhishingAddressLocal>)
@Query("delete from phishing_addresses")
suspend fun clearTable()
@Query("select publicKey from phishing_addresses")
suspend fun getAllAddresses(): List<String>
}
@@ -0,0 +1,28 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import io.novafoundation.nova.core_db.model.PhishingSiteLocal
@Dao
abstract class PhishingSitesDao {
@Query("SELECT EXISTS (SELECT * FROM phishing_sites WHERE host in (:hostSuffixes))")
abstract suspend fun isPhishing(hostSuffixes: List<String>): Boolean
@Transaction
open suspend fun updatePhishingSites(newSites: List<PhishingSiteLocal>) {
clearPhishingSites()
insertPhishingSites(newSites)
}
@Query("DELETE FROM phishing_sites")
protected abstract suspend fun clearPhishingSites()
@Insert(onConflict = OnConflictStrategy.REPLACE)
protected abstract suspend fun insertPhishingSites(sites: List<PhishingSiteLocal>)
}
@@ -0,0 +1,46 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import io.novafoundation.nova.core_db.model.StakingDashboardAccountsView
import io.novafoundation.nova.core_db.model.StakingDashboardItemLocal
import kotlinx.coroutines.flow.Flow
@Dao
interface StakingDashboardDao {
@Query(
"""
SELECT * FROM staking_dashboard_items WHERE
metaId = :metaId
AND chainId = :chainId
AND chainAssetId = :chainAssetId
AND stakingType = :stakingType
"""
)
suspend fun getDashboardItem(
chainId: String,
chainAssetId: Int,
stakingType: String,
metaId: Long,
): StakingDashboardItemLocal?
@Query("SELECT * FROM staking_dashboard_items WHERE metaId = :metaId")
fun dashboardItemsFlow(metaId: Long): Flow<List<StakingDashboardItemLocal>>
@Query(
"""
SELECT * FROM staking_dashboard_items
WHERE metaId = :metaId AND chainId = :chainId AND chainAssetId = :assetId AND stakingType IN (:assetTypes)
"""
)
fun dashboardItemsFlow(metaId: Long, chainId: String, assetId: Int, assetTypes: List<String>): Flow<List<StakingDashboardItemLocal>>
@Query("SELECT chainId, chainAssetId, stakingType, stakeStatusAccount, rewardsAccount FROM staking_dashboard_items WHERE metaId = :metaId")
fun stakingAccountsViewFlow(metaId: Long): Flow<List<StakingDashboardAccountsView>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertItem(dashboardItemLocal: StakingDashboardItemLocal)
}
@@ -0,0 +1,22 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import io.novafoundation.nova.core_db.model.StakingRewardPeriodLocal
import io.novasama.substrate_sdk_android.runtime.AccountId
import kotlinx.coroutines.flow.Flow
@Dao
interface StakingRewardPeriodDao {
@Query("SELECT * FROM staking_reward_period WHERE accountId = :accountId AND chainId = :chainId AND assetId = :assetId AND stakingType = :stakingType")
suspend fun getStakingRewardPeriod(accountId: AccountId, chainId: String, assetId: Int, stakingType: String): StakingRewardPeriodLocal?
@Query("SELECT * FROM staking_reward_period WHERE accountId = :accountId AND chainId = :chainId AND assetId = :assetId AND stakingType = :stakingType")
fun observeStakingRewardPeriod(accountId: AccountId, chainId: String, assetId: Int, stakingType: String): Flow<StakingRewardPeriodLocal?>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertStakingRewardPeriod(stakingRewardPeriodLocal: StakingRewardPeriodLocal)
}
@@ -0,0 +1,27 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import io.novafoundation.nova.core_db.model.TotalRewardLocal
import io.novasama.substrate_sdk_android.runtime.AccountId
import kotlinx.coroutines.flow.Flow
@Dao
abstract class StakingTotalRewardDao {
@Query(
"""
SELECT * FROM total_reward
WHERE accountId = :accountId AND chainId = :chainId AND chainAssetId = :chainAssetId and stakingType = :stakingType
"""
)
abstract fun observeTotalRewards(accountId: AccountId, chainId: String, chainAssetId: Int, stakingType: String): Flow<TotalRewardLocal>
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insert(totalRewardLocal: TotalRewardLocal)
@Query("DELETE FROM total_reward")
abstract suspend fun deleteAll()
}
@@ -0,0 +1,62 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import io.novafoundation.nova.core_db.model.StorageEntryLocal
import kotlinx.coroutines.flow.Flow
private const val SELECT_FULL_KEY_QUERY = "SELECT * from storage WHERE chainId = :chainId AND storageKey = :fullKey"
private const val SELECT_PREFIX_KEY_QUERY = "SELECT * from storage WHERE chainId = :chainId AND storageKey LIKE :keyPrefix || '%'"
@Dao
abstract class StorageDao {
@Query("SELECT EXISTS($SELECT_PREFIX_KEY_QUERY)")
abstract suspend fun isPrefixInCache(chainId: String, keyPrefix: String): Boolean
@Query("SELECT EXISTS($SELECT_FULL_KEY_QUERY)")
abstract suspend fun isFullKeyInCache(chainId: String, fullKey: String): Boolean
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insert(entry: StorageEntryLocal)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insert(entries: List<StorageEntryLocal>)
@Query("DELETE FROM storage WHERE chainId = :chainId AND storageKey LIKE :prefix || '%'")
abstract suspend fun removeByPrefix(prefix: String, chainId: String)
@Query(
"""
DELETE FROM storage WHERE chainId = :chainId
AND storageKey LIKE :prefix || '%'
AND storageKey NOT IN (:exceptionFullKeys)
"""
)
abstract suspend fun removeByPrefixExcept(prefix: String, exceptionFullKeys: List<String>, chainId: String)
@Query(SELECT_FULL_KEY_QUERY)
abstract fun observeEntry(chainId: String, fullKey: String): Flow<StorageEntryLocal?>
@Query(SELECT_PREFIX_KEY_QUERY)
abstract fun observeEntries(chainId: String, keyPrefix: String): Flow<List<StorageEntryLocal>>
@Query("SELECT storageKey from storage WHERE chainId = :chainId AND storageKey LIKE :keyPrefix || '%'")
abstract suspend fun getKeys(chainId: String, keyPrefix: String): List<String>
@Query("SELECT * from storage WHERE chainId = :chainId AND storageKey in (:fullKeys)")
abstract fun observeEntries(chainId: String, fullKeys: List<String>): Flow<List<StorageEntryLocal>>
@Query("SELECT storageKey from storage WHERE chainId = :chainId AND storageKey in (:keys)")
abstract suspend fun filterKeysInCache(chainId: String, keys: List<String>): List<String>
@Transaction
open suspend fun insertPrefixedEntries(entries: List<StorageEntryLocal>, prefix: String, chainId: String) {
removeByPrefix(prefix, chainId)
insert(entries)
}
}
@@ -0,0 +1,41 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import io.novafoundation.nova.core_db.model.TinderGovBasketItemLocal
import io.novafoundation.nova.core_db.model.TinderGovVotingPowerLocal
import kotlinx.coroutines.flow.Flow
@Dao
interface TinderGovDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun setVotingPower(item: TinderGovVotingPowerLocal)
@Query("SELECT * FROM tinder_gov_voting_power WHERE metaId = :metaId AND chainId = :chainId")
suspend fun getVotingPower(metaId: Long, chainId: String): TinderGovVotingPowerLocal?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun addToBasket(item: TinderGovBasketItemLocal)
@Delete
suspend fun removeFromBasket(item: TinderGovBasketItemLocal)
@Delete
suspend fun removeFromBasket(items: List<TinderGovBasketItemLocal>)
@Query("SELECT * FROM tinder_gov_basket WHERE metaId = :metaId AND chainId == :chainId")
suspend fun getBasket(metaId: Long, chainId: String): List<TinderGovBasketItemLocal>
@Query("SELECT * FROM tinder_gov_basket WHERE metaId = :metaId AND chainId == :chainId")
fun observeBasket(metaId: Long, chainId: String): Flow<List<TinderGovBasketItemLocal>>
@Query("SELECT COUNT(*) FROM tinder_gov_basket WHERE metaId = :metaId AND chainId == :chainId")
fun basketSize(metaId: Long, chainId: String): Int
@Query("DELETE FROM tinder_gov_basket WHERE metaId = :metaId AND chainId == :chainId")
fun clearBasket(metaId: Long, chainId: String)
}
@@ -0,0 +1,74 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import io.novafoundation.nova.common.utils.CollectionDiffer
import io.novafoundation.nova.core_db.model.TokenLocal
import io.novafoundation.nova.core_db.model.TokenWithCurrency
import kotlinx.coroutines.flow.Flow
private const val RETRIEVE_TOKEN_WITH_CURRENCY = """
SELECT * FROM currencies AS currency
LEFT OUTER JOIN tokens AS token ON token.currencyId = currency.id AND token.tokenSymbol = :symbol
WHERE currency.selected = 1
"""
private const val RETRIEVE_TOKENS_WITH_CURRENCY = """
SELECT * FROM currencies AS currency
LEFT OUTER JOIN tokens AS token ON token.currencyId = currency.id AND token.tokenSymbol in (:symbols)
WHERE currency.selected = 1
"""
private const val INSERT_TOKEN_WITH_SELECTED_CURRENCY = """
INSERT OR IGNORE INTO tokens (tokenSymbol, rate, currencyId, recentRateChange)
VALUES(:symbol, NULL, (SELECT id FROM currencies WHERE selected = 1), NULL)
"""
@Dao
abstract class TokenDao {
@Transaction
open suspend fun applyDiff(diff: CollectionDiffer.Diff<TokenLocal>) {
deleteTokens(diff.removed)
insertTokens(diff.added)
updateTokens(diff.updated)
}
@Query(RETRIEVE_TOKEN_WITH_CURRENCY)
abstract suspend fun getTokenWithCurrency(symbol: String): TokenWithCurrency?
@Query(RETRIEVE_TOKENS_WITH_CURRENCY)
abstract fun observeTokensWithCurrency(symbols: List<String>): Flow<List<TokenWithCurrency>>
@Query(RETRIEVE_TOKENS_WITH_CURRENCY)
abstract fun getTokensWithCurrency(symbols: List<String>): List<TokenWithCurrency>
@Query(RETRIEVE_TOKEN_WITH_CURRENCY)
abstract fun observeTokenWithCurrency(symbol: String): Flow<TokenWithCurrency>
@Query("SELECT * FROM tokens")
abstract suspend fun getTokens(): List<TokenLocal>
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insertTokens(tokens: List<TokenLocal>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insertToken(token: TokenLocal)
@Query(INSERT_TOKEN_WITH_SELECTED_CURRENCY)
abstract suspend fun insertTokenWithSelectedCurrency(symbol: String)
@Update
abstract suspend fun updateTokens(chains: List<TokenLocal>)
@Delete
abstract suspend fun deleteTokens(tokens: List<TokenLocal>)
@Query("DELETE FROM tokens")
abstract suspend fun deleteAll()
}
@@ -0,0 +1,33 @@
package io.novafoundation.nova.core_db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import io.novafoundation.nova.core_db.model.WalletConnectPairingLocal
import kotlinx.coroutines.flow.Flow
@Dao
interface WalletConnectSessionsDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertPairing(pairing: WalletConnectPairingLocal)
@Query("DELETE FROM wallet_connect_pairings WHERE pairingTopic = :pairingTopic")
suspend fun deletePairing(pairingTopic: String)
@Query("SELECT * FROM wallet_connect_pairings WHERE pairingTopic = :pairingTopic")
suspend fun getPairing(pairingTopic: String): WalletConnectPairingLocal?
@Query("SELECT * FROM wallet_connect_pairings WHERE pairingTopic = :pairingTopic")
fun pairingFlow(pairingTopic: String): Flow<WalletConnectPairingLocal?>
@Query("DELETE FROM wallet_connect_pairings WHERE pairingTopic NOT IN (:pairingTopics)")
suspend fun removeAllPairingsOtherThan(pairingTopics: List<String>)
@Query("SELECT * FROM wallet_connect_pairings")
fun allPairingsFlow(): Flow<List<WalletConnectPairingLocal>>
@Query("SELECT * FROM wallet_connect_pairings WHERE metaId = :metaId")
fun pairingsByMetaIdFlow(metaId: Long): Flow<List<WalletConnectPairingLocal>>
}
@@ -0,0 +1,101 @@
package io.novafoundation.nova.core_db.di
import io.novafoundation.nova.core_db.AppDatabase
import io.novafoundation.nova.core_db.dao.AccountDao
import io.novafoundation.nova.core_db.dao.AccountStakingDao
import io.novafoundation.nova.core_db.dao.AssetDao
import io.novafoundation.nova.core_db.dao.BrowserHostSettingsDao
import io.novafoundation.nova.core_db.dao.BrowserTabsDao
import io.novafoundation.nova.core_db.dao.ChainAssetDao
import io.novafoundation.nova.core_db.dao.ChainDao
import io.novafoundation.nova.core_db.dao.CoinPriceDao
import io.novafoundation.nova.core_db.dao.ContributionDao
import io.novafoundation.nova.core_db.dao.CurrencyDao
import io.novafoundation.nova.core_db.dao.DappAuthorizationDao
import io.novafoundation.nova.core_db.dao.ExternalBalanceDao
import io.novafoundation.nova.core_db.dao.FavouriteDAppsDao
import io.novafoundation.nova.core_db.dao.GiftsDao
import io.novafoundation.nova.core_db.dao.GovernanceDAppsDao
import io.novafoundation.nova.core_db.dao.HoldsDao
import io.novafoundation.nova.core_db.dao.LockDao
import io.novafoundation.nova.core_db.dao.MetaAccountDao
import io.novafoundation.nova.core_db.dao.MultisigOperationsDao
import io.novafoundation.nova.core_db.dao.NftDao
import io.novafoundation.nova.core_db.dao.NodeDao
import io.novafoundation.nova.core_db.dao.OperationDao
import io.novafoundation.nova.core_db.dao.PhishingAddressDao
import io.novafoundation.nova.core_db.dao.PhishingSitesDao
import io.novafoundation.nova.core_db.dao.StakingDashboardDao
import io.novafoundation.nova.core_db.dao.StakingRewardPeriodDao
import io.novafoundation.nova.core_db.dao.StakingTotalRewardDao
import io.novafoundation.nova.core_db.dao.StorageDao
import io.novafoundation.nova.core_db.dao.TinderGovDao
import io.novafoundation.nova.core_db.dao.TokenDao
import io.novafoundation.nova.core_db.dao.WalletConnectSessionsDao
interface DbApi {
val phishingSitesDao: PhishingSitesDao
val favouritesDAppsDao: FavouriteDAppsDao
val currencyDao: CurrencyDao
val walletConnectSessionsDao: WalletConnectSessionsDao
val stakingDashboardDao: StakingDashboardDao
val externalBalanceDao: ExternalBalanceDao
val holdsDao: HoldsDao
fun provideDatabase(): AppDatabase
fun provideLockDao(): LockDao
fun provideAccountDao(): AccountDao
fun contributionDao(): ContributionDao
fun provideNodeDao(): NodeDao
fun provideAssetDao(): AssetDao
fun provideOperationDao(): OperationDao
fun providePhishingAddressDao(): PhishingAddressDao
fun storageDao(): StorageDao
fun tokenDao(): TokenDao
fun accountStakingDao(): AccountStakingDao
fun stakingTotalRewardDao(): StakingTotalRewardDao
fun chainDao(): ChainDao
fun chainAssetDao(): ChainAssetDao
fun metaAccountDao(): MetaAccountDao
fun dappAuthorizationDao(): DappAuthorizationDao
fun nftDao(): NftDao
fun governanceDAppsDao(): GovernanceDAppsDao
fun browserHostSettingsDao(): BrowserHostSettingsDao
fun coinPriceDao(): CoinPriceDao
fun stakingRewardPeriodDao(): StakingRewardPeriodDao
fun tinderGovDao(): TinderGovDao
fun browserTabsDao(): BrowserTabsDao
fun multisigOperationsDao(): MultisigOperationsDao
fun giftsDao(): GiftsDao
}
@@ -0,0 +1,24 @@
package io.novafoundation.nova.core_db.di
import dagger.Component
import io.novafoundation.nova.common.di.CommonApi
import io.novafoundation.nova.common.di.scope.ApplicationScope
@Component(
modules = [
DbModule::class
],
dependencies = [
DbDependencies::class
]
)
@ApplicationScope
abstract class DbComponent : DbApi {
@Component(
dependencies = [
CommonApi::class
]
)
interface DbDependenciesComponent : DbDependencies
}
@@ -0,0 +1,20 @@
package io.novafoundation.nova.core_db.di
import android.content.Context
import com.google.gson.Gson
import io.novafoundation.nova.common.data.secrets.v1.SecretStoreV1
import io.novafoundation.nova.common.data.secrets.v2.SecretStoreV2
import io.novafoundation.nova.common.data.storage.Preferences
interface DbDependencies {
fun gson(): Gson
fun preferences(): Preferences
fun context(): Context
fun secretStoreV1(): SecretStoreV1
fun secretStoreV2(): SecretStoreV2
}
@@ -0,0 +1,21 @@
package io.novafoundation.nova.core_db.di
import io.novafoundation.nova.common.di.FeatureApiHolder
import io.novafoundation.nova.common.di.FeatureContainer
import io.novafoundation.nova.common.di.scope.ApplicationScope
import javax.inject.Inject
@ApplicationScope
class DbHolder @Inject constructor(
featureContainer: FeatureContainer
) : FeatureApiHolder(featureContainer) {
override fun initializeDependencies(): Any {
val dbDependencies = DaggerDbComponent_DbDependenciesComponent.builder()
.commonApi(commonApi())
.build()
return DaggerDbComponent.builder()
.dbDependencies(dbDependencies)
.build()
}
}
@@ -0,0 +1,236 @@
package io.novafoundation.nova.core_db.di
import android.content.Context
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.core_db.AppDatabase
import io.novafoundation.nova.core_db.dao.AccountDao
import io.novafoundation.nova.core_db.dao.AccountStakingDao
import io.novafoundation.nova.core_db.dao.AssetDao
import io.novafoundation.nova.core_db.dao.BrowserHostSettingsDao
import io.novafoundation.nova.core_db.dao.BrowserTabsDao
import io.novafoundation.nova.core_db.dao.ChainAssetDao
import io.novafoundation.nova.core_db.dao.ChainDao
import io.novafoundation.nova.core_db.dao.CoinPriceDao
import io.novafoundation.nova.core_db.dao.ContributionDao
import io.novafoundation.nova.core_db.dao.CurrencyDao
import io.novafoundation.nova.core_db.dao.DappAuthorizationDao
import io.novafoundation.nova.core_db.dao.ExternalBalanceDao
import io.novafoundation.nova.core_db.dao.FavouriteDAppsDao
import io.novafoundation.nova.core_db.dao.GiftsDao
import io.novafoundation.nova.core_db.dao.GovernanceDAppsDao
import io.novafoundation.nova.core_db.dao.HoldsDao
import io.novafoundation.nova.core_db.dao.LockDao
import io.novafoundation.nova.core_db.dao.MetaAccountDao
import io.novafoundation.nova.core_db.dao.MultisigOperationsDao
import io.novafoundation.nova.core_db.dao.NftDao
import io.novafoundation.nova.core_db.dao.NodeDao
import io.novafoundation.nova.core_db.dao.OperationDao
import io.novafoundation.nova.core_db.dao.PhishingAddressDao
import io.novafoundation.nova.core_db.dao.PhishingSitesDao
import io.novafoundation.nova.core_db.dao.StakingDashboardDao
import io.novafoundation.nova.core_db.dao.StakingRewardPeriodDao
import io.novafoundation.nova.core_db.dao.StakingTotalRewardDao
import io.novafoundation.nova.core_db.dao.StorageDao
import io.novafoundation.nova.core_db.dao.TinderGovDao
import io.novafoundation.nova.core_db.dao.TokenDao
import io.novafoundation.nova.core_db.dao.WalletConnectSessionsDao
@Module
class DbModule {
@Provides
@ApplicationScope
fun provideAppDatabase(
context: Context
): AppDatabase {
return AppDatabase.get(context)
}
@Provides
@ApplicationScope
fun provideUserDao(appDatabase: AppDatabase): AccountDao {
return appDatabase.userDao()
}
@Provides
@ApplicationScope
fun provideNodeDao(appDatabase: AppDatabase): NodeDao {
return appDatabase.nodeDao()
}
@Provides
@ApplicationScope
fun provideAssetDao(appDatabase: AppDatabase): AssetDao {
return appDatabase.assetDao()
}
@Provides
@ApplicationScope
fun provideLockDao(appDatabase: AppDatabase): LockDao {
return appDatabase.lockDao()
}
@Provides
@ApplicationScope
fun provideContributionDao(appDatabase: AppDatabase): ContributionDao {
return appDatabase.contributionDao()
}
@Provides
@ApplicationScope
fun provideOperationHistoryDao(appDatabase: AppDatabase): OperationDao {
return appDatabase.operationDao()
}
@Provides
@ApplicationScope
fun providePhishingAddressDao(appDatabase: AppDatabase): PhishingAddressDao {
return appDatabase.phishingAddressesDao()
}
@Provides
@ApplicationScope
fun provideStorageDao(appDatabase: AppDatabase): StorageDao {
return appDatabase.storageDao()
}
@Provides
@ApplicationScope
fun provideTokenDao(appDatabase: AppDatabase): TokenDao {
return appDatabase.tokenDao()
}
@Provides
@ApplicationScope
fun provideAccountStakingDao(appDatabase: AppDatabase): AccountStakingDao {
return appDatabase.accountStakingDao()
}
@Provides
@ApplicationScope
fun provideStakingTotalRewardDao(appDatabase: AppDatabase): StakingTotalRewardDao {
return appDatabase.stakingTotalRewardDao()
}
@Provides
@ApplicationScope
fun provideChainDao(appDatabase: AppDatabase): ChainDao {
return appDatabase.chainDao()
}
@Provides
@ApplicationScope
fun provideChainAssetDao(appDatabase: AppDatabase): ChainAssetDao {
return appDatabase.chainAssetDao()
}
@Provides
@ApplicationScope
fun provideMetaAccountDao(appDatabase: AppDatabase): MetaAccountDao {
return appDatabase.metaAccountDao()
}
@Provides
@ApplicationScope
fun provideDappAuthorizationDao(appDatabase: AppDatabase): DappAuthorizationDao {
return appDatabase.dAppAuthorizationDao()
}
@Provides
@ApplicationScope
fun provideNftDao(appDatabase: AppDatabase): NftDao {
return appDatabase.nftDao()
}
@Provides
@ApplicationScope
fun providePhishingSitesDao(appDatabase: AppDatabase): PhishingSitesDao {
return appDatabase.phishingSitesDao()
}
@Provides
@ApplicationScope
fun provideFavouriteDappsDao(appDatabase: AppDatabase): FavouriteDAppsDao {
return appDatabase.favouriteDAppsDao()
}
@Provides
@ApplicationScope
fun provideCurrencyDao(appDatabase: AppDatabase): CurrencyDao {
return appDatabase.currencyDao()
}
@Provides
@ApplicationScope
fun provideGovernanceDAppDao(appDatabase: AppDatabase): GovernanceDAppsDao {
return appDatabase.governanceDAppsDao()
}
@Provides
@ApplicationScope
fun provideBrowserHostSettingsDao(appDatabase: AppDatabase): BrowserHostSettingsDao {
return appDatabase.browserHostSettingsDao()
}
@Provides
@ApplicationScope
fun provideWalletConnectSessionsDao(appDatabase: AppDatabase): WalletConnectSessionsDao {
return appDatabase.walletConnectSessionsDao()
}
@Provides
@ApplicationScope
fun provideStakingDashboardDao(appDatabase: AppDatabase): StakingDashboardDao {
return appDatabase.stakingDashboardDao()
}
@Provides
@ApplicationScope
fun provideCoinPriceDao(appDatabase: AppDatabase): CoinPriceDao {
return appDatabase.coinPriceDao()
}
@Provides
@ApplicationScope
fun provideStakingRewardPeriodDao(appDatabase: AppDatabase): StakingRewardPeriodDao {
return appDatabase.stakingRewardPeriodDao()
}
@Provides
@ApplicationScope
fun provideExternalBalanceDao(appDatabase: AppDatabase): ExternalBalanceDao {
return appDatabase.externalBalanceDao()
}
@Provides
@ApplicationScope
fun provideHoldsDao(appDatabase: AppDatabase): HoldsDao {
return appDatabase.holdsDao()
}
@Provides
@ApplicationScope
fun provideTinderGovDao(appDatabase: AppDatabase): TinderGovDao {
return appDatabase.tinderGovDao()
}
@Provides
@ApplicationScope
fun provideBrowserTabsDao(appDatabase: AppDatabase): BrowserTabsDao {
return appDatabase.browserTabsDao()
}
@Provides
@ApplicationScope
fun provideMultisigOperationsDao(appDatabase: AppDatabase): MultisigOperationsDao {
return appDatabase.multisigOperationsDao()
}
@Provides
@ApplicationScope
fun provideGiftsDao(appDatabase: AppDatabase): GiftsDao {
return appDatabase.giftsDao()
}
}
@@ -0,0 +1,8 @@
package io.novafoundation.nova.core_db.ext
import io.novafoundation.nova.core_db.dao.FullAssetIdLocal
import io.novafoundation.nova.core_db.model.chain.ChainAssetLocal
fun ChainAssetLocal.fullId(): FullAssetIdLocal {
return FullAssetIdLocal(this.chainId, this.id)
}
@@ -0,0 +1,22 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val ChangeDAppAuthorization_10_11 = object : Migration(10, 11) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE dapp_authorizations")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `dapp_authorizations` (
`baseUrl` TEXT NOT NULL,
`metaId` INTEGER NOT NULL,
`dAppTitle` TEXT, `authorized` INTEGER,
PRIMARY KEY(`baseUrl`, `metaId`)
)
""".trimIndent()
)
}
}
@@ -0,0 +1,45 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val RemoveChainForeignKeyFromChainAccount_11_12 = object : Migration(11, 12) {
override fun migrate(database: SupportSQLiteDatabase) {
// rename
database.execSQL("DROP INDEX `index_chain_accounts_chainId`")
database.execSQL("DROP INDEX `index_chain_accounts_metaId`")
database.execSQL("DROP INDEX `index_chain_accounts_accountId`")
database.execSQL("ALTER TABLE chain_accounts RENAME TO chain_accounts_old")
// new table
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `chain_accounts` (
`metaId` INTEGER NOT NULL,
`chainId` TEXT NOT NULL,
`publicKey` BLOB NOT NULL,
`accountId` BLOB NOT NULL,
`cryptoType` TEXT NOT NULL,
PRIMARY KEY(`metaId`, `chainId`),
FOREIGN KEY(`metaId`) REFERENCES `meta_accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE
)
""".trimIndent()
)
database.execSQL("CREATE INDEX IF NOT EXISTS `index_chain_accounts_chainId` ON `chain_accounts` (`chainId`)")
database.execSQL("CREATE INDEX IF NOT EXISTS `index_chain_accounts_metaId` ON `chain_accounts` (`metaId`)")
database.execSQL("CREATE INDEX IF NOT EXISTS `index_chain_accounts_accountId` ON `chain_accounts` (`accountId`)")
// insert to new from old
database.execSQL(
"""
INSERT INTO chain_accounts
SELECT *
FROM chain_accounts_old
""".trimIndent()
)
// delete old
database.execSQL("DROP TABLE chain_accounts_old")
}
}
@@ -0,0 +1,54 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
// used on master for Astar hotfix
val AddAdditionalFieldToChains_12_13 = object : Migration(12, 13) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE chains ADD COLUMN additional TEXT DEFAULT null")
}
}
// used on develop for parachainStaking rewards
val AddChainToTotalRewards_12_13 = object : Migration(12, 13) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE total_reward")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `total_reward` (
`accountAddress` TEXT NOT NULL,
`chainId` TEXT NOT NULL,
`chainAssetId` INTEGER NOT NULL,
`totalReward` TEXT NOT NULL,
PRIMARY KEY(`chainId`, `chainAssetId`, `accountAddress`)
)
""".trimIndent()
)
}
}
val FixMigrationConflicts_13_14 = object : Migration(13, 14) {
override fun migrate(database: SupportSQLiteDatabase) {
if (isMigratingFromMaster(database)) {
// migrating from master -> execute missing develop migration
AddChainToTotalRewards_12_13.migrate(database)
} else {
// migrating from develop -> execute missing master migration
AddAdditionalFieldToChains_12_13.migrate(database)
}
}
private fun isMigratingFromMaster(database: SupportSQLiteDatabase): Boolean {
return runCatching {
// check for column added in astar hotfix (master)
database.query("SELECT additional FROM chains LIMIT 1")
}.fold(
onSuccess = { true },
onFailure = { false }
)
}
}
@@ -0,0 +1,19 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import io.novafoundation.nova.core_db.converters.MetaAccountTypeConverters
import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal
val AddMetaAccountType_14_15 = object : Migration(14, 15) {
override fun migrate(database: SupportSQLiteDatabase) {
val converters = MetaAccountTypeConverters()
// all accounts that exist till now are added via secrets
val defaultType = MetaAccountLocal.Type.SECRETS
val typeRepresentationInDb = converters.fromEnum(defaultType)
database.execSQL("ALTER TABLE meta_accounts ADD COLUMN type TEXT NOT NULL DEFAULT '$typeRepresentationInDb'")
}
}
@@ -0,0 +1,45 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val NullableSubstratePublicKey_15_16 = object : Migration(15, 16) {
override fun migrate(database: SupportSQLiteDatabase) {
// rename
database.execSQL("DROP INDEX `index_meta_accounts_substrateAccountId`")
database.execSQL("DROP INDEX `index_meta_accounts_ethereumAddress`")
database.execSQL("ALTER TABLE meta_accounts RENAME TO meta_accounts_old")
// new table
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `meta_accounts` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`substratePublicKey` BLOB,
`substrateCryptoType` TEXT,
`substrateAccountId` BLOB NOT NULL,
`ethereumPublicKey` BLOB,
`ethereumAddress` BLOB,
`name` TEXT NOT NULL,
`isSelected` INTEGER NOT NULL,
`position` INTEGER NOT NULL,
`type` TEXT NOT NULL
)
""".trimIndent()
)
database.execSQL("CREATE INDEX IF NOT EXISTS `index_meta_accounts_substrateAccountId` ON `meta_accounts` (`substrateAccountId`)")
database.execSQL("CREATE INDEX IF NOT EXISTS `index_meta_accounts_ethereumAddress` ON `meta_accounts` (`ethereumAddress`)")
// insert to new from old
database.execSQL(
"""
INSERT INTO meta_accounts
SELECT *
FROM meta_accounts_old
""".trimIndent()
)
// delete old
database.execSQL("DROP TABLE meta_accounts_old")
}
}
@@ -0,0 +1,44 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val WatchOnlyChainAccounts_16_17 = object : Migration(16, 17) {
override fun migrate(database: SupportSQLiteDatabase) {
// rename
database.execSQL("DROP INDEX `index_chain_accounts_chainId`")
database.execSQL("DROP INDEX `index_chain_accounts_metaId`")
database.execSQL("DROP INDEX `index_chain_accounts_accountId`")
database.execSQL("ALTER TABLE chain_accounts RENAME TO chain_accounts_old")
// new table
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `chain_accounts` (
`metaId` INTEGER NOT NULL,
`chainId` TEXT NOT NULL,
`publicKey` BLOB,
`accountId` BLOB NOT NULL,
`cryptoType` TEXT,
PRIMARY KEY(`metaId`, `chainId`),
FOREIGN KEY(`metaId`) REFERENCES `meta_accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE
)
""".trimIndent()
)
database.execSQL("CREATE INDEX IF NOT EXISTS `index_chain_accounts_chainId` ON `chain_accounts` (`chainId`)")
database.execSQL("CREATE INDEX IF NOT EXISTS `index_chain_accounts_metaId` ON `chain_accounts` (`metaId`)")
database.execSQL("CREATE INDEX IF NOT EXISTS `index_chain_accounts_accountId` ON `chain_accounts` (`accountId`)")
// insert to new from old
database.execSQL(
"""
INSERT INTO chain_accounts
SELECT *
FROM chain_accounts_old
""".trimIndent()
)
// delete old
database.execSQL("DROP TABLE chain_accounts_old")
}
}
@@ -0,0 +1,51 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val RemoveColorFromChains_17_18 = object : Migration(17, 18) {
override fun migrate(database: SupportSQLiteDatabase) {
// rename
database.execSQL("ALTER TABLE chains RENAME TO chains_old")
// new table
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `chains` (
`id` TEXT NOT NULL,
`parentId` TEXT,
`name` TEXT NOT NULL,
`icon` TEXT NOT NULL,
`prefix` INTEGER NOT NULL,
`isEthereumBased` INTEGER NOT NULL,
`isTestNet` INTEGER NOT NULL,
`hasCrowdloans` INTEGER NOT NULL,
`additional` TEXT,
`url` TEXT,
`overridesCommon` INTEGER,
`staking_url` TEXT,
`staking_type` TEXT,
`history_url` TEXT,
`history_type` TEXT,
`crowdloans_url` TEXT,
`crowdloans_type` TEXT,
PRIMARY KEY(`id`)
)
""".trimIndent()
)
// insert to new from old
database.execSQL(
// select all but color
"""
INSERT INTO chains
SELECT id, parentId, name, icon, prefix, isEthereumBased, isTestNet, hasCrowdloans, additional, url, overridesCommon,
staking_url, staking_type, history_url, history_type, crowdloans_url, crowdloans_type
FROM chains_old
""".trimIndent()
)
// delete old
database.execSQL("DROP TABLE chains_old")
}
}
@@ -0,0 +1,25 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val AddCurrencies_18_19 = object : Migration(18, 19) {
override fun migrate(database: SupportSQLiteDatabase) {
// new table
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `currencies` (
`code` TEXT NOT NULL,
`name` TEXT NOT NULL,
`symbol` TEXT,
`category` TEXT NOT NULL,
`popular` INTEGER NOT NULL,
`id` INTEGER NOT NULL,
`coingeckoId` TEXT NOT NULL,
`selected` INTEGER NOT NULL,
PRIMARY KEY(`id`)
)
""".trimIndent()
)
}
}
@@ -0,0 +1,36 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val ChangeTokens_19_20 = object : Migration(19, 20) {
override fun migrate(database: SupportSQLiteDatabase) {
// rename table
database.execSQL("ALTER TABLE tokens RENAME TO tokens_old")
// new table
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `tokens` (
`tokenSymbol` TEXT NOT NULL,
`rate` TEXT,
`recentRateChange` TEXT,
`currencyId` INTEGER NOT NULL,
PRIMARY KEY(`tokenSymbol`, `currencyId`)
)
""".trimIndent()
)
// insert to new from old
database.execSQL(
"""
INSERT INTO tokens (tokenSymbol, rate, recentRateChange, currencyId)
SELECT symbol, dollarRate, recentRateChange, 0
FROM tokens_old
""".trimIndent()
)
// delete old
database.execSQL("DROP TABLE tokens_old")
}
}
@@ -0,0 +1,19 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val AddDAppAuthorizations_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `dapp_authorizations` (
`baseUrl` TEXT NOT NULL,
`authorized` INTEGER,
PRIMARY KEY(`baseUrl`)
)
""".trimIndent()
)
}
}
@@ -0,0 +1,10 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val ChangeChainNodes_20_21 = object : Migration(20, 21) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE chain_nodes ADD `orderId` INTEGER NOT NULL DEFAULT 0")
}
}
@@ -0,0 +1,46 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val NullableSubstrateAccountId_21_22 = object : Migration(21, 22) {
override fun migrate(database: SupportSQLiteDatabase) {
// rename
database.execSQL("DROP INDEX `index_meta_accounts_substrateAccountId`")
database.execSQL("DROP INDEX `index_meta_accounts_ethereumAddress`")
database.execSQL("ALTER TABLE meta_accounts RENAME TO meta_accounts_old")
// new table
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `meta_accounts` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`substratePublicKey` BLOB,
`substrateCryptoType` TEXT,
`substrateAccountId` BLOB,
`ethereumPublicKey` BLOB,
`ethereumAddress` BLOB,
`name` TEXT NOT NULL,
`isSelected` INTEGER NOT NULL,
`position` INTEGER NOT NULL,
`type` TEXT NOT NULL
)
""".trimIndent()
)
database.execSQL("CREATE INDEX IF NOT EXISTS `index_meta_accounts_substrateAccountId` ON `meta_accounts` (`substrateAccountId`)")
database.execSQL("CREATE INDEX IF NOT EXISTS `index_meta_accounts_ethereumAddress` ON `meta_accounts` (`ethereumAddress`)")
// insert to new from old
database.execSQL(
"""
INSERT INTO meta_accounts
SELECT *
FROM meta_accounts_old
""".trimIndent()
)
// delete old
database.execSQL("DROP TABLE meta_accounts_old")
}
}
@@ -0,0 +1,25 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val AddLocks_22_23 = object : Migration(22, 23) {
override fun migrate(database: SupportSQLiteDatabase) {
// new table
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `locks` (
`metaId` INTEGER NOT NULL,
`chainId` TEXT NOT NULL,
`assetId` INTEGER NOT NULL,
`type` TEXT NOT NULL,
`amount` TEXT NOT NULL,
PRIMARY KEY(`metaId`, `chainId`, `assetId`, `type`),
FOREIGN KEY(`metaId`) REFERENCES `meta_accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE ,
FOREIGN KEY(`chainId`) REFERENCES `chains`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE ,
FOREIGN KEY(`assetId`, `chainId`) REFERENCES `chain_assets`(`id`, `chainId`) ON UPDATE NO ACTION ON DELETE CASCADE
)
""".trimIndent()
)
}
}
@@ -0,0 +1,22 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val AddContributions_23_24 = object : Migration(23, 24) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `contributions` (
`metaId` INTEGER NOT NULL,
`chainId` TEXT NOT NULL,
`assetId` INTEGER NOT NULL,
`paraId` TEXT NOT NULL,
`amountInPlanks` TEXT NOT NULL,
`sourceId` TEXT NOT NULL,
PRIMARY KEY(`metaId`, `chainId`, `assetId`, `paraId`, `sourceId`)
)
""".trimIndent()
)
}
}
@@ -0,0 +1,11 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val AddGovernanceFlagToChains_24_25 = object : Migration(24, 25) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE chains ADD COLUMN hasGovernance INTEGER NOT NULL DEFAULT 0")
}
}
@@ -0,0 +1,22 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val AddGovernanceDapps_25_26 = object : Migration(25, 26) {
override fun migrate(database: SupportSQLiteDatabase) {
// new table
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `governance_dapps` (
`chainId` TEXT NOT NULL,
`name` TEXT NOT NULL,
`referendumUrl` TEXT NOT NULL,
`iconUrl` TEXT NOT NULL,
`details` TEXT NOT NULL,
PRIMARY KEY(`chainId`, `name`)
)
""".trimIndent()
)
}
}
@@ -0,0 +1,55 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val GovernanceFlagToEnum_26_27 = object : Migration(26, 27) {
override fun migrate(database: SupportSQLiteDatabase) {
// rename
database.execSQL("ALTER TABLE chains RENAME TO chains_old")
// new table
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `chains` (
`id` TEXT NOT NULL,
`parentId` TEXT,
`name` TEXT NOT NULL,
`icon` TEXT NOT NULL,
`prefix` INTEGER NOT NULL,
`isEthereumBased` INTEGER NOT NULL,
`isTestNet` INTEGER NOT NULL,
`hasCrowdloans` INTEGER NOT NULL,
`governance` TEXT NOT NULL,
`additional` TEXT,
`url` TEXT,
`overridesCommon` INTEGER,
`staking_url` TEXT,
`staking_type` TEXT,
`history_url` TEXT,
`history_type` TEXT,
`crowdloans_url` TEXT,
`crowdloans_type` TEXT,
PRIMARY KEY(`id`)
)
""".trimIndent()
)
val governanceDefault = "NONE"
// insert to new from old
database.execSQL(
// select all but color
"""
INSERT INTO chains
SELECT id, parentId, name, icon, prefix, isEthereumBased, isTestNet, hasCrowdloans, "$governanceDefault", additional, url, overridesCommon,
staking_url, staking_type, history_url, history_type, crowdloans_url, crowdloans_type
FROM chains_old
""".trimIndent()
)
// delete old
database.execSQL("DROP TABLE chains_old")
}
}
@@ -0,0 +1,13 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val AddGovernanceExternalApiToChain_27_28 = object : Migration(27, 28) {
override fun migrate(database: SupportSQLiteDatabase) {
// new columns
database.execSQL("ALTER TABLE `chains` ADD COLUMN `governance_url` TEXT")
database.execSQL("ALTER TABLE `chains` ADD COLUMN `governance_type` TEXT")
}
}
@@ -0,0 +1,12 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import io.novafoundation.nova.core_db.model.chain.ChainAssetLocal
val AddSourceToLocalAsset_28_29 = object : Migration(28, 29) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE `chain_assets` ADD COLUMN `source` TEXT NOT NULL DEFAULT '${ChainAssetLocal.SOURCE_DEFAULT}'")
}
}
@@ -0,0 +1,85 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val AddTransferApisTable_29_30 = object : Migration(29, 30) {
override fun migrate(database: SupportSQLiteDatabase) {
removeTransferApiFieldsFromChains(database)
addTransferApiTable(database)
clearOperationsCache(database)
}
private fun clearOperationsCache(database: SupportSQLiteDatabase) {
database.execSQL("DELETE FROM operations")
}
private fun addTransferApiTable(database: SupportSQLiteDatabase) {
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `chain_transfer_history_apis` (
`chainId` TEXT NOT NULL,
`assetType` TEXT NOT NULL,
`apiType` TEXT NOT NULL,
`url` TEXT NOT NULL,
PRIMARY KEY(`chainId`, `url`),
FOREIGN KEY(`chainId`) REFERENCES `chains`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE
)
""".trimIndent()
)
database.execSQL(
"""
CREATE INDEX IF NOT EXISTS `index_chain_transfer_history_apis_chainId` ON `chain_transfer_history_apis` (`chainId`)
""".trimIndent()
)
}
private fun removeTransferApiFieldsFromChains(database: SupportSQLiteDatabase) {
// rename
database.execSQL("ALTER TABLE chains RENAME TO chains_old")
// new table
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `chains` (
`id` TEXT NOT NULL,
`parentId` TEXT,
`name` TEXT NOT NULL,
`icon` TEXT NOT NULL,
`prefix` INTEGER NOT NULL,
`isEthereumBased` INTEGER NOT NULL,
`isTestNet` INTEGER NOT NULL,
`hasCrowdloans` INTEGER NOT NULL,
`governance` TEXT NOT NULL,
`additional` TEXT,
`url` TEXT,
`overridesCommon` INTEGER,
`staking_url` TEXT,
`staking_type` TEXT,
`crowdloans_url` TEXT,
`crowdloans_type` TEXT,
`governance_url` TEXT,
`governance_type` TEXT,
PRIMARY KEY(`id`)
)
""".trimIndent()
)
// insert to new from old
database.execSQL(
// select all but color
"""
INSERT INTO chains
SELECT id, parentId, name, icon, prefix, isEthereumBased, isTestNet, hasCrowdloans, governance,
additional, url, overridesCommon, staking_url, staking_type, crowdloans_url, crowdloans_type, governance_url, governance_type
FROM chains_old
""".trimIndent()
)
// delete old
database.execSQL("DROP TABLE chains_old")
}
}
@@ -0,0 +1,13 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val AssetTypes_2_3 = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE chain_assets ADD COLUMN type TEXT DEFAULT NULL")
database.execSQL("ALTER TABLE chain_assets ADD COLUMN typeExtras TEXT DEFAULT NULL")
database.execSQL("ALTER TABLE chain_assets ADD COLUMN icon TEXT DEFAULT NULL")
}
}
@@ -0,0 +1,12 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import io.novafoundation.nova.core_db.model.chain.ChainAssetLocal
val AddEnabledColumnToChainAssets_30_31 = object : Migration(30, 31) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE `chain_assets` ADD COLUMN `enabled` INTEGER NOT NULL DEFAULT ${ChainAssetLocal.ENABLED_DEFAULT_STR}")
}
}
@@ -0,0 +1,272 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import io.novafoundation.nova.core_db.model.chain.ChainAssetLocal
/**
* Due to previous migration of chain & meta account tables by means of rename-create-insert-delete strategy
* foreign keys to these tables got renamed and now points to wrong table which causes crashes for subset of users
* This migration recreates all affected tables
*/
val FixBrokenForeignKeys_31_32 = object : Migration(31, 32) {
override fun migrate(database: SupportSQLiteDatabase) {
// foreign key to ChainLocal
recreateChainAssets(database)
// foreign key to ChainAssetLocal which was recreated above
recreateAssets(database)
// foreign key to MetaAccountLocal
recreateChainAccount(database)
// foreign key to ChainLocal
recreateChainRuntimeInfo(database)
// foreign key to ChainLocal
recreateChainExplorers(database)
// foreign key to ChainLocal
recreateChainNodes(database)
// foreign key to ChainLocal, ChainAssetLocal, MetaAccount
recreateBalanceLocks(database)
}
private fun recreateChainAssets(database: SupportSQLiteDatabase) {
database.execSQL("DROP INDEX IF EXISTS `index_chain_assets_chainId`")
database.execSQL("ALTER TABLE chain_assets RENAME TO chain_assets_old")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `chain_assets` (
`id` INTEGER NOT NULL,
`chainId` TEXT NOT NULL,
`name` TEXT NOT NULL,
`symbol` TEXT NOT NULL,
`priceId` TEXT,
`staking` TEXT NOT NULL,
`precision` INTEGER NOT NULL,
`icon` TEXT,
`type` TEXT,
`source` TEXT NOT NULL DEFAULT '${ChainAssetLocal.SOURCE_DEFAULT}',
`buyProviders` TEXT,
`typeExtras` TEXT,
`enabled` INTEGER NOT NULL DEFAULT ${ChainAssetLocal.ENABLED_DEFAULT_STR},
PRIMARY KEY(`chainId`,`id`),
FOREIGN KEY(`chainId`) REFERENCES `chains`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE
)
""".trimIndent()
)
database.execSQL(
"""
INSERT INTO chain_assets
SELECT
id, chainId, name, symbol, priceId,
staking, precision, icon, type, source,
buyProviders, typeExtras, enabled
FROM chain_assets_old
""".trimIndent()
)
database.execSQL("CREATE INDEX IF NOT EXISTS `index_chain_assets_chainId` ON `chain_assets` (`chainId`)")
database.execSQL("DROP TABLE chain_assets_old")
}
private fun recreateAssets(database: SupportSQLiteDatabase) {
database.execSQL("DROP INDEX IF EXISTS `index_assets_metaId`")
database.execSQL("ALTER TABLE assets RENAME TO assets_old")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `assets` (
`assetId` INTEGER NOT NULL,
`chainId` TEXT NOT NULL,
`metaId` INTEGER NOT NULL,
`freeInPlanks` TEXT NOT NULL,
`frozenInPlanks` TEXT NOT NULL,
`reservedInPlanks` TEXT NOT NULL,
`bondedInPlanks` TEXT NOT NULL,
`redeemableInPlanks` TEXT NOT NULL,
`unbondingInPlanks` TEXT NOT NULL,
PRIMARY KEY(`assetId`,`chainId`,`metaId`),
FOREIGN KEY(`assetId`,`chainId`) REFERENCES `chain_assets`(`id`, `chainId`) ON UPDATE NO ACTION ON DELETE CASCADE
)
""".trimIndent()
)
database.execSQL(
"""
INSERT INTO assets
SELECT
assetId, chainId, metaId,
freeInPlanks, frozenInPlanks, reservedInPlanks,
bondedInPlanks, redeemableInPlanks, unbondingInPlanks
FROM assets_old
""".trimIndent()
)
database.execSQL("CREATE INDEX IF NOT EXISTS `index_assets_metaId` ON `assets` (`metaId`)")
database.execSQL("DROP TABLE assets_old")
}
private fun recreateChainAccount(database: SupportSQLiteDatabase) {
database.execSQL("DROP INDEX IF EXISTS `index_chain_accounts_chainId`")
database.execSQL("DROP INDEX IF EXISTS `index_chain_accounts_metaId`")
database.execSQL("DROP INDEX IF EXISTS `index_chain_accounts_accountId`")
database.execSQL("ALTER TABLE chain_accounts RENAME TO chain_accounts_old")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `chain_accounts` (
`metaId` INTEGER NOT NULL,
`chainId` TEXT NOT NULL,
`publicKey` BLOB,
`accountId` BLOB NOT NULL,
`cryptoType` TEXT,
PRIMARY KEY(`metaId`, `chainId`),
FOREIGN KEY(`metaId`) REFERENCES `meta_accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE
)
""".trimIndent()
)
database.execSQL(
"""
INSERT INTO chain_accounts
SELECT metaId, chainId, publicKey, accountId, cryptoType
FROM chain_accounts_old
""".trimIndent()
)
database.execSQL("CREATE INDEX IF NOT EXISTS `index_chain_accounts_chainId` ON `chain_accounts` (`chainId`)")
database.execSQL("CREATE INDEX IF NOT EXISTS `index_chain_accounts_metaId` ON `chain_accounts` (`metaId`)")
database.execSQL("CREATE INDEX IF NOT EXISTS `index_chain_accounts_accountId` ON `chain_accounts` (`accountId`)")
database.execSQL("DROP TABLE chain_accounts_old")
}
private fun recreateChainRuntimeInfo(database: SupportSQLiteDatabase) {
database.execSQL("DROP INDEX IF EXISTS `index_chain_runtimes_chainId`")
database.execSQL("ALTER TABLE chain_runtimes RENAME TO chain_runtimes_old")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `chain_runtimes` (
`chainId` TEXT NOT NULL,
`syncedVersion` INTEGER NOT NULL,
`remoteVersion` INTEGER NOT NULL,
PRIMARY KEY(`chainId`),
FOREIGN KEY(`chainId`) REFERENCES `chains`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE
)
""".trimIndent()
)
database.execSQL(
"""
INSERT INTO chain_runtimes
SELECT chainId, syncedVersion, remoteVersion FROM chain_runtimes_old
""".trimIndent()
)
database.execSQL("CREATE INDEX IF NOT EXISTS `index_chain_runtimes_chainId` ON `chain_runtimes` (`chainId`)")
database.execSQL("DROP TABLE chain_runtimes_old")
}
private fun recreateChainExplorers(database: SupportSQLiteDatabase) {
database.execSQL("DROP INDEX IF EXISTS `index_chain_explorers_chainId`")
database.execSQL("ALTER TABLE chain_explorers RENAME TO chain_explorers_old")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `chain_explorers` (
`chainId` TEXT NOT NULL,
`name` TEXT NOT NULL,
`extrinsic` TEXT,
`account` TEXT,
`event` TEXT,
PRIMARY KEY(`chainId`, `name`),
FOREIGN KEY(`chainId`) REFERENCES `chains`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE
)
""".trimIndent()
)
database.execSQL(
"""
INSERT INTO chain_explorers
SELECT chainId, name, extrinsic, account, event FROM chain_explorers_old
""".trimIndent()
)
database.execSQL("CREATE INDEX IF NOT EXISTS `index_chain_explorers_chainId` ON `chain_explorers` (`chainId`)")
database.execSQL("DROP TABLE chain_explorers_old")
}
private fun recreateChainNodes(database: SupportSQLiteDatabase) {
database.execSQL("DROP INDEX IF EXISTS `index_chain_nodes_chainId`")
database.execSQL("ALTER TABLE chain_nodes RENAME TO chain_nodes_old")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `chain_nodes` (
`chainId` TEXT NOT NULL,
`url` TEXT NOT NULL,
`name` TEXT NOT NULL,
`orderId` INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY(`chainId`, `url`),
FOREIGN KEY(`chainId`) REFERENCES `chains`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE
)
""".trimIndent()
)
database.execSQL(
"""
INSERT INTO chain_nodes
SELECT chainId, url, name, orderId FROM chain_nodes_old
""".trimIndent()
)
database.execSQL("CREATE INDEX IF NOT EXISTS `index_chain_nodes_chainId` ON `chain_nodes` (`chainId`)")
database.execSQL("DROP TABLE chain_nodes_old")
}
private fun recreateBalanceLocks(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE locks RENAME TO locks_old")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `locks` (
`metaId` INTEGER NOT NULL,
`chainId` TEXT NOT NULL,
`assetId` INTEGER NOT NULL,
`type` TEXT NOT NULL,
`amount` TEXT NOT NULL,
PRIMARY KEY(`metaId`, `chainId`, `assetId`, `type`),
FOREIGN KEY(`metaId`) REFERENCES `meta_accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE,
FOREIGN KEY(`chainId`) REFERENCES `chains`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE ,
FOREIGN KEY(`assetId`, `chainId`) REFERENCES `chain_assets`(`id`, `chainId`) ON UPDATE NO ACTION ON DELETE CASCADE
)
""".trimIndent()
)
database.execSQL(
"""
INSERT INTO locks
SELECT metaId, chainId, assetId, type, amount FROM locks_old
""".trimIndent()
)
database.execSQL("DROP TABLE locks_old")
}
}
@@ -0,0 +1,24 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val AddVersioningToGovernanceDapps_32_33 = object : Migration(32, 33) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE `governance_dapps`")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `governance_dapps` (
`chainId` TEXT NOT NULL,
`name` TEXT NOT NULL,
`referendumUrlV1` TEXT,
`referendumUrlV2` TEXT,
`iconUrl` TEXT NOT NULL,
`details` TEXT NOT NULL,
PRIMARY KEY(`chainId`, `name`)
)
""".trimIndent()
)
}
}
@@ -0,0 +1,11 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val AddGovernanceNetworkToExternalApi_33_34 = object : Migration(33, 34) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE chains ADD COLUMN `governance_parameters` TEXT")
}
}
@@ -0,0 +1,19 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val AddBrowserHostSettings_34_35 = object : Migration(34, 35) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `browser_host_settings` (
`hostUrl` TEXT NOT NULL,
`isDesktopModeEnabled` INTEGER NOT NULL,
PRIMARY KEY(`hostUrl`)
)
""".trimIndent()
)
}
}
@@ -0,0 +1,74 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val ExtractExternalApiToSeparateTable_35_36 = object : Migration(35, 36) {
override fun migrate(database: SupportSQLiteDatabase) {
removeExternalApisColumnsFromChains(database)
migrateExternalApisTable(database)
// recreating chainId causes broken foreign keys to appear on some devices
// so we run this migration again to fix it
FixBrokenForeignKeys_31_32.migrate(database)
}
private fun migrateExternalApisTable(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE chain_transfer_history_apis")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `chain_external_apis` (
`chainId` TEXT NOT NULL,
`sourceType` TEXT NOT NULL,
`apiType` TEXT NOT NULL,
`parameters` TEXT,
`url` TEXT NOT NULL,
PRIMARY KEY(`chainId`, `url`, `apiType`),
FOREIGN KEY(`chainId`) REFERENCES `chains`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE
)
""".trimIndent()
)
database.execSQL("CREATE INDEX IF NOT EXISTS `index_chain_external_apis_chainId` ON `chain_external_apis` (`chainId`)")
}
private fun removeExternalApisColumnsFromChains(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE chains RENAME TO chains_old")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `chains` (
`id` TEXT NOT NULL,
`parentId` TEXT,
`name` TEXT NOT NULL,
`icon` TEXT NOT NULL,
`prefix` INTEGER NOT NULL,
`isEthereumBased` INTEGER NOT NULL,
`isTestNet` INTEGER NOT NULL,
`hasCrowdloans` INTEGER NOT NULL,
`governance` TEXT NOT NULL,
`additional` TEXT,
`url` TEXT,
`overridesCommon` INTEGER,
PRIMARY KEY(`id`)
)
""".trimIndent()
)
database.execSQL(
"""
INSERT INTO chains
SELECT
id, parentId, name, icon, prefix,
isEthereumBased, isTestNet, hasCrowdloans, governance, additional,
url, overridesCommon
FROM chains_old
""".trimIndent()
)
database.execSQL("DROP TABLE chains_old")
}
}
@@ -0,0 +1,14 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import io.novafoundation.nova.core_db.model.chain.ChainLocal
val AddRuntimeFlagToChains_36_37 = object : Migration(36, 37) {
override fun migrate(database: SupportSQLiteDatabase) {
val default = ChainLocal.Default.HAS_SUBSTRATE_RUNTIME
database.execSQL("ALTER TABLE chains ADD COLUMN `hasSubstrateRuntime` INTEGER NOT NULL DEFAULT $default")
}
}
@@ -0,0 +1,37 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val AddExtrinsicContentField_37_38 = object : Migration(37, 38) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE operations")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `operations` (
`id` TEXT NOT NULL,
`address` TEXT NOT NULL,
`chainId` TEXT NOT NULL,
`chainAssetId` INTEGER NOT NULL,
`time` INTEGER NOT NULL,
`status` INTEGER NOT NULL,
`source` INTEGER NOT NULL,
`operationType` INTEGER NOT NULL,
`amount` TEXT,
`sender` TEXT,
`receiver` TEXT,
`hash` TEXT,
`fee` TEXT,
`isReward` INTEGER,
`era` INTEGER,
`validator` TEXT,
`extrinsicContent_type` TEXT,
`extrinsicContent_module` TEXT,
`extrinsicContent_call` TEXT,
PRIMARY KEY(`id`, `address`, `chainId`, `chainAssetId`)
)
""".trimIndent()
)
}
}
@@ -0,0 +1,13 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import io.novafoundation.nova.core_db.model.chain.ChainLocal
val AddNodeSelectionStrategyField_38_39 = object : Migration(38, 39) {
override fun migrate(database: SupportSQLiteDatabase) {
val default = ChainLocal.Default.NODE_SELECTION_STRATEGY_DEFAULT
database.execSQL("ALTER TABLE chains ADD COLUMN `nodeSelectionStrategy` TEXT NOT NULL DEFAULT '$default'")
}
}
@@ -0,0 +1,21 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val AddWalletConnectSessions_39_40 = object : Migration(39, 40) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `wallet_connect_sessions` (
`sessionTopic` TEXT NOT NULL,
`metaId` INTEGER NOT NULL,
PRIMARY KEY(`sessionTopic`),
FOREIGN KEY(`metaId`) REFERENCES `meta_accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE
)
""".trimIndent()
)
database.execSQL("CREATE INDEX IF NOT EXISTS `index_wallet_connect_sessions_metaId` ON `wallet_connect_sessions` (`metaId`)")
}
}
@@ -0,0 +1,30 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val ChangeAsset_3_4 = object : Migration(3, 4) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE assets")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `assets` (
`tokenSymbol` TEXT NOT NULL,
`chainId` TEXT NOT NULL,
`metaId` INTEGER NOT NULL,
`freeInPlanks` TEXT NOT NULL,
`frozenInPlanks` TEXT NOT NULL,
`reservedInPlanks` TEXT NOT NULL,
`bondedInPlanks` TEXT NOT NULL,
`redeemableInPlanks` TEXT NOT NULL,
`unbondingInPlanks` TEXT NOT NULL,
PRIMARY KEY(`tokenSymbol`, `chainId`, `metaId`)
)
""".trimIndent()
)
database.execSQL("CREATE INDEX IF NOT EXISTS `index_assets_metaId` ON `assets` (`metaId`)")
}
}
@@ -0,0 +1,32 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val AddStakingDashboardItems_41_42 = object : Migration(41, 42) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `staking_dashboard_items` (
`chainId` TEXT NOT NULL,
`chainAssetId` INTEGER NOT NULL,
`stakingType` TEXT NOT NULL,
`metaId` INTEGER NOT NULL,
`hasStake` INTEGER NOT NULL,
`stake` TEXT,
`status` TEXT,
`rewards` TEXT,
`estimatedEarnings` REAL,
`primaryStakingAccountId` BLOB,
PRIMARY KEY(`chainId`, `chainAssetId`, `stakingType`, `metaId`),
FOREIGN KEY(`chainId`) REFERENCES `chains`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE,
FOREIGN KEY(`chainAssetId`, `chainId`) REFERENCES `chain_assets`(`id`, `chainId`) ON UPDATE NO ACTION ON DELETE CASCADE ,
FOREIGN KEY(`metaId`) REFERENCES `meta_accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE
)
""".trimIndent()
)
database.execSQL("CREATE INDEX IF NOT EXISTS `index_staking_dashboard_items_metaId` ON `staking_dashboard_items` (`metaId`)")
}
}
@@ -0,0 +1,24 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val TransferFiatAmount_40_41 = object : Migration(40, 41) {
override fun migrate(database: SupportSQLiteDatabase) {
createCoinPriceTable(database)
}
private fun createCoinPriceTable(database: SupportSQLiteDatabase) {
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `coin_prices` (
`priceId` TEXT NOT NULL,
`currencyId` TEXT NOT NULL,
`timestamp` INTEGER NOT NULL,
`rate` TEXT NOT NULL,
PRIMARY KEY(`priceId`, `currencyId`, `timestamp`)
)
""".trimIndent()
)
}
}
@@ -0,0 +1,27 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val StakingRewardPeriods_42_43 = object : Migration(42, 43) {
override fun migrate(database: SupportSQLiteDatabase) {
createCoinPriceTable(database)
}
private fun createCoinPriceTable(database: SupportSQLiteDatabase) {
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `staking_reward_period` (
`accountId` BLOB NOT NULL,
`chainId` TEXT NOT NULL,
`assetId` INTEGER NOT NULL,
`stakingType` TEXT NOT NULL,
`periodType` TEXT NOT NULL,
`customPeriodStart` INTEGER,
`customPeriodEnd` INTEGER,
PRIMARY KEY(`accountId`, `chainId`, `assetId`, `stakingType`))
""".trimIndent()
)
}
}
@@ -0,0 +1,36 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val AddRewardAccountToStakingDashboard_43_44 = object : Migration(43, 44) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE `staking_dashboard_items`")
database.execSQL("DROP INDEX IF EXISTS `index_staking_dashboard_items_metaId`")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `staking_dashboard_items` (
`chainId` TEXT NOT NULL,
`chainAssetId` INTEGER NOT NULL,
`stakingType` TEXT NOT NULL,
`metaId` INTEGER NOT NULL,
`hasStake` INTEGER NOT NULL,
`stake` TEXT,
`status` TEXT,
`rewards` TEXT,
`estimatedEarnings` REAL,
`stakeStatusAccount` BLOB,
`rewardsAccount` BLOB,
PRIMARY KEY(`chainId`, `chainAssetId`, `stakingType`, `metaId`),
FOREIGN KEY(`chainId`) REFERENCES `chains`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE,
FOREIGN KEY(`chainAssetId`, `chainId`) REFERENCES `chain_assets`(`id`, `chainId`) ON UPDATE NO ACTION ON DELETE CASCADE,
FOREIGN KEY(`metaId`) REFERENCES `meta_accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE
)
""".trimIndent()
)
database.execSQL("CREATE INDEX IF NOT EXISTS `index_staking_dashboard_items_metaId` ON `staking_dashboard_items` (`metaId`)")
}
}
@@ -0,0 +1,24 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val AddStakingTypeToTotalRewards_44_45 = object : Migration(44, 45) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE total_reward")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `total_reward` (
`accountId` BLOB NOT NULL,
`chainId` TEXT NOT NULL,
`chainAssetId` INTEGER NOT NULL,
`stakingType` TEXT NOT NULL,
`totalReward` TEXT NOT NULL,
PRIMARY KEY(`chainId`,`chainAssetId`,`stakingType`, `accountId`)
)
""".trimIndent()
)
}
}
@@ -0,0 +1,24 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val AddExternalBalances_45_46 = object : Migration(45, 46) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `externalBalances` (
`metaId` INTEGER NOT NULL,
`chainId` TEXT NOT NULL,
`assetId` INTEGER NOT NULL,
`type` TEXT NOT NULL,
`subtype` TEXT NOT NULL,
`amount` TEXT NOT NULL,
PRIMARY KEY(`metaId`, `chainId`, `assetId`, `type`, `subtype`),
FOREIGN KEY(`assetId`, `chainId`) REFERENCES `chain_assets`(`id`, `chainId`) ON UPDATE NO ACTION ON DELETE CASCADE,
FOREIGN KEY(`metaId`) REFERENCES `meta_accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )
"""
)
}
}
@@ -0,0 +1,11 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val AddPoolIdToOperations_46_47 = object : Migration(46, 47) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE operations ADD COLUMN poolId INTEGER")
}
}
@@ -0,0 +1,13 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val AddEventIdToOperation_47_48 = object : Migration(47, 48) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE operations ADD COLUMN eventId TEXT")
database.execSQL("DELETE FROM operations")
}
}
@@ -0,0 +1,11 @@
package io.novafoundation.nova.core_db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
val AddSwapOption_48_49 = object : Migration(48, 49) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE chains ADD COLUMN `swap` TEXT NOT NULL DEFAULT ''")
}
}

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