Updated on 2026-08-14
This commit is contained in:
parent
3531c95908
commit
52cb5d4adf
12 changed files with 1029 additions and 0 deletions
|
|
@ -6,6 +6,7 @@ plugins {
|
|||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.kotlin.serialization)
|
||||
alias(deps.plugins.hilt.android)
|
||||
alias(deps.plugins.room)
|
||||
alias(deps.plugins.ksp)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,439 @@
|
|||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 1,
|
||||
"identityHash": "aafa8b51b5a5a32d0ec2b0720cec6c1e",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "express_provider",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `icon_url` TEXT NOT NULL, `provider_url` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "iconUrl",
|
||||
"columnName": "icon_url",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "providerUrl",
|
||||
"columnName": "provider_url",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "express_exchange",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tx_id` TEXT NOT NULL, `owner_address` TEXT NOT NULL, `provider_id` TEXT NOT NULL, `status` TEXT NOT NULL, `to_is_actual` INTEGER NOT NULL DEFAULT 0, `payin_hash` TEXT, `payout_hash` TEXT, `external_tx_id` TEXT, `external_tx_url` TEXT, `rate_type` TEXT NOT NULL, `created_at` INTEGER NOT NULL, `updated_at` INTEGER NOT NULL, `from_network` TEXT NOT NULL, `from_token_id` TEXT, `from_raw_amount` TEXT NOT NULL, `from_decimals` INTEGER NOT NULL, `to_network` TEXT NOT NULL, `to_token_id` TEXT, `to_raw_amount` TEXT NOT NULL, `to_decimals` INTEGER NOT NULL, `refund_network` TEXT, `refund_token_id` TEXT, `refund_raw_amount` TEXT, `refund_decimals` INTEGER, `refund_hash` TEXT, PRIMARY KEY(`tx_id`), FOREIGN KEY(`provider_id`) REFERENCES `express_provider`(`id`) ON UPDATE NO ACTION ON DELETE RESTRICT )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "txId",
|
||||
"columnName": "tx_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "ownerAddress",
|
||||
"columnName": "owner_address",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "providerId",
|
||||
"columnName": "provider_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "status",
|
||||
"columnName": "status",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "toIsActual",
|
||||
"columnName": "to_is_actual",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "payinHash",
|
||||
"columnName": "payin_hash",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "payoutHash",
|
||||
"columnName": "payout_hash",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "externalTxId",
|
||||
"columnName": "external_tx_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "externalTxUrl",
|
||||
"columnName": "external_tx_url",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rateType",
|
||||
"columnName": "rate_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "createdAt",
|
||||
"columnName": "created_at",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "updatedAt",
|
||||
"columnName": "updated_at",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "from.network",
|
||||
"columnName": "from_network",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "from.tokenId",
|
||||
"columnName": "from_token_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "from.rawAmount",
|
||||
"columnName": "from_raw_amount",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "from.decimals",
|
||||
"columnName": "from_decimals",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "to.network",
|
||||
"columnName": "to_network",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "to.tokenId",
|
||||
"columnName": "to_token_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "to.rawAmount",
|
||||
"columnName": "to_raw_amount",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "to.decimals",
|
||||
"columnName": "to_decimals",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "refund.network",
|
||||
"columnName": "refund_network",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "refund.tokenId",
|
||||
"columnName": "refund_token_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "refund.rawAmount",
|
||||
"columnName": "refund_raw_amount",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "refund.decimals",
|
||||
"columnName": "refund_decimals",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "refund.hash",
|
||||
"columnName": "refund_hash",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"tx_id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_express_exchange_owner_address_from_network_updated_at",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"owner_address",
|
||||
"from_network",
|
||||
"updated_at"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_express_exchange_owner_address_from_network_updated_at` ON `${TABLE_NAME}` (`owner_address`, `from_network`, `updated_at`)"
|
||||
},
|
||||
{
|
||||
"name": "index_express_exchange_owner_address_payin_hash",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"owner_address",
|
||||
"payin_hash"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_express_exchange_owner_address_payin_hash` ON `${TABLE_NAME}` (`owner_address`, `payin_hash`)"
|
||||
},
|
||||
{
|
||||
"name": "index_express_exchange_owner_address_payout_hash",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"owner_address",
|
||||
"payout_hash"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_express_exchange_owner_address_payout_hash` ON `${TABLE_NAME}` (`owner_address`, `payout_hash`)"
|
||||
},
|
||||
{
|
||||
"name": "index_express_exchange_owner_address_refund_hash",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"owner_address",
|
||||
"refund_hash"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_express_exchange_owner_address_refund_hash` ON `${TABLE_NAME}` (`owner_address`, `refund_hash`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "express_provider",
|
||||
"onDelete": "RESTRICT",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"provider_id"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "express_onramp",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tx_id` TEXT NOT NULL, `owner_address` TEXT NOT NULL, `provider_id` TEXT NOT NULL, `status` TEXT NOT NULL, `from_currency_code` TEXT NOT NULL, `from_amount` TEXT NOT NULL, `to_network` TEXT NOT NULL, `to_token_id` TEXT, `to_expected_raw_amount` TEXT NOT NULL, `to_actual_raw_amount` TEXT, `to_decimals` INTEGER NOT NULL, `payout_hash` TEXT, `external_tx_id` TEXT, `external_tx_url` TEXT, `rate_type` TEXT NOT NULL, `fail_reason` TEXT, `created_at` INTEGER NOT NULL, `updated_at` INTEGER NOT NULL, `refund_currency_code` TEXT, `refund_amount` TEXT, PRIMARY KEY(`tx_id`), FOREIGN KEY(`provider_id`) REFERENCES `express_provider`(`id`) ON UPDATE NO ACTION ON DELETE RESTRICT )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "txId",
|
||||
"columnName": "tx_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "ownerAddress",
|
||||
"columnName": "owner_address",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "providerId",
|
||||
"columnName": "provider_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "status",
|
||||
"columnName": "status",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "fromCurrencyCode",
|
||||
"columnName": "from_currency_code",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "fromAmount",
|
||||
"columnName": "from_amount",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "toNetwork",
|
||||
"columnName": "to_network",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "toTokenId",
|
||||
"columnName": "to_token_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "toExpectedRawAmount",
|
||||
"columnName": "to_expected_raw_amount",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "toActualRawAmount",
|
||||
"columnName": "to_actual_raw_amount",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "toDecimals",
|
||||
"columnName": "to_decimals",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "payoutHash",
|
||||
"columnName": "payout_hash",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "externalTxId",
|
||||
"columnName": "external_tx_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "externalTxUrl",
|
||||
"columnName": "external_tx_url",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rateType",
|
||||
"columnName": "rate_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "failReason",
|
||||
"columnName": "fail_reason",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "createdAt",
|
||||
"columnName": "created_at",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "updatedAt",
|
||||
"columnName": "updated_at",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "refund.currencyCode",
|
||||
"columnName": "refund_currency_code",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "refund.amount",
|
||||
"columnName": "refund_amount",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"tx_id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_express_onramp_owner_address_to_network_updated_at",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"owner_address",
|
||||
"to_network",
|
||||
"updated_at"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_express_onramp_owner_address_to_network_updated_at` ON `${TABLE_NAME}` (`owner_address`, `to_network`, `updated_at`)"
|
||||
},
|
||||
{
|
||||
"name": "index_express_onramp_owner_address_payout_hash",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"owner_address",
|
||||
"payout_hash"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_express_onramp_owner_address_payout_hash` ON `${TABLE_NAME}` (`owner_address`, `payout_hash`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "express_provider",
|
||||
"onDelete": "RESTRICT",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"provider_id"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'aafa8b51b5a5a32d0ec2b0720cec6c1e')"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStoreFactory
|
||||
import androidx.datastore.dataStoreFile
|
||||
import androidx.room.Room
|
||||
import com.tangem.datasource.local.txhistory.db.TxHistoryDatabase
|
||||
import com.tangem.datasource.local.txhistory.store.CommonSyncState
|
||||
import com.tangem.datasource.local.txhistory.store.CommonSyncStateKey
|
||||
import com.tangem.datasource.local.txhistory.store.DefaultTxHistoryStore
|
||||
import com.tangem.datasource.local.txhistory.store.TxHistoryStore
|
||||
import com.tangem.datasource.utils.KotlinxDataStoreSerializer
|
||||
import com.tangem.datasource.utils.KotlinxDataStoreSerializer.Companion.jsonBuilder
|
||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.serialization.builtins.MapSerializer
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface TxHistoryModule {
|
||||
|
||||
companion object {
|
||||
|
||||
private const val TX_HISTORY_DATABASE_NAME = "tx_history_database.db"
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTxHistoryDatabase(@ApplicationContext context: Context): TxHistoryDatabase {
|
||||
return Room.databaseBuilder(
|
||||
context = context,
|
||||
klass = TxHistoryDatabase::class.java,
|
||||
name = TX_HISTORY_DATABASE_NAME,
|
||||
).build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTxHistoryStore(@ApplicationContext context: Context, appScope: AppCoroutineScope): TxHistoryStore {
|
||||
val commonSerializer = KotlinxDataStoreSerializer(
|
||||
defaultValue = emptyMap(),
|
||||
serializer = MapSerializer(
|
||||
CommonSyncStateKey.serializer(),
|
||||
CommonSyncState.serializer(),
|
||||
),
|
||||
json = jsonBuilder {
|
||||
allowStructuredMapKeys = true
|
||||
},
|
||||
)
|
||||
|
||||
val expressExchangeStore = DataStoreFactory.create(
|
||||
serializer = commonSerializer,
|
||||
produceFile = { context.dataStoreFile(fileName = "TxHistoryExpressExchangeStore") },
|
||||
scope = appScope,
|
||||
)
|
||||
val expressOnrampStore = DataStoreFactory.create(
|
||||
serializer = commonSerializer,
|
||||
produceFile = { context.dataStoreFile(fileName = "TxHistoryExpressOnrampStore") },
|
||||
scope = appScope,
|
||||
)
|
||||
|
||||
return DefaultTxHistoryStore(
|
||||
expressExchangeStore = expressExchangeStore,
|
||||
expressOnrampStore = expressOnrampStore,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.datasource.local.txhistory.db
|
||||
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import com.tangem.datasource.local.txhistory.db.entity.ExpressHistoryDao
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressExchangeEntity
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressOnrampEntity
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressProviderEntity
|
||||
|
||||
@Database(
|
||||
version = 1,
|
||||
entities = [
|
||||
ExpressProviderEntity::class,
|
||||
ExpressExchangeEntity::class,
|
||||
ExpressOnrampEntity::class,
|
||||
],
|
||||
)
|
||||
abstract class TxHistoryDatabase : RoomDatabase() {
|
||||
|
||||
abstract fun expressHistoryDao(): ExpressHistoryDao
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
package com.tangem.datasource.local.txhistory.db.entity
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressExchangeEntity
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressOnrampEntity
|
||||
import com.tangem.datasource.local.txhistory.db.entity.express.ExpressProviderEntity
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface ExpressHistoryDao {
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun upsertProviders(items: List<ExpressProviderEntity>)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun upsertExchanges(items: List<ExpressExchangeEntity>)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun upsertOnramps(items: List<ExpressOnrampEntity>)
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT *
|
||||
FROM express_exchange
|
||||
WHERE owner_address = :ownerAddress
|
||||
ORDER BY updated_at DESC
|
||||
""",
|
||||
)
|
||||
fun observeExchanges(ownerAddress: String): Flow<List<ExpressExchangeEntity>>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT *
|
||||
FROM express_onramp
|
||||
WHERE owner_address = :ownerAddress
|
||||
ORDER BY updated_at DESC
|
||||
""",
|
||||
)
|
||||
fun observeOnramps(ownerAddress: String): Flow<List<ExpressOnrampEntity>>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT *
|
||||
FROM express_exchange
|
||||
WHERE owner_address = :ownerAddress
|
||||
AND payin_hash = :hash
|
||||
LIMIT 1
|
||||
""",
|
||||
)
|
||||
suspend fun findExchangeByPayinHash(ownerAddress: String, hash: String): ExpressExchangeEntity?
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT *
|
||||
FROM express_exchange
|
||||
WHERE owner_address = :ownerAddress
|
||||
AND payout_hash = :hash
|
||||
LIMIT 1
|
||||
""",
|
||||
)
|
||||
suspend fun findExchangeByPayoutHash(ownerAddress: String, hash: String): ExpressExchangeEntity?
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT *
|
||||
FROM express_exchange
|
||||
WHERE owner_address = :ownerAddress
|
||||
AND refund_hash = :hash
|
||||
LIMIT 1
|
||||
""",
|
||||
)
|
||||
suspend fun findExchangeByRefundHash(ownerAddress: String, hash: String): ExpressExchangeEntity?
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT *
|
||||
FROM express_onramp
|
||||
WHERE owner_address = :ownerAddress
|
||||
AND payout_hash = :hash
|
||||
LIMIT 1
|
||||
""",
|
||||
)
|
||||
suspend fun findOnrampByPayoutHash(ownerAddress: String, hash: String): ExpressOnrampEntity?
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
package com.tangem.datasource.local.txhistory.db.entity.express
|
||||
|
||||
import androidx.room.*
|
||||
|
||||
@Suppress("BooleanPropertyNaming")
|
||||
@Entity(
|
||||
tableName = "express_exchange",
|
||||
foreignKeys = [
|
||||
ForeignKey(
|
||||
entity = ExpressProviderEntity::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["provider_id"],
|
||||
onDelete = ForeignKey.RESTRICT,
|
||||
),
|
||||
],
|
||||
indices = [
|
||||
Index(value = ["owner_address", "from_network", "updated_at"]),
|
||||
Index(value = ["owner_address", "payin_hash"]),
|
||||
Index(value = ["owner_address", "payout_hash"]),
|
||||
Index(value = ["owner_address", "refund_hash"]),
|
||||
],
|
||||
)
|
||||
data class ExpressExchangeEntity(
|
||||
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "tx_id")
|
||||
val txId: String,
|
||||
|
||||
@ColumnInfo(name = "owner_address")
|
||||
val ownerAddress: String,
|
||||
|
||||
@ColumnInfo(name = "provider_id")
|
||||
val providerId: String,
|
||||
|
||||
/**
|
||||
* waiting
|
||||
* confirming
|
||||
* exchanging
|
||||
* sending
|
||||
* finished
|
||||
* failed
|
||||
* refunded
|
||||
* expired
|
||||
*/
|
||||
@ColumnInfo(name = "status")
|
||||
val status: String,
|
||||
|
||||
@Embedded(prefix = "from_")
|
||||
val from: AssetEmbedded,
|
||||
|
||||
@Embedded(prefix = "to_")
|
||||
val to: AssetEmbedded,
|
||||
|
||||
/**
|
||||
* true -> actual provider-confirmed amount
|
||||
* false -> estimated amount
|
||||
*/
|
||||
@ColumnInfo(name = "to_is_actual", defaultValue = "0")
|
||||
val toIsActual: Boolean,
|
||||
|
||||
/**
|
||||
* Match key for PAYIN leg
|
||||
*/
|
||||
@ColumnInfo(name = "payin_hash")
|
||||
val payinHash: String?,
|
||||
|
||||
/**
|
||||
* Match key for PAYOUT leg
|
||||
*/
|
||||
@ColumnInfo(name = "payout_hash")
|
||||
val payoutHash: String?,
|
||||
|
||||
@ColumnInfo(name = "external_tx_id")
|
||||
val externalTxId: String?,
|
||||
|
||||
@ColumnInfo(name = "external_tx_url")
|
||||
val externalTxUrl: String?,
|
||||
|
||||
/**
|
||||
* fixed / float
|
||||
*/
|
||||
@ColumnInfo(name = "rate_type")
|
||||
val rateType: String,
|
||||
|
||||
@ColumnInfo(name = "created_at")
|
||||
val createdAt: Long,
|
||||
|
||||
@ColumnInfo(name = "updated_at")
|
||||
val updatedAt: Long,
|
||||
|
||||
@Embedded(prefix = "refund_")
|
||||
val refund: RefundEmbedded?,
|
||||
) {
|
||||
|
||||
data class AssetEmbedded(
|
||||
|
||||
@ColumnInfo(name = "network")
|
||||
val network: String,
|
||||
|
||||
@ColumnInfo(name = "token_id")
|
||||
val tokenId: String?,
|
||||
|
||||
@ColumnInfo(name = "raw_amount")
|
||||
val rawAmount: String,
|
||||
|
||||
@ColumnInfo(name = "decimals")
|
||||
val decimals: Int,
|
||||
)
|
||||
|
||||
data class RefundEmbedded(
|
||||
|
||||
@ColumnInfo(name = "network")
|
||||
val network: String?,
|
||||
|
||||
@ColumnInfo(name = "token_id")
|
||||
val tokenId: String?,
|
||||
|
||||
@ColumnInfo(name = "raw_amount")
|
||||
val rawAmount: String?,
|
||||
|
||||
@ColumnInfo(name = "decimals")
|
||||
val decimals: Int?,
|
||||
|
||||
/**
|
||||
* Match key for REFUND leg
|
||||
*/
|
||||
@ColumnInfo(name = "hash")
|
||||
val hash: String?,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
package com.tangem.datasource.local.txhistory.db.entity.express
|
||||
|
||||
import androidx.room.*
|
||||
|
||||
@Entity(
|
||||
tableName = "express_onramp",
|
||||
foreignKeys = [
|
||||
ForeignKey(
|
||||
entity = ExpressProviderEntity::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["provider_id"],
|
||||
onDelete = ForeignKey.RESTRICT,
|
||||
),
|
||||
],
|
||||
indices = [
|
||||
Index(value = ["owner_address", "to_network", "updated_at"]),
|
||||
Index(value = ["owner_address", "payout_hash"]),
|
||||
],
|
||||
)
|
||||
data class ExpressOnrampEntity(
|
||||
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "tx_id")
|
||||
val txId: String,
|
||||
|
||||
@ColumnInfo(name = "owner_address")
|
||||
val ownerAddress: String,
|
||||
|
||||
@ColumnInfo(name = "provider_id")
|
||||
val providerId: String,
|
||||
|
||||
/**
|
||||
|
||||
* waiting-for-payment
|
||||
* payment-processing
|
||||
* paused
|
||||
* verifying
|
||||
* sending
|
||||
* finished
|
||||
* failed
|
||||
* expired
|
||||
* refunded
|
||||
*/
|
||||
@ColumnInfo(name = "status")
|
||||
val status: String,
|
||||
|
||||
/**
|
||||
* ISO-4217
|
||||
*/
|
||||
@ColumnInfo(name = "from_currency_code")
|
||||
val fromCurrencyCode: String,
|
||||
|
||||
/**
|
||||
* Decimal string
|
||||
*/
|
||||
@ColumnInfo(name = "from_amount")
|
||||
val fromAmount: String,
|
||||
|
||||
@ColumnInfo(name = "to_network")
|
||||
val toNetwork: String,
|
||||
|
||||
@ColumnInfo(name = "to_token_id")
|
||||
val toTokenId: String?,
|
||||
|
||||
/**
|
||||
* Estimated amount at creation moment
|
||||
*/
|
||||
@ColumnInfo(name = "to_expected_raw_amount")
|
||||
val toExpectedRawAmount: String,
|
||||
|
||||
/**
|
||||
* Actual provider-confirmed amount
|
||||
*/
|
||||
@ColumnInfo(name = "to_actual_raw_amount")
|
||||
val toActualRawAmount: String?,
|
||||
|
||||
@ColumnInfo(name = "to_decimals")
|
||||
val toDecimals: Int,
|
||||
|
||||
/**
|
||||
* Match key with gateway_tx.hash
|
||||
*/
|
||||
@ColumnInfo(name = "payout_hash")
|
||||
val payoutHash: String?,
|
||||
|
||||
@ColumnInfo(name = "external_tx_id")
|
||||
val externalTxId: String?,
|
||||
|
||||
@ColumnInfo(name = "external_tx_url")
|
||||
val externalTxUrl: String?,
|
||||
|
||||
/**
|
||||
* fixed / float
|
||||
*/
|
||||
@ColumnInfo(name = "rate_type")
|
||||
val rateType: String,
|
||||
|
||||
@ColumnInfo(name = "fail_reason")
|
||||
val failReason: String?,
|
||||
|
||||
@ColumnInfo(name = "created_at")
|
||||
val createdAt: Long,
|
||||
|
||||
@ColumnInfo(name = "updated_at")
|
||||
val updatedAt: Long,
|
||||
|
||||
@Embedded(prefix = "refund_")
|
||||
val refund: RefundEmbedded?,
|
||||
) {
|
||||
|
||||
data class RefundEmbedded(
|
||||
|
||||
/**
|
||||
* ISO-4217
|
||||
*/
|
||||
@ColumnInfo(name = "currency_code")
|
||||
val currencyCode: String?,
|
||||
|
||||
@ColumnInfo(name = "amount")
|
||||
val amount: String?,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.datasource.local.txhistory.db.entity.express
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(
|
||||
tableName = "express_provider",
|
||||
)
|
||||
data class ExpressProviderEntity(
|
||||
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "id")
|
||||
val id: String,
|
||||
|
||||
@ColumnInfo(name = "name")
|
||||
val name: String,
|
||||
|
||||
@ColumnInfo(name = "icon_url")
|
||||
val iconUrl: String,
|
||||
|
||||
@ColumnInfo(name = "provider_url")
|
||||
val providerUrl: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.tangem.datasource.local.txhistory.store
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
internal class DefaultTxHistoryStore(
|
||||
private val expressExchangeStore: DataStore<Map<CommonSyncStateKey, CommonSyncState>>,
|
||||
private val expressOnrampStore: DataStore<Map<CommonSyncStateKey, CommonSyncState>>,
|
||||
) : TxHistoryStore {
|
||||
|
||||
override fun expressExchangeSyncState(key: CommonSyncStateKey): Flow<CommonSyncState> {
|
||||
return expressExchangeStore.data.map { map -> map.getOrDefault(key) }
|
||||
}
|
||||
|
||||
override fun expressOnrampSyncState(key: CommonSyncStateKey): Flow<CommonSyncState> {
|
||||
return expressOnrampStore.data.map { map -> map.getOrDefault(key) }
|
||||
}
|
||||
|
||||
override suspend fun updateExpressExchangeSyncState(
|
||||
key: CommonSyncStateKey,
|
||||
value: CommonSyncState,
|
||||
): CommonSyncState {
|
||||
return expressExchangeStore.updateData { map -> map.plus(key to value) }
|
||||
.getOrDefault(key)
|
||||
}
|
||||
|
||||
override suspend fun updateExpressOnrampSyncState(
|
||||
key: CommonSyncStateKey,
|
||||
value: CommonSyncState,
|
||||
): CommonSyncState {
|
||||
return expressOnrampStore.updateData { map -> map.plus(key to value) }
|
||||
.getOrDefault(key)
|
||||
}
|
||||
|
||||
private fun Map<CommonSyncStateKey, CommonSyncState>.getOrDefault(key: CommonSyncStateKey): CommonSyncState =
|
||||
this.getOrDefault(key, CommonSyncState.default(key))
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.datasource.local.txhistory.store
|
||||
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class CommonSyncStateKey(
|
||||
val accountId: AccountId,
|
||||
val address: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class CommonSyncState(
|
||||
val accountId: AccountId,
|
||||
val address: String,
|
||||
val isInitialCompleted: Boolean,
|
||||
val cursor: String?,
|
||||
) {
|
||||
companion object {
|
||||
fun default(key: CommonSyncStateKey) = CommonSyncState(
|
||||
accountId = key.accountId,
|
||||
address = key.address,
|
||||
isInitialCompleted = false,
|
||||
cursor = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.datasource.local.txhistory.store
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface TxHistoryStore {
|
||||
|
||||
fun expressExchangeSyncState(key: CommonSyncStateKey): Flow<CommonSyncState>
|
||||
fun expressOnrampSyncState(key: CommonSyncStateKey): Flow<CommonSyncState>
|
||||
|
||||
suspend fun updateExpressExchangeSyncState(key: CommonSyncStateKey, value: CommonSyncState): CommonSyncState
|
||||
suspend fun updateExpressOnrampSyncState(key: CommonSyncStateKey, value: CommonSyncState): CommonSyncState
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package com.tangem.datasource.utils
|
||||
|
||||
import androidx.datastore.core.CorruptionException
|
||||
import androidx.datastore.core.Serializer
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonBuilder
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
/**
|
||||
* Kotlinx Serialization serializer for [androidx.datastore.core.DataStore]
|
||||
*
|
||||
*/
|
||||
class KotlinxDataStoreSerializer<T>(
|
||||
override val defaultValue: T,
|
||||
private val serializer: KSerializer<T>,
|
||||
private val json: Json = DefaultJson,
|
||||
) : Serializer<T> {
|
||||
|
||||
override suspend fun readFrom(input: InputStream): T {
|
||||
return try {
|
||||
input.bufferedReader().use { reader ->
|
||||
json.decodeFromString(
|
||||
deserializer = serializer,
|
||||
string = reader.readText(),
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
throw CorruptionException("Failed to deserialize data", e)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun writeTo(t: T, output: OutputStream) {
|
||||
output.bufferedWriter().use { writer ->
|
||||
writer.write(
|
||||
json.encodeToString(
|
||||
serializer = serializer,
|
||||
value = t,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val DefaultJson = Json {
|
||||
ignoreUnknownKeys = true
|
||||
encodeDefaults = true
|
||||
}
|
||||
|
||||
fun jsonBuilder(builderAction: JsonBuilder.() -> Unit): Json {
|
||||
return Json(DefaultJson, builderAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue