Initial commit: Pezkuwi Wallet Android

Security hardened release:
- Code obfuscation enabled (minifyEnabled=true, shrinkResources=true)
- Sensitive files excluded (google-services.json, keystores)
- Branch.io key moved to BuildConfig placeholder
- Updated dependencies: OkHttp 4.12.0, Gson 2.10.1, BouncyCastle 1.77
- Comprehensive ProGuard rules for crypto wallet
- Navigation 2.7.7, Lifecycle 2.7.0, ConstraintLayout 2.1.4
This commit is contained in:
2026-02-12 05:19:41 +03:00
commit a294aa1a6b
7687 changed files with 441811 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/build
+54
View File
@@ -0,0 +1,54 @@
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments += ["room.schemaLocation":
"$projectDir/schemas".toString()]
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets {
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
namespace 'io.novafoundation.nova.core_db'
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation substrateSdkDep
implementation project(":common")
implementation gsonDep
implementation kotlinDep
implementation coroutinesDep
implementation daggerDep
ksp daggerCompiler
implementation roomDep
implementation roomKtxDep
ksp roomCompiler
androidTestImplementation androidTestRunnerDep
androidTestImplementation androidTestRulesDep
androidTestImplementation androidJunitDep
androidTestImplementation roomTestsDep
testImplementation project(':test-shared')
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -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()
)
}
}

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