From d64efbeb528c7de5a77005a8c14e58c41f686505 Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Wed, 3 Jun 2026 16:12:51 +0800 Subject: [PATCH 1/7] fix(wallet): limit timeout retries and pending status --- .../android/ui/wallet/TransactionInterface.kt | 54 ++++++++----- .../android/ui/wallet/WalletViewModel.kt | 6 ++ .../java/one/mixin/android/vo/SnapshotItem.kt | 4 + .../main/res/layout/fragment_transaction.xml | 2 +- .../one/mixin/android/vo/SnapshotItemTest.kt | 78 +++++++++++++++++++ 5 files changed, 122 insertions(+), 22 deletions(-) create mode 100644 app/src/test/java/one/mixin/android/vo/SnapshotItemTest.kt diff --git a/app/src/main/java/one/mixin/android/ui/wallet/TransactionInterface.kt b/app/src/main/java/one/mixin/android/ui/wallet/TransactionInterface.kt index 7ed0852e30..b96198b88f 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/TransactionInterface.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/TransactionInterface.kt @@ -34,6 +34,7 @@ import one.mixin.android.ui.home.inscription.InscriptionActivity import one.mixin.android.vo.Fiats import one.mixin.android.vo.SnapshotItem import one.mixin.android.vo.Ticker +import one.mixin.android.vo.safe.RawTransaction import one.mixin.android.vo.safe.SafeSnapshotType import one.mixin.android.vo.safe.TokenItem import one.mixin.android.widget.linktext.RoundBackgroundColorSpan @@ -61,7 +62,8 @@ interface TransactionInterface { contentBinding.avatarVa.setOnClickListener { clickAvatar(fragment, asset, snapshot.inscriptionHash) } - updateUI(fragment, contentBinding, asset, snapshot) + val rawTransaction = snapshot.traceId?.let { walletViewModel.findRawTransaction(it) } + updateUI(fragment, contentBinding, asset, snapshot, rawTransaction) fetchThatTimePrice( fragment, lifecycleScope, @@ -77,6 +79,7 @@ interface TransactionInterface { walletViewModel, snapshot, asset, + rawTransaction, ) } } @@ -87,23 +90,27 @@ interface TransactionInterface { contentBinding.avatarVa.setOnClickListener { clickAvatar(fragment, tokenItem, snapshotItem.inscriptionHash) } - updateUI(fragment, contentBinding, tokenItem, snapshotItem) - fetchThatTimePrice( - fragment, - lifecycleScope, - walletViewModel, - contentBinding, - tokenItem.assetId, - snapshotItem, - ) - refreshIncompleteSnapshot( - fragment, - contentBinding, - lifecycleScope, - walletViewModel, - snapshotItem, - tokenItem, - ) + lifecycleScope.launch { + val rawTransaction = snapshotItem.traceId?.let { walletViewModel.findRawTransaction(it) } + updateUI(fragment, contentBinding, tokenItem, snapshotItem, rawTransaction) + fetchThatTimePrice( + fragment, + lifecycleScope, + walletViewModel, + contentBinding, + tokenItem.assetId, + snapshotItem, + ) + refreshIncompleteSnapshot( + fragment, + contentBinding, + lifecycleScope, + walletViewModel, + snapshotItem, + tokenItem, + rawTransaction, + ) + } } } @@ -286,12 +293,14 @@ interface TransactionInterface { contentBinding: FragmentTransactionBinding, asset: TokenItem, snapshot: SnapshotItem, + rawTransaction: RawTransaction? = null, ) { if (checkDestroyed(fragment)) return contentBinding.apply { val amountVal = snapshot.amount.toFloatOrNull() val isPositive = if (amountVal == null) false else amountVal > 0 + val showPendingHash = snapshot.shouldShowPendingHash(rawTransaction) if (snapshot.inscriptionHash.isNullOrEmpty()) { avatarVa.displayedChild = 0 contentBinding.avatar.loadToken(asset) @@ -326,7 +335,7 @@ interface TransactionInterface { val amountColor = fragment.resources.getColor( when { - snapshot.type == SafeSnapshotType.pending.name -> { + snapshot.type == SafeSnapshotType.pending.name || showPendingHash -> { R.color.wallet_text_gray } isPositive -> { @@ -425,7 +434,9 @@ interface TransactionInterface { if (snapshot.withdrawal != null) { hashLl.isVisible = true hashTitle.text = fragment.getString(R.string.withdrawal_hash) - if (snapshot.withdrawal.withdrawalHash.isBlank()) { + if (showPendingHash) { + hashTv.text = fragment.getString(R.string.State_Pending) + } else if (snapshot.withdrawal.withdrawalHash.isBlank()) { hashTv.text = fragment.getString(R.string.withdrawal_pending) } else { hashTv.text = snapshot.withdrawal.withdrawalHash @@ -463,12 +474,13 @@ interface TransactionInterface { walletViewModel: WalletViewModel, snapshot: SnapshotItem, asset: TokenItem, + rawTransaction: RawTransaction? = null, ) { if (snapshot.isDataIncomplete()) { lifecycleScope.launch { walletViewModel.refreshSnapshot(snapshot.snapshotId)?.let { it.label = snapshot.label // Saving temporary variables - updateUI(fragment, contentBinding, asset, it) + updateUI(fragment, contentBinding, asset, it, rawTransaction) } } } diff --git a/app/src/main/java/one/mixin/android/ui/wallet/WalletViewModel.kt b/app/src/main/java/one/mixin/android/ui/wallet/WalletViewModel.kt index 96832c1a90..24b1f5670d 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/WalletViewModel.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/WalletViewModel.kt @@ -57,6 +57,7 @@ import one.mixin.android.vo.UtxoItem import one.mixin.android.vo.market.Market import one.mixin.android.vo.market.MarketItem import one.mixin.android.vo.safe.Output +import one.mixin.android.vo.safe.RawTransaction import one.mixin.android.vo.safe.SafeSnapshot import one.mixin.android.vo.safe.TokenItem import one.mixin.android.vo.sumsub.ProfileResponse @@ -319,6 +320,11 @@ internal constructor( suspend fun findSnapshot(snapshotId: String): SnapshotItem? = tokenRepository.findSnapshotById(snapshotId) + suspend fun findRawTransaction(traceId: String): RawTransaction? = + withContext(Dispatchers.IO) { + tokenRepository.findRawTransaction(traceId) + } + suspend fun profile(): MixinResponse = tokenRepository.profile() suspend fun fetchSessionsSuspend(ids: List) = userRepository.fetchSessionsSuspend(ids) diff --git a/app/src/main/java/one/mixin/android/vo/SnapshotItem.kt b/app/src/main/java/one/mixin/android/vo/SnapshotItem.kt index d7996d72da..0588b498f3 100644 --- a/app/src/main/java/one/mixin/android/vo/SnapshotItem.kt +++ b/app/src/main/java/one/mixin/android/vo/SnapshotItem.kt @@ -12,6 +12,8 @@ import kotlinx.serialization.SerialName import one.mixin.android.extension.hexString import one.mixin.android.extension.isByteArrayValidUtf8 import one.mixin.android.extension.isValidHex +import one.mixin.android.vo.safe.OutputState +import one.mixin.android.vo.safe.RawTransaction import one.mixin.android.vo.safe.SafeDeposit import one.mixin.android.vo.safe.SafeSnapshotType import one.mixin.android.vo.safe.SafeWithdrawal @@ -159,6 +161,8 @@ data class SnapshotItem( } fun isPendingWithdrawal() = withdrawal != null && withdrawal.withdrawalHash.isNullOrBlank() + + fun shouldShowPendingHash(rawTransaction: RawTransaction?) = rawTransaction?.state == OutputState.unspent } @Parcelize diff --git a/app/src/main/res/layout/fragment_transaction.xml b/app/src/main/res/layout/fragment_transaction.xml index a3dc632e0c..dfcc5b60c7 100644 --- a/app/src/main/res/layout/fragment_transaction.xml +++ b/app/src/main/res/layout/fragment_transaction.xml @@ -353,4 +353,4 @@ - \ No newline at end of file + diff --git a/app/src/test/java/one/mixin/android/vo/SnapshotItemTest.kt b/app/src/test/java/one/mixin/android/vo/SnapshotItemTest.kt new file mode 100644 index 0000000000..a1445efba0 --- /dev/null +++ b/app/src/test/java/one/mixin/android/vo/SnapshotItemTest.kt @@ -0,0 +1,78 @@ +package one.mixin.android.vo + +import kotlin.test.Test +import kotlin.test.assertEquals +import one.mixin.android.vo.safe.OutputState +import one.mixin.android.vo.safe.RawTransaction +import one.mixin.android.vo.safe.RawTransactionType +import one.mixin.android.vo.safe.SafeSnapshotType + +class SnapshotItemTest { + @Test + fun `pending hash is shown when raw transaction is unspent`() { + val snapshot = snapshotItem(type = SafeSnapshotType.snapshot.name, traceId = "trace-id") + val rawTransaction = rawTransaction("trace-id", OutputState.unspent) + + assertEquals(true, snapshot.shouldShowPendingHash(rawTransaction)) + } + + @Test + fun `pending hash is hidden when raw transaction was sent`() { + val snapshot = snapshotItem(type = SafeSnapshotType.snapshot.name, traceId = "trace-id") + val rawTransaction = rawTransaction("trace-id", OutputState.signed) + + assertEquals(false, snapshot.shouldShowPendingHash(rawTransaction)) + } + + @Test + fun `pending hash is hidden when raw transaction is missing`() { + val snapshot = snapshotItem(type = SafeSnapshotType.withdrawal.name, traceId = "trace-id") + + assertEquals(false, snapshot.shouldShowPendingHash(null)) + } + + private fun rawTransaction( + requestId: String, + state: OutputState, + ) = RawTransaction( + requestId = requestId, + rawTransaction = "raw", + receiverId = "", + type = RawTransactionType.TRANSFER, + state = state, + createdAt = "2026-06-03T00:00:00Z", + inscriptionHash = null, + ) + + private fun snapshotItem( + type: String, + traceId: String?, + ) = SnapshotItem( + snapshotId = "snapshot-id", + type = type, + assetId = "asset-id", + amount = "-1", + createdAt = "2026-06-03T00:00:00Z", + opponentId = "opponent-id", + opponentFullName = null, + transactionHash = "hash", + memo = null, + assetSymbol = "XIN", + confirmations = null, + avatarUrl = null, + assetConfirmations = 0, + traceId = traceId, + openingBalance = null, + closingBalance = null, + deposit = null, + withdrawal = null, + label = null, + inscriptionHash = null, + collectionHash = null, + name = null, + sequence = null, + contentType = null, + contentUrl = null, + iconUrl = null, + ) +} From 3534c04138d59b5bb3656262f834925b6d766507 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 3 Jun 2026 17:11:39 +0800 Subject: [PATCH 2/7] fix(wallet): show raw transaction pending spinner --- .../android/ui/wallet/TransactionInterface.kt | 6 ++-- .../main/res/layout/fragment_transaction.xml | 34 ++++++++++++++++--- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/one/mixin/android/ui/wallet/TransactionInterface.kt b/app/src/main/java/one/mixin/android/ui/wallet/TransactionInterface.kt index b96198b88f..04d840bddb 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/TransactionInterface.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/TransactionInterface.kt @@ -361,6 +361,7 @@ interface TransactionInterface { transactionIdTv.text = snapshot.snapshotId transactionHashLayout.isVisible = !snapshot.transactionHash.isNullOrBlank() transactionHashTv.text = snapshot.transactionHash + hashPendingPb.isVisible = false dateTv.text = snapshot.createdAt.fullDate() memoLl.isVisible = snapshot.formatMemo != null memoTv.text = snapshot.formatMemo?.utf ?: snapshot.formatMemo?.hex @@ -434,9 +435,8 @@ interface TransactionInterface { if (snapshot.withdrawal != null) { hashLl.isVisible = true hashTitle.text = fragment.getString(R.string.withdrawal_hash) - if (showPendingHash) { - hashTv.text = fragment.getString(R.string.State_Pending) - } else if (snapshot.withdrawal.withdrawalHash.isBlank()) { + hashPendingPb.isVisible = showPendingHash + if (snapshot.withdrawal.withdrawalHash.isBlank()) { hashTv.text = fragment.getString(R.string.withdrawal_pending) } else { hashTv.text = snapshot.withdrawal.withdrawalHash diff --git a/app/src/main/res/layout/fragment_transaction.xml b/app/src/main/res/layout/fragment_transaction.xml index dfcc5b60c7..3cdfd0f2f6 100644 --- a/app/src/main/res/layout/fragment_transaction.xml +++ b/app/src/main/res/layout/fragment_transaction.xml @@ -290,12 +290,38 @@ android:layout_height="wrap_content" android:text="@string/deposit_hash" /> - + android:gravity="center_vertical" + android:layout_marginStart="20dp" + android:layout_marginEnd="20dp" + android:layout_marginTop="5dp" + android:layout_marginBottom="12dp" + android:orientation="horizontal"> + + + + + Date: Thu, 4 Jun 2026 12:34:50 +0800 Subject: [PATCH 3/7] refactor(db): rename raw transaction states (signed/spent) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add RawTransactionState enum: signed (已签名待广播) / spent (已广播终态) - Add RawTransactionStateConverter for Room - Update RawTransaction.kt to use new enum - Update RawTransactionDao queries (unspent → signed) - Update BottomSheetViewModel: write signed, mark spent after broadcast - Update RestoreTransactionJob: mark spent after restore - Database migration: signed→spent first, then unspent→signed - Register converter in MixinDatabase - Bump database version --- .../70.json | 3346 +++++++++++++++++ .../main/java/one/mixin/android/Constants.kt | 2 +- .../one/mixin/android/db/MixinDatabase.kt | 6 +- .../android/db/MixinDatabaseMigrations.kt | 8 + .../one/mixin/android/db/RawTransactionDao.kt | 4 +- .../converter/RawTransactionStateConverter.kt | 17 + .../android/job/RestoreTransactionJob.kt | 14 +- .../android/ui/common/BottomSheetViewModel.kt | 33 +- .../mixin/android/vo/safe/RawTransaction.kt | 2 +- .../android/vo/safe/RawTransactionState.kt | 6 + 10 files changed, 3410 insertions(+), 28 deletions(-) create mode 100644 app/schemas/one.mixin.android.db.MixinDatabase/70.json create mode 100644 app/src/main/java/one/mixin/android/db/converter/RawTransactionStateConverter.kt create mode 100644 app/src/main/java/one/mixin/android/vo/safe/RawTransactionState.kt diff --git a/app/schemas/one.mixin.android.db.MixinDatabase/70.json b/app/schemas/one.mixin.android.db.MixinDatabase/70.json new file mode 100644 index 0000000000..4be7a7a3ef --- /dev/null +++ b/app/schemas/one.mixin.android.db.MixinDatabase/70.json @@ -0,0 +1,3346 @@ +{ + "formatVersion": 1, + "database": { + "version": 70, + "identityHash": "a49373f3e5e79b69c09e11d526d973d6", + "entities": [ + { + "tableName": "users", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_id` TEXT NOT NULL, `identity_number` TEXT NOT NULL, `relationship` TEXT NOT NULL, `biography` TEXT NOT NULL, `full_name` TEXT, `avatar_url` TEXT, `phone` TEXT, `is_verified` INTEGER, `created_at` TEXT, `mute_until` TEXT, `has_pin` INTEGER, `app_id` TEXT, `is_scam` INTEGER, `is_deactivated` INTEGER, `membership` TEXT, PRIMARY KEY(`user_id`))", + "fields": [ + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "identityNumber", + "columnName": "identity_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "relationship", + "columnName": "relationship", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "biography", + "columnName": "biography", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT" + }, + { + "fieldPath": "avatarUrl", + "columnName": "avatar_url", + "affinity": "TEXT" + }, + { + "fieldPath": "phone", + "columnName": "phone", + "affinity": "TEXT" + }, + { + "fieldPath": "isVerified", + "columnName": "is_verified", + "affinity": "INTEGER" + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT" + }, + { + "fieldPath": "muteUntil", + "columnName": "mute_until", + "affinity": "TEXT" + }, + { + "fieldPath": "hasPin", + "columnName": "has_pin", + "affinity": "INTEGER" + }, + { + "fieldPath": "appId", + "columnName": "app_id", + "affinity": "TEXT" + }, + { + "fieldPath": "isScam", + "columnName": "is_scam", + "affinity": "INTEGER" + }, + { + "fieldPath": "isDeactivated", + "columnName": "is_deactivated", + "affinity": "INTEGER" + }, + { + "fieldPath": "membership", + "columnName": "membership", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "user_id" + ] + }, + "indices": [ + { + "name": "index_users_relationship_full_name", + "unique": false, + "columnNames": [ + "relationship", + "full_name" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_users_relationship_full_name` ON `${TABLE_NAME}` (`relationship`, `full_name`)" + } + ] + }, + { + "tableName": "conversations", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`conversation_id` TEXT NOT NULL, `owner_id` TEXT, `category` TEXT, `name` TEXT, `icon_url` TEXT, `announcement` TEXT, `code_url` TEXT, `pay_type` TEXT, `created_at` TEXT NOT NULL, `pin_time` TEXT, `last_message_id` TEXT, `last_read_message_id` TEXT, `unseen_message_count` INTEGER, `status` INTEGER NOT NULL, `draft` TEXT, `mute_until` TEXT, `last_message_created_at` TEXT, `expire_in` INTEGER, PRIMARY KEY(`conversation_id`))", + "fields": [ + { + "fieldPath": "conversationId", + "columnName": "conversation_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ownerId", + "columnName": "owner_id", + "affinity": "TEXT" + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT" + }, + { + "fieldPath": "iconUrl", + "columnName": "icon_url", + "affinity": "TEXT" + }, + { + "fieldPath": "announcement", + "columnName": "announcement", + "affinity": "TEXT" + }, + { + "fieldPath": "codeUrl", + "columnName": "code_url", + "affinity": "TEXT" + }, + { + "fieldPath": "payType", + "columnName": "pay_type", + "affinity": "TEXT" + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pinTime", + "columnName": "pin_time", + "affinity": "TEXT" + }, + { + "fieldPath": "lastMessageId", + "columnName": "last_message_id", + "affinity": "TEXT" + }, + { + "fieldPath": "lastReadMessageId", + "columnName": "last_read_message_id", + "affinity": "TEXT" + }, + { + "fieldPath": "unseenMessageCount", + "columnName": "unseen_message_count", + "affinity": "INTEGER" + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "draft", + "columnName": "draft", + "affinity": "TEXT" + }, + { + "fieldPath": "muteUntil", + "columnName": "mute_until", + "affinity": "TEXT" + }, + { + "fieldPath": "lastMessageCreatedAt", + "columnName": "last_message_created_at", + "affinity": "TEXT" + }, + { + "fieldPath": "expireIn", + "columnName": "expire_in", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "conversation_id" + ] + }, + "indices": [ + { + "name": "index_conversations_pin_time_last_message_created_at", + "unique": false, + "columnNames": [ + "pin_time", + "last_message_created_at" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_conversations_pin_time_last_message_created_at` ON `${TABLE_NAME}` (`pin_time`, `last_message_created_at`)" + } + ] + }, + { + "tableName": "messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `conversation_id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `category` TEXT NOT NULL, `content` TEXT, `media_url` TEXT, `media_mime_type` TEXT, `media_size` INTEGER, `media_duration` TEXT, `media_width` INTEGER, `media_height` INTEGER, `media_hash` TEXT, `thumb_image` TEXT, `thumb_url` TEXT, `media_key` BLOB, `media_digest` BLOB, `media_status` TEXT, `status` TEXT NOT NULL, `created_at` TEXT NOT NULL, `action` TEXT, `participant_id` TEXT, `snapshot_id` TEXT, `hyperlink` TEXT, `name` TEXT, `album_id` TEXT, `sticker_id` TEXT, `shared_user_id` TEXT, `media_waveform` BLOB, `media_mine_type` TEXT, `quote_message_id` TEXT, `quote_content` TEXT, `caption` TEXT, PRIMARY KEY(`id`), FOREIGN KEY(`conversation_id`) REFERENCES `conversations`(`conversation_id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "messageId", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conversationId", + "columnName": "conversation_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT" + }, + { + "fieldPath": "mediaUrl", + "columnName": "media_url", + "affinity": "TEXT" + }, + { + "fieldPath": "mediaMimeType", + "columnName": "media_mime_type", + "affinity": "TEXT" + }, + { + "fieldPath": "mediaSize", + "columnName": "media_size", + "affinity": "INTEGER" + }, + { + "fieldPath": "mediaDuration", + "columnName": "media_duration", + "affinity": "TEXT" + }, + { + "fieldPath": "mediaWidth", + "columnName": "media_width", + "affinity": "INTEGER" + }, + { + "fieldPath": "mediaHeight", + "columnName": "media_height", + "affinity": "INTEGER" + }, + { + "fieldPath": "mediaHash", + "columnName": "media_hash", + "affinity": "TEXT" + }, + { + "fieldPath": "thumbImage", + "columnName": "thumb_image", + "affinity": "TEXT" + }, + { + "fieldPath": "thumbUrl", + "columnName": "thumb_url", + "affinity": "TEXT" + }, + { + "fieldPath": "mediaKey", + "columnName": "media_key", + "affinity": "BLOB" + }, + { + "fieldPath": "mediaDigest", + "columnName": "media_digest", + "affinity": "BLOB" + }, + { + "fieldPath": "mediaStatus", + "columnName": "media_status", + "affinity": "TEXT" + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "action", + "columnName": "action", + "affinity": "TEXT" + }, + { + "fieldPath": "participantId", + "columnName": "participant_id", + "affinity": "TEXT" + }, + { + "fieldPath": "snapshotId", + "columnName": "snapshot_id", + "affinity": "TEXT" + }, + { + "fieldPath": "hyperlink", + "columnName": "hyperlink", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT" + }, + { + "fieldPath": "albumId", + "columnName": "album_id", + "affinity": "TEXT" + }, + { + "fieldPath": "stickerId", + "columnName": "sticker_id", + "affinity": "TEXT" + }, + { + "fieldPath": "sharedUserId", + "columnName": "shared_user_id", + "affinity": "TEXT" + }, + { + "fieldPath": "mediaWaveform", + "columnName": "media_waveform", + "affinity": "BLOB" + }, + { + "fieldPath": "mediaMineType", + "columnName": "media_mine_type", + "affinity": "TEXT" + }, + { + "fieldPath": "quoteMessageId", + "columnName": "quote_message_id", + "affinity": "TEXT" + }, + { + "fieldPath": "quoteContent", + "columnName": "quote_content", + "affinity": "TEXT" + }, + { + "fieldPath": "caption", + "columnName": "caption", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_messages_conversation_id_created_at", + "unique": false, + "columnNames": [ + "conversation_id", + "created_at" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_messages_conversation_id_created_at` ON `${TABLE_NAME}` (`conversation_id`, `created_at`)" + }, + { + "name": "index_messages_conversation_id_category", + "unique": false, + "columnNames": [ + "conversation_id", + "category" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_messages_conversation_id_category` ON `${TABLE_NAME}` (`conversation_id`, `category`)" + }, + { + "name": "index_messages_conversation_id_quote_message_id", + "unique": false, + "columnNames": [ + "conversation_id", + "quote_message_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_messages_conversation_id_quote_message_id` ON `${TABLE_NAME}` (`conversation_id`, `quote_message_id`)" + }, + { + "name": "index_messages_conversation_id_status_user_id_created_at", + "unique": false, + "columnNames": [ + "conversation_id", + "status", + "user_id", + "created_at" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_messages_conversation_id_status_user_id_created_at` ON `${TABLE_NAME}` (`conversation_id`, `status`, `user_id`, `created_at`)" + } + ], + "foreignKeys": [ + { + "table": "conversations", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "conversation_id" + ], + "referencedColumns": [ + "conversation_id" + ] + } + ] + }, + { + "tableName": "participants", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`conversation_id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `role` TEXT NOT NULL, `created_at` TEXT NOT NULL, PRIMARY KEY(`conversation_id`, `user_id`), FOREIGN KEY(`conversation_id`) REFERENCES `conversations`(`conversation_id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "conversationId", + "columnName": "conversation_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "conversation_id", + "user_id" + ] + }, + "foreignKeys": [ + { + "table": "conversations", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "conversation_id" + ], + "referencedColumns": [ + "conversation_id" + ] + } + ] + }, + { + "tableName": "participant_session", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`conversation_id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `session_id` TEXT NOT NULL, `sent_to_server` INTEGER, `created_at` TEXT, `public_key` TEXT, PRIMARY KEY(`conversation_id`, `user_id`, `session_id`))", + "fields": [ + { + "fieldPath": "conversationId", + "columnName": "conversation_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sessionId", + "columnName": "session_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sentToServer", + "columnName": "sent_to_server", + "affinity": "INTEGER" + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT" + }, + { + "fieldPath": "publicKey", + "columnName": "public_key", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "conversation_id", + "user_id", + "session_id" + ] + } + }, + { + "tableName": "offsets", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `timestamp` TEXT NOT NULL, PRIMARY KEY(`key`))", + "fields": [ + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "key" + ] + } + }, + { + "tableName": "assets", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`asset_id` TEXT NOT NULL, `symbol` TEXT NOT NULL, `name` TEXT NOT NULL, `icon_url` TEXT NOT NULL, `balance` TEXT NOT NULL, `destination` TEXT NOT NULL, `tag` TEXT, `price_btc` TEXT NOT NULL, `price_usd` TEXT NOT NULL, `chain_id` TEXT NOT NULL, `change_usd` TEXT NOT NULL, `change_btc` TEXT NOT NULL, `confirmations` INTEGER NOT NULL, `asset_key` TEXT, `reserve` TEXT, `deposit_entries` TEXT, `withdrawal_memo_possibility` TEXT, PRIMARY KEY(`asset_id`))", + "fields": [ + { + "fieldPath": "assetId", + "columnName": "asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "iconUrl", + "columnName": "icon_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "balance", + "columnName": "balance", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "destination", + "columnName": "destination", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "tag", + "columnName": "tag", + "affinity": "TEXT" + }, + { + "fieldPath": "priceBtc", + "columnName": "price_btc", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "priceUsd", + "columnName": "price_usd", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "chainId", + "columnName": "chain_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "changeUsd", + "columnName": "change_usd", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "changeBtc", + "columnName": "change_btc", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "confirmations", + "columnName": "confirmations", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "assetKey", + "columnName": "asset_key", + "affinity": "TEXT" + }, + { + "fieldPath": "reserve", + "columnName": "reserve", + "affinity": "TEXT" + }, + { + "fieldPath": "depositEntries", + "columnName": "deposit_entries", + "affinity": "TEXT" + }, + { + "fieldPath": "withdrawalMemoPossibility", + "columnName": "withdrawal_memo_possibility", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "asset_id" + ] + } + }, + { + "tableName": "assets_extra", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`asset_id` TEXT NOT NULL, `hidden` INTEGER, PRIMARY KEY(`asset_id`))", + "fields": [ + { + "fieldPath": "assetId", + "columnName": "asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hidden", + "columnName": "hidden", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "asset_id" + ] + } + }, + { + "tableName": "tokens_extra", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`asset_id` TEXT NOT NULL, `kernel_asset_id` TEXT NOT NULL, `hidden` INTEGER, `balance` TEXT, `updated_at` TEXT NOT NULL, PRIMARY KEY(`asset_id`))", + "fields": [ + { + "fieldPath": "assetId", + "columnName": "asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "asset", + "columnName": "kernel_asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hidden", + "columnName": "hidden", + "affinity": "INTEGER" + }, + { + "fieldPath": "balance", + "columnName": "balance", + "affinity": "TEXT" + }, + { + "fieldPath": "updatedAt", + "columnName": "updated_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "asset_id" + ] + }, + "indices": [ + { + "name": "index_tokens_extra_kernel_asset_id", + "unique": false, + "columnNames": [ + "kernel_asset_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_tokens_extra_kernel_asset_id` ON `${TABLE_NAME}` (`kernel_asset_id`)" + } + ] + }, + { + "tableName": "snapshots", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`snapshot_id` TEXT NOT NULL, `type` TEXT NOT NULL, `asset_id` TEXT NOT NULL, `amount` TEXT NOT NULL, `created_at` TEXT NOT NULL, `opponent_id` TEXT, `trace_id` TEXT, `transaction_hash` TEXT, `sender` TEXT, `receiver` TEXT, `memo` TEXT, `confirmations` INTEGER, `snapshot_hash` TEXT, `opening_balance` TEXT, `closing_balance` TEXT, PRIMARY KEY(`snapshot_id`))", + "fields": [ + { + "fieldPath": "snapshotId", + "columnName": "snapshot_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "assetId", + "columnName": "asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "opponentId", + "columnName": "opponent_id", + "affinity": "TEXT" + }, + { + "fieldPath": "traceId", + "columnName": "trace_id", + "affinity": "TEXT" + }, + { + "fieldPath": "transactionHash", + "columnName": "transaction_hash", + "affinity": "TEXT" + }, + { + "fieldPath": "sender", + "columnName": "sender", + "affinity": "TEXT" + }, + { + "fieldPath": "receiver", + "columnName": "receiver", + "affinity": "TEXT" + }, + { + "fieldPath": "memo", + "columnName": "memo", + "affinity": "TEXT" + }, + { + "fieldPath": "confirmations", + "columnName": "confirmations", + "affinity": "INTEGER" + }, + { + "fieldPath": "snapshotHash", + "columnName": "snapshot_hash", + "affinity": "TEXT" + }, + { + "fieldPath": "openingBalance", + "columnName": "opening_balance", + "affinity": "TEXT" + }, + { + "fieldPath": "closingBalance", + "columnName": "closing_balance", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "snapshot_id" + ] + } + }, + { + "tableName": "messages_history", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_id` TEXT NOT NULL, PRIMARY KEY(`message_id`))", + "fields": [ + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "message_id" + ] + } + }, + { + "tableName": "sent_sender_keys", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`conversation_id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `sent_to_server` INTEGER NOT NULL, `sender_key_id` INTEGER, `created_at` TEXT, PRIMARY KEY(`conversation_id`, `user_id`))", + "fields": [ + { + "fieldPath": "conversationId", + "columnName": "conversation_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sentToServer", + "columnName": "sent_to_server", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderKeyId", + "columnName": "sender_key_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "conversation_id", + "user_id" + ] + } + }, + { + "tableName": "stickers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sticker_id` TEXT NOT NULL, `album_id` TEXT, `name` TEXT NOT NULL, `asset_url` TEXT NOT NULL, `asset_type` TEXT NOT NULL, `asset_width` INTEGER NOT NULL, `asset_height` INTEGER NOT NULL, `created_at` TEXT NOT NULL, `last_use_at` TEXT, PRIMARY KEY(`sticker_id`))", + "fields": [ + { + "fieldPath": "stickerId", + "columnName": "sticker_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "albumId", + "columnName": "album_id", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "assetUrl", + "columnName": "asset_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "assetType", + "columnName": "asset_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "assetWidth", + "columnName": "asset_width", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "assetHeight", + "columnName": "asset_height", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastUseAt", + "columnName": "last_use_at", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "sticker_id" + ] + } + }, + { + "tableName": "sticker_albums", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`album_id` TEXT NOT NULL, `name` TEXT NOT NULL, `icon_url` TEXT NOT NULL, `created_at` TEXT NOT NULL, `update_at` TEXT NOT NULL, `user_id` TEXT NOT NULL, `category` TEXT NOT NULL, `description` TEXT NOT NULL, `banner` TEXT, `is_verified` INTEGER NOT NULL, `ordered_at` INTEGER NOT NULL DEFAULT 0, `added` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`album_id`))", + "fields": [ + { + "fieldPath": "albumId", + "columnName": "album_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "iconUrl", + "columnName": "icon_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updateAt", + "columnName": "update_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "banner", + "columnName": "banner", + "affinity": "TEXT" + }, + { + "fieldPath": "isVerified", + "columnName": "is_verified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "orderedAt", + "columnName": "ordered_at", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "added", + "columnName": "added", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "album_id" + ] + } + }, + { + "tableName": "apps", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`app_id` TEXT NOT NULL, `app_number` TEXT NOT NULL, `home_uri` TEXT NOT NULL, `redirect_uri` TEXT NOT NULL, `name` TEXT NOT NULL, `icon_url` TEXT NOT NULL, `category` TEXT, `description` TEXT NOT NULL, `app_secret` TEXT NOT NULL, `capabilities` TEXT, `creator_id` TEXT NOT NULL, `resource_patterns` TEXT, `updated_at` TEXT, PRIMARY KEY(`app_id`))", + "fields": [ + { + "fieldPath": "appId", + "columnName": "app_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "appNumber", + "columnName": "app_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "homeUri", + "columnName": "home_uri", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "redirectUri", + "columnName": "redirect_uri", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "iconUrl", + "columnName": "icon_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT" + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "appSecret", + "columnName": "app_secret", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "capabilities", + "columnName": "capabilities", + "affinity": "TEXT" + }, + { + "fieldPath": "creatorId", + "columnName": "creator_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resourcePatterns", + "columnName": "resource_patterns", + "affinity": "TEXT" + }, + { + "fieldPath": "updatedAt", + "columnName": "updated_at", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "app_id" + ] + } + }, + { + "tableName": "hyperlinks", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`hyperlink` TEXT NOT NULL, `site_name` TEXT NOT NULL, `site_title` TEXT NOT NULL, `site_description` TEXT, `site_image` TEXT, PRIMARY KEY(`hyperlink`))", + "fields": [ + { + "fieldPath": "hyperlink", + "columnName": "hyperlink", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "siteName", + "columnName": "site_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "siteTitle", + "columnName": "site_title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "siteDescription", + "columnName": "site_description", + "affinity": "TEXT" + }, + { + "fieldPath": "siteImage", + "columnName": "site_image", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "hyperlink" + ] + } + }, + { + "tableName": "flood_messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_id` TEXT NOT NULL, `data` TEXT NOT NULL, `created_at` TEXT NOT NULL, PRIMARY KEY(`message_id`))", + "fields": [ + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "message_id" + ] + } + }, + { + "tableName": "addresses", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`address_id` TEXT NOT NULL, `type` TEXT NOT NULL, `asset_id` TEXT NOT NULL, `chain_id` TEXT NOT NULL, `destination` TEXT NOT NULL, `label` TEXT NOT NULL, `updated_at` TEXT NOT NULL, `tag` TEXT, `dust` TEXT, PRIMARY KEY(`address_id`))", + "fields": [ + { + "fieldPath": "addressId", + "columnName": "address_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "assetId", + "columnName": "asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "chainId", + "columnName": "chain_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "destination", + "columnName": "destination", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "label", + "columnName": "label", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updated_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "tag", + "columnName": "tag", + "affinity": "TEXT" + }, + { + "fieldPath": "dust", + "columnName": "dust", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "address_id" + ] + }, + "indices": [ + { + "name": "index_addresses_chain_id_updated_at", + "unique": false, + "columnNames": [ + "chain_id", + "updated_at" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_addresses_chain_id_updated_at` ON `${TABLE_NAME}` (`chain_id`, `updated_at`)" + } + ] + }, + { + "tableName": "resend_messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `status` INTEGER NOT NULL, `created_at` TEXT NOT NULL, PRIMARY KEY(`message_id`, `user_id`))", + "fields": [ + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "message_id", + "user_id" + ] + } + }, + { + "tableName": "resend_session_messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `session_id` TEXT NOT NULL, `status` INTEGER NOT NULL, `created_at` TEXT NOT NULL, PRIMARY KEY(`message_id`, `user_id`, `session_id`))", + "fields": [ + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sessionId", + "columnName": "session_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "message_id", + "user_id", + "session_id" + ] + } + }, + { + "tableName": "sticker_relationships", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`album_id` TEXT NOT NULL, `sticker_id` TEXT NOT NULL, PRIMARY KEY(`album_id`, `sticker_id`))", + "fields": [ + { + "fieldPath": "albumId", + "columnName": "album_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "stickerId", + "columnName": "sticker_id", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "album_id", + "sticker_id" + ] + } + }, + { + "tableName": "top_assets", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`asset_id` TEXT NOT NULL, `symbol` TEXT NOT NULL, `name` TEXT NOT NULL, `icon_url` TEXT NOT NULL, `balance` TEXT NOT NULL, `destination` TEXT NOT NULL, `tag` TEXT, `price_btc` TEXT NOT NULL, `price_usd` TEXT NOT NULL, `chain_id` TEXT NOT NULL, `change_usd` TEXT NOT NULL, `change_btc` TEXT NOT NULL, `confirmations` INTEGER NOT NULL, PRIMARY KEY(`asset_id`))", + "fields": [ + { + "fieldPath": "assetId", + "columnName": "asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "iconUrl", + "columnName": "icon_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "balance", + "columnName": "balance", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "destination", + "columnName": "destination", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "tag", + "columnName": "tag", + "affinity": "TEXT" + }, + { + "fieldPath": "priceBtc", + "columnName": "price_btc", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "priceUsd", + "columnName": "price_usd", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "chainId", + "columnName": "chain_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "changeUsd", + "columnName": "change_usd", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "changeBtc", + "columnName": "change_btc", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "confirmations", + "columnName": "confirmations", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "asset_id" + ] + } + }, + { + "tableName": "favorite_apps", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`app_id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `created_at` TEXT NOT NULL, PRIMARY KEY(`app_id`, `user_id`))", + "fields": [ + { + "fieldPath": "appId", + "columnName": "app_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "app_id", + "user_id" + ] + } + }, + { + "tableName": "jobs", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`job_id` TEXT NOT NULL, `action` TEXT NOT NULL, `created_at` TEXT NOT NULL, `order_id` INTEGER, `priority` INTEGER NOT NULL, `user_id` TEXT, `blaze_message` TEXT, `conversation_id` TEXT, `resend_message_id` TEXT, `run_count` INTEGER NOT NULL, PRIMARY KEY(`job_id`))", + "fields": [ + { + "fieldPath": "jobId", + "columnName": "job_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "action", + "columnName": "action", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "orderId", + "columnName": "order_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT" + }, + { + "fieldPath": "blazeMessage", + "columnName": "blaze_message", + "affinity": "TEXT" + }, + { + "fieldPath": "conversationId", + "columnName": "conversation_id", + "affinity": "TEXT" + }, + { + "fieldPath": "resendMessageId", + "columnName": "resend_message_id", + "affinity": "TEXT" + }, + { + "fieldPath": "runCount", + "columnName": "run_count", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "job_id" + ] + }, + "indices": [ + { + "name": "index_jobs_action", + "unique": false, + "columnNames": [ + "action" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_jobs_action` ON `${TABLE_NAME}` (`action`)" + } + ] + }, + { + "tableName": "message_mentions", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_id` TEXT NOT NULL, `conversation_id` TEXT NOT NULL, `mentions` TEXT NOT NULL, `has_read` INTEGER NOT NULL, PRIMARY KEY(`message_id`))", + "fields": [ + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conversationId", + "columnName": "conversation_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mentions", + "columnName": "mentions", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasRead", + "columnName": "has_read", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "message_id" + ] + }, + "indices": [ + { + "name": "index_message_mentions_conversation_id", + "unique": false, + "columnNames": [ + "conversation_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_message_mentions_conversation_id` ON `${TABLE_NAME}` (`conversation_id`)" + } + ] + }, + { + "tableName": "messages_fts4", + "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`message_id` TEXT NOT NULL, `content` TEXT, tokenize=unicode61, notindexed=`message_id`)", + "fields": [ + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [] + }, + "ftsVersion": "FTS4", + "ftsOptions": { + "tokenizer": "unicode61", + "tokenizerArgs": [], + "contentTable": "", + "languageIdColumnName": "", + "matchInfo": "FTS4", + "notIndexedColumns": [ + "message_id" + ], + "prefixSizes": [], + "preferredOrder": "ASC" + }, + "contentSyncTriggers": [] + }, + { + "tableName": "circles", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`circle_id` TEXT NOT NULL, `name` TEXT NOT NULL, `created_at` TEXT NOT NULL, `ordered_at` TEXT, PRIMARY KEY(`circle_id`))", + "fields": [ + { + "fieldPath": "circleId", + "columnName": "circle_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "orderedAt", + "columnName": "ordered_at", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "circle_id" + ] + } + }, + { + "tableName": "circle_conversations", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`conversation_id` TEXT NOT NULL, `circle_id` TEXT NOT NULL, `user_id` TEXT, `created_at` TEXT NOT NULL, `pin_time` TEXT, PRIMARY KEY(`conversation_id`, `circle_id`))", + "fields": [ + { + "fieldPath": "conversationId", + "columnName": "conversation_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "circleId", + "columnName": "circle_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT" + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pinTime", + "columnName": "pin_time", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "conversation_id", + "circle_id" + ] + } + }, + { + "tableName": "traces", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`trace_id` TEXT NOT NULL, `asset_id` TEXT NOT NULL, `amount` TEXT NOT NULL, `opponent_id` TEXT, `destination` TEXT, `tag` TEXT, `snapshot_id` TEXT, `created_at` TEXT NOT NULL, PRIMARY KEY(`trace_id`))", + "fields": [ + { + "fieldPath": "traceId", + "columnName": "trace_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "assetId", + "columnName": "asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "opponentId", + "columnName": "opponent_id", + "affinity": "TEXT" + }, + { + "fieldPath": "destination", + "columnName": "destination", + "affinity": "TEXT" + }, + { + "fieldPath": "tag", + "columnName": "tag", + "affinity": "TEXT" + }, + { + "fieldPath": "snapshotId", + "columnName": "snapshot_id", + "affinity": "TEXT" + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "trace_id" + ] + } + }, + { + "tableName": "transcript_messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`transcript_id` TEXT NOT NULL, `message_id` TEXT NOT NULL, `user_id` TEXT, `user_full_name` TEXT, `category` TEXT NOT NULL, `created_at` TEXT NOT NULL, `content` TEXT, `media_url` TEXT, `media_name` TEXT, `media_size` INTEGER, `media_width` INTEGER, `media_height` INTEGER, `media_mime_type` TEXT, `media_duration` INTEGER, `media_status` TEXT, `media_waveform` BLOB, `thumb_image` TEXT, `thumb_url` TEXT, `media_key` BLOB, `media_digest` BLOB, `media_created_at` TEXT, `sticker_id` TEXT, `shared_user_id` TEXT, `mentions` TEXT, `quote_id` TEXT, `quote_content` TEXT, `caption` TEXT, PRIMARY KEY(`transcript_id`, `message_id`))", + "fields": [ + { + "fieldPath": "transcriptId", + "columnName": "transcript_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT" + }, + { + "fieldPath": "userFullName", + "columnName": "user_full_name", + "affinity": "TEXT" + }, + { + "fieldPath": "type", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT" + }, + { + "fieldPath": "mediaUrl", + "columnName": "media_url", + "affinity": "TEXT" + }, + { + "fieldPath": "mediaName", + "columnName": "media_name", + "affinity": "TEXT" + }, + { + "fieldPath": "mediaSize", + "columnName": "media_size", + "affinity": "INTEGER" + }, + { + "fieldPath": "mediaWidth", + "columnName": "media_width", + "affinity": "INTEGER" + }, + { + "fieldPath": "mediaHeight", + "columnName": "media_height", + "affinity": "INTEGER" + }, + { + "fieldPath": "mediaMimeType", + "columnName": "media_mime_type", + "affinity": "TEXT" + }, + { + "fieldPath": "mediaDuration", + "columnName": "media_duration", + "affinity": "INTEGER" + }, + { + "fieldPath": "mediaStatus", + "columnName": "media_status", + "affinity": "TEXT" + }, + { + "fieldPath": "mediaWaveform", + "columnName": "media_waveform", + "affinity": "BLOB" + }, + { + "fieldPath": "thumbImage", + "columnName": "thumb_image", + "affinity": "TEXT" + }, + { + "fieldPath": "thumbUrl", + "columnName": "thumb_url", + "affinity": "TEXT" + }, + { + "fieldPath": "mediaKey", + "columnName": "media_key", + "affinity": "BLOB" + }, + { + "fieldPath": "mediaDigest", + "columnName": "media_digest", + "affinity": "BLOB" + }, + { + "fieldPath": "mediaCreatedAt", + "columnName": "media_created_at", + "affinity": "TEXT" + }, + { + "fieldPath": "stickerId", + "columnName": "sticker_id", + "affinity": "TEXT" + }, + { + "fieldPath": "sharedUserId", + "columnName": "shared_user_id", + "affinity": "TEXT" + }, + { + "fieldPath": "mentions", + "columnName": "mentions", + "affinity": "TEXT" + }, + { + "fieldPath": "quoteId", + "columnName": "quote_id", + "affinity": "TEXT" + }, + { + "fieldPath": "quoteContent", + "columnName": "quote_content", + "affinity": "TEXT" + }, + { + "fieldPath": "caption", + "columnName": "caption", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "transcript_id", + "message_id" + ] + } + }, + { + "tableName": "pin_messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_id` TEXT NOT NULL, `conversation_id` TEXT NOT NULL, `created_at` TEXT NOT NULL, PRIMARY KEY(`message_id`))", + "fields": [ + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conversationId", + "columnName": "conversation_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "message_id" + ] + }, + "indices": [ + { + "name": "index_pin_messages_conversation_id_created_at", + "unique": false, + "columnNames": [ + "conversation_id", + "created_at" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_pin_messages_conversation_id_created_at` ON `${TABLE_NAME}` (`conversation_id`, `created_at`)" + } + ] + }, + { + "tableName": "properties", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT NOT NULL, `updated_at` TEXT NOT NULL, PRIMARY KEY(`key`))", + "fields": [ + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updated_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "key" + ] + } + }, + { + "tableName": "remote_messages_status", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_id` TEXT NOT NULL, `conversation_id` TEXT NOT NULL, `status` TEXT NOT NULL, PRIMARY KEY(`message_id`))", + "fields": [ + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conversationId", + "columnName": "conversation_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "message_id" + ] + }, + "indices": [ + { + "name": "index_remote_messages_status_conversation_id_status", + "unique": false, + "columnNames": [ + "conversation_id", + "status" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_remote_messages_status_conversation_id_status` ON `${TABLE_NAME}` (`conversation_id`, `status`)" + } + ] + }, + { + "tableName": "expired_messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`message_id` TEXT NOT NULL, `expire_in` INTEGER NOT NULL, `expire_at` INTEGER, PRIMARY KEY(`message_id`))", + "fields": [ + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "expireIn", + "columnName": "expire_in", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "expireAt", + "columnName": "expire_at", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "message_id" + ] + } + }, + { + "tableName": "conversation_ext", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`conversation_id` TEXT NOT NULL, `count` INTEGER NOT NULL DEFAULT 0, `created_at` TEXT NOT NULL, PRIMARY KEY(`conversation_id`))", + "fields": [ + { + "fieldPath": "conversationId", + "columnName": "conversation_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "count", + "columnName": "count", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "conversation_id" + ] + } + }, + { + "tableName": "chains", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`chain_id` TEXT NOT NULL, `name` TEXT NOT NULL, `symbol` TEXT NOT NULL, `icon_url` TEXT NOT NULL, `threshold` INTEGER NOT NULL, `withdrawal_memo_possibility` TEXT NOT NULL, PRIMARY KEY(`chain_id`))", + "fields": [ + { + "fieldPath": "chainId", + "columnName": "chain_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "iconUrl", + "columnName": "icon_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "threshold", + "columnName": "threshold", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "withdrawalMemoPossibility", + "columnName": "withdrawal_memo_possibility", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "chain_id" + ] + } + }, + { + "tableName": "outputs", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`output_id` TEXT NOT NULL, `transaction_hash` TEXT NOT NULL, `output_index` INTEGER NOT NULL, `asset` TEXT NOT NULL, `sequence` INTEGER NOT NULL, `amount` TEXT NOT NULL, `mask` TEXT NOT NULL, `keys` TEXT NOT NULL, `receivers` TEXT NOT NULL, `receivers_hash` TEXT NOT NULL, `receivers_threshold` INTEGER NOT NULL, `extra` TEXT NOT NULL, `state` TEXT NOT NULL, `created_at` TEXT NOT NULL, `updated_at` TEXT NOT NULL, `signed_by` TEXT NOT NULL, `signed_at` TEXT NOT NULL, `spent_at` TEXT NOT NULL, `inscription_hash` TEXT, PRIMARY KEY(`output_id`))", + "fields": [ + { + "fieldPath": "outputId", + "columnName": "output_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "transactionHash", + "columnName": "transaction_hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "outputIndex", + "columnName": "output_index", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "asset", + "columnName": "asset", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sequence", + "columnName": "sequence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mask", + "columnName": "mask", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "keys", + "columnName": "keys", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "receivers", + "columnName": "receivers", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "receiversHash", + "columnName": "receivers_hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "receiversThreshold", + "columnName": "receivers_threshold", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "extra", + "columnName": "extra", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updated_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "signedBy", + "columnName": "signed_by", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "signedAt", + "columnName": "signed_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "spentAt", + "columnName": "spent_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "inscriptionHash", + "columnName": "inscription_hash", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "output_id" + ] + }, + "indices": [ + { + "name": "index_outputs_asset_state_sequence", + "unique": false, + "columnNames": [ + "asset", + "state", + "sequence" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_outputs_asset_state_sequence` ON `${TABLE_NAME}` (`asset`, `state`, `sequence`)" + }, + { + "name": "index_outputs_transaction_hash_output_index", + "unique": true, + "columnNames": [ + "transaction_hash", + "output_index" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_outputs_transaction_hash_output_index` ON `${TABLE_NAME}` (`transaction_hash`, `output_index`)" + }, + { + "name": "index_outputs_inscription_hash", + "unique": false, + "columnNames": [ + "inscription_hash" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_outputs_inscription_hash` ON `${TABLE_NAME}` (`inscription_hash`)" + } + ] + }, + { + "tableName": "tokens", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`asset_id` TEXT NOT NULL, `kernel_asset_id` TEXT NOT NULL, `symbol` TEXT NOT NULL, `name` TEXT NOT NULL, `icon_url` TEXT NOT NULL, `price_btc` TEXT NOT NULL, `price_usd` TEXT NOT NULL, `chain_id` TEXT NOT NULL, `change_usd` TEXT NOT NULL, `change_btc` TEXT NOT NULL, `confirmations` INTEGER NOT NULL, `asset_key` TEXT NOT NULL, `dust` TEXT NOT NULL, `collection_hash` TEXT, `precision` INTEGER NOT NULL, PRIMARY KEY(`asset_id`))", + "fields": [ + { + "fieldPath": "assetId", + "columnName": "asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "asset", + "columnName": "kernel_asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "iconUrl", + "columnName": "icon_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "priceBtc", + "columnName": "price_btc", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "priceUsd", + "columnName": "price_usd", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "chainId", + "columnName": "chain_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "changeUsd", + "columnName": "change_usd", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "changeBtc", + "columnName": "change_btc", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "confirmations", + "columnName": "confirmations", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "assetKey", + "columnName": "asset_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dust", + "columnName": "dust", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "collectionHash", + "columnName": "collection_hash", + "affinity": "TEXT" + }, + { + "fieldPath": "precision", + "columnName": "precision", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "asset_id" + ] + }, + "indices": [ + { + "name": "index_tokens_kernel_asset_id", + "unique": false, + "columnNames": [ + "kernel_asset_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_tokens_kernel_asset_id` ON `${TABLE_NAME}` (`kernel_asset_id`)" + }, + { + "name": "index_tokens_collection_hash", + "unique": false, + "columnNames": [ + "collection_hash" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_tokens_collection_hash` ON `${TABLE_NAME}` (`collection_hash`)" + } + ] + }, + { + "tableName": "deposit_entries", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`entry_id` TEXT NOT NULL, `chain_id` TEXT NOT NULL, `destination` TEXT NOT NULL, `members` TEXT NOT NULL, `tag` TEXT, `signature` TEXT NOT NULL, `threshold` INTEGER NOT NULL, `is_primary` INTEGER NOT NULL, `minimum` TEXT NOT NULL, `maximum` TEXT NOT NULL, PRIMARY KEY(`entry_id`))", + "fields": [ + { + "fieldPath": "entryId", + "columnName": "entry_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "chainId", + "columnName": "chain_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "destination", + "columnName": "destination", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "members", + "columnName": "members", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "tag", + "columnName": "tag", + "affinity": "TEXT" + }, + { + "fieldPath": "signature", + "columnName": "signature", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "threshold", + "columnName": "threshold", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPrimary", + "columnName": "is_primary", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "minimum", + "columnName": "minimum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "maximum", + "columnName": "maximum", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "entry_id" + ] + } + }, + { + "tableName": "safe_snapshots", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`snapshot_id` TEXT NOT NULL, `type` TEXT NOT NULL, `asset_id` TEXT NOT NULL, `amount` TEXT NOT NULL, `user_id` TEXT NOT NULL, `opponent_id` TEXT NOT NULL, `memo` TEXT NOT NULL, `transaction_hash` TEXT NOT NULL, `created_at` TEXT NOT NULL, `trace_id` TEXT, `confirmations` INTEGER, `opening_balance` TEXT, `closing_balance` TEXT, `deposit` TEXT, `withdrawal` TEXT, `inscription_hash` TEXT, PRIMARY KEY(`snapshot_id`))", + "fields": [ + { + "fieldPath": "snapshotId", + "columnName": "snapshot_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "assetId", + "columnName": "asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "opponentId", + "columnName": "opponent_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "memo", + "columnName": "memo", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "transactionHash", + "columnName": "transaction_hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "traceId", + "columnName": "trace_id", + "affinity": "TEXT" + }, + { + "fieldPath": "confirmations", + "columnName": "confirmations", + "affinity": "INTEGER" + }, + { + "fieldPath": "openingBalance", + "columnName": "opening_balance", + "affinity": "TEXT" + }, + { + "fieldPath": "closingBalance", + "columnName": "closing_balance", + "affinity": "TEXT" + }, + { + "fieldPath": "deposit", + "columnName": "deposit", + "affinity": "TEXT" + }, + { + "fieldPath": "withdrawal", + "columnName": "withdrawal", + "affinity": "TEXT" + }, + { + "fieldPath": "inscriptionHash", + "columnName": "inscription_hash", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "snapshot_id" + ] + }, + "indices": [ + { + "name": "index_safe_snapshots_created_at", + "unique": false, + "columnNames": [ + "created_at" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_safe_snapshots_created_at` ON `${TABLE_NAME}` (`created_at`)" + }, + { + "name": "index_safe_snapshots_type_asset_id", + "unique": false, + "columnNames": [ + "type", + "asset_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_safe_snapshots_type_asset_id` ON `${TABLE_NAME}` (`type`, `asset_id`)" + } + ] + }, + { + "tableName": "raw_transactions", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`request_id` TEXT NOT NULL, `raw_transaction` TEXT NOT NULL, `receiver_id` TEXT NOT NULL, `type` INTEGER NOT NULL, `state` TEXT NOT NULL, `created_at` TEXT NOT NULL, `inscription_hash` TEXT, PRIMARY KEY(`request_id`))", + "fields": [ + { + "fieldPath": "requestId", + "columnName": "request_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "rawTransaction", + "columnName": "raw_transaction", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "receiverId", + "columnName": "receiver_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "inscriptionHash", + "columnName": "inscription_hash", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "request_id" + ] + }, + "indices": [ + { + "name": "index_raw_transactions_state_type", + "unique": false, + "columnNames": [ + "state", + "type" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_raw_transactions_state_type` ON `${TABLE_NAME}` (`state`, `type`)" + } + ] + }, + { + "tableName": "inscription_collections", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`collection_hash` TEXT NOT NULL, `supply` TEXT NOT NULL, `unit` TEXT NOT NULL, `symbol` TEXT NOT NULL, `name` TEXT NOT NULL, `icon_url` TEXT NOT NULL, `created_at` TEXT NOT NULL, `updated_at` TEXT NOT NULL, `description` TEXT, `kernel_asset_id` TEXT, `treasury` TEXT, PRIMARY KEY(`collection_hash`))", + "fields": [ + { + "fieldPath": "collectionHash", + "columnName": "collection_hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "supply", + "columnName": "supply", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "unit", + "columnName": "unit", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "iconURL", + "columnName": "icon_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updated_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT" + }, + { + "fieldPath": "kernelAssetId", + "columnName": "kernel_asset_id", + "affinity": "TEXT" + }, + { + "fieldPath": "treasury", + "columnName": "treasury", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "collection_hash" + ] + } + }, + { + "tableName": "inscription_items", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`inscription_hash` TEXT NOT NULL, `collection_hash` TEXT NOT NULL, `sequence` INTEGER NOT NULL, `content_type` TEXT NOT NULL, `content_url` TEXT NOT NULL, `occupied_by` TEXT, `occupied_at` TEXT, `created_at` TEXT NOT NULL, `updated_at` TEXT NOT NULL, `owner` TEXT, `traits` TEXT, PRIMARY KEY(`inscription_hash`))", + "fields": [ + { + "fieldPath": "inscriptionHash", + "columnName": "inscription_hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "collectionHash", + "columnName": "collection_hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sequence", + "columnName": "sequence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "contentType", + "columnName": "content_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contentURL", + "columnName": "content_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "occupiedBy", + "columnName": "occupied_by", + "affinity": "TEXT" + }, + { + "fieldPath": "occupiedAt", + "columnName": "occupied_at", + "affinity": "TEXT" + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updated_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "owner", + "columnName": "owner", + "affinity": "TEXT" + }, + { + "fieldPath": "traits", + "columnName": "traits", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "inscription_hash" + ] + } + }, + { + "tableName": "markets", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`coin_id` TEXT NOT NULL, `name` TEXT NOT NULL, `symbol` TEXT NOT NULL, `icon_url` TEXT NOT NULL, `current_price` TEXT NOT NULL, `market_cap` TEXT NOT NULL, `market_cap_rank` TEXT NOT NULL, `total_volume` TEXT NOT NULL, `high_24h` TEXT NOT NULL, `low_24h` TEXT NOT NULL, `price_change_24h` TEXT NOT NULL, `price_change_percentage_1h` TEXT NOT NULL, `price_change_percentage_24h` TEXT NOT NULL, `price_change_percentage_7d` TEXT NOT NULL, `price_change_percentage_30d` TEXT NOT NULL, `market_cap_change_24h` TEXT NOT NULL, `market_cap_change_percentage_24h` TEXT NOT NULL, `circulating_supply` TEXT NOT NULL, `total_supply` TEXT NOT NULL, `max_supply` TEXT NOT NULL, `ath` TEXT NOT NULL, `ath_change_percentage` TEXT NOT NULL, `ath_date` TEXT NOT NULL, `atl` TEXT NOT NULL, `atl_change_percentage` TEXT NOT NULL, `atl_date` TEXT NOT NULL, `asset_ids` TEXT, `sparkline_in_7d` TEXT NOT NULL, `sparkline_in_24h` TEXT NOT NULL, `updated_at` TEXT NOT NULL, PRIMARY KEY(`coin_id`))", + "fields": [ + { + "fieldPath": "coinId", + "columnName": "coin_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "iconUrl", + "columnName": "icon_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "currentPrice", + "columnName": "current_price", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "marketCap", + "columnName": "market_cap", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "marketCapRank", + "columnName": "market_cap_rank", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "totalVolume", + "columnName": "total_volume", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "high24h", + "columnName": "high_24h", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "low24h", + "columnName": "low_24h", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "priceChange24h", + "columnName": "price_change_24h", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "priceChangePercentage1H", + "columnName": "price_change_percentage_1h", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "priceChangePercentage24H", + "columnName": "price_change_percentage_24h", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "priceChangePercentage7D", + "columnName": "price_change_percentage_7d", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "priceChangePercentage30D", + "columnName": "price_change_percentage_30d", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "marketCapChange24h", + "columnName": "market_cap_change_24h", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "marketCapChangePercentage24h", + "columnName": "market_cap_change_percentage_24h", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "circulatingSupply", + "columnName": "circulating_supply", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "totalSupply", + "columnName": "total_supply", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "maxSupply", + "columnName": "max_supply", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ath", + "columnName": "ath", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "athChangePercentage", + "columnName": "ath_change_percentage", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "athDate", + "columnName": "ath_date", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "atl", + "columnName": "atl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "atlChangePercentage", + "columnName": "atl_change_percentage", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "atlDate", + "columnName": "atl_date", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "assetIds", + "columnName": "asset_ids", + "affinity": "TEXT" + }, + { + "fieldPath": "sparklineIn7d", + "columnName": "sparkline_in_7d", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sparklineIn24h", + "columnName": "sparkline_in_24h", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updated_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "coin_id" + ] + } + }, + { + "tableName": "history_prices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`coin_id` TEXT NOT NULL, `type` TEXT NOT NULL, `data` TEXT NOT NULL, `updated_at` TEXT NOT NULL, PRIMARY KEY(`coin_id`, `type`))", + "fields": [ + { + "fieldPath": "coinId", + "columnName": "coin_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updated_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "coin_id", + "type" + ] + } + }, + { + "tableName": "market_coins", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`asset_id` TEXT NOT NULL, `coin_id` TEXT NOT NULL, `created_at` TEXT NOT NULL, PRIMARY KEY(`asset_id`))", + "fields": [ + { + "fieldPath": "assetId", + "columnName": "asset_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "coinId", + "columnName": "coin_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "asset_id" + ] + } + }, + { + "tableName": "market_favored", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`coin_id` TEXT NOT NULL, `is_favored` INTEGER NOT NULL, `created_at` TEXT NOT NULL, PRIMARY KEY(`coin_id`))", + "fields": [ + { + "fieldPath": "coinId", + "columnName": "coin_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isFavored", + "columnName": "is_favored", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "coin_id" + ] + } + }, + { + "tableName": "market_alerts", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`alert_id` TEXT NOT NULL, `coin_id` TEXT NOT NULL, `type` TEXT NOT NULL, `frequency` TEXT NOT NULL, `status` TEXT NOT NULL, `value` TEXT NOT NULL, `created_at` TEXT NOT NULL, PRIMARY KEY(`alert_id`))", + "fields": [ + { + "fieldPath": "alertId", + "columnName": "alert_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "coinId", + "columnName": "coin_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "frequency", + "columnName": "frequency", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "alert_id" + ] + } + }, + { + "tableName": "market_cap_ranks", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`coin_id` TEXT NOT NULL, `market_cap_rank` TEXT NOT NULL, `updated_at` TEXT NOT NULL, PRIMARY KEY(`coin_id`))", + "fields": [ + { + "fieldPath": "coinId", + "columnName": "coin_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "marketCapRank", + "columnName": "market_cap_rank", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updated_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "coin_id" + ] + } + }, + { + "tableName": "membership_orders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`order_id` TEXT NOT NULL, `category` TEXT NOT NULL, `amount` TEXT NOT NULL, `amount_actual` TEXT NOT NULL, `amount_original` TEXT NOT NULL, `after` TEXT NOT NULL, `before` TEXT NOT NULL, `created_at` TEXT NOT NULL, `fiat_order` TEXT, `stars` INTEGER NOT NULL, `payment_url` TEXT, `status` TEXT NOT NULL, PRIMARY KEY(`order_id`))", + "fields": [ + { + "fieldPath": "orderId", + "columnName": "order_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amountActual", + "columnName": "amount_actual", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amountOriginal", + "columnName": "amount_original", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "after", + "columnName": "after", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "before", + "columnName": "before", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fiatOrder", + "columnName": "fiat_order", + "affinity": "TEXT" + }, + { + "fieldPath": "stars", + "columnName": "stars", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "paymentUrl", + "columnName": "payment_url", + "affinity": "TEXT" + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "order_id" + ] + }, + "indices": [ + { + "name": "index_membership_orders_created_at", + "unique": false, + "columnNames": [ + "created_at" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_membership_orders_created_at` ON `${TABLE_NAME}` (`created_at`)" + } + ] + } + ], + "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, 'a49373f3e5e79b69c09e11d526d973d6')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/one/mixin/android/Constants.kt b/app/src/main/java/one/mixin/android/Constants.kt index 5e7c68591e..d84b0e37de 100644 --- a/app/src/main/java/one/mixin/android/Constants.kt +++ b/app/src/main/java/one/mixin/android/Constants.kt @@ -216,7 +216,7 @@ object Constants { object DataBase { const val DB_NAME = "mixin.db" const val MINI_VERSION = 15 - const val CURRENT_VERSION = 69 + const val CURRENT_VERSION = 70 const val FTS_DB_NAME = "fts.db" const val PENDING_DB_NAME = "pending.db" diff --git a/app/src/main/java/one/mixin/android/db/MixinDatabase.kt b/app/src/main/java/one/mixin/android/db/MixinDatabase.kt index a8c7746079..0576388d2c 100644 --- a/app/src/main/java/one/mixin/android/db/MixinDatabase.kt +++ b/app/src/main/java/one/mixin/android/db/MixinDatabase.kt @@ -69,6 +69,7 @@ import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_65_66 import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_66_67 import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_67_68 import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_68_69 +import one.mixin.android.db.MixinDatabaseMigrations.Companion.MIGRATION_69_70 import one.mixin.android.db.converter.DepositEntryListConverter import one.mixin.android.db.converter.FiatOrderConverter @@ -76,6 +77,7 @@ import one.mixin.android.db.converter.MembershipConverter import one.mixin.android.db.converter.MessageStatusConverter import one.mixin.android.db.converter.OutputStateConverter import one.mixin.android.db.converter.PriceListConverter +import one.mixin.android.db.converter.RawTransactionStateConverter import one.mixin.android.db.converter.RawTransactionTypeConverter import one.mixin.android.db.converter.SafeDepositConverter import one.mixin.android.db.converter.SafeWithdrawalConverter @@ -207,6 +209,7 @@ import kotlin.math.min SafeDepositConverter::class, SafeWithdrawalConverter::class, RawTransactionTypeConverter::class, + RawTransactionStateConverter::class, OutputStateConverter::class, TreasuryConverter::class, PriceListConverter::class, @@ -424,7 +427,8 @@ abstract class MixinDatabase : RoomDatabase() { MIGRATION_65_66, MIGRATION_66_67, MIGRATION_67_68, - MIGRATION_68_69 + MIGRATION_68_69, + MIGRATION_69_70 ) .enableMultiInstanceInvalidation() .setQueryExecutor( diff --git a/app/src/main/java/one/mixin/android/db/MixinDatabaseMigrations.kt b/app/src/main/java/one/mixin/android/db/MixinDatabaseMigrations.kt index d5fd42aaad..f161ad9b48 100644 --- a/app/src/main/java/one/mixin/android/db/MixinDatabaseMigrations.kt +++ b/app/src/main/java/one/mixin/android/db/MixinDatabaseMigrations.kt @@ -597,6 +597,14 @@ class MixinDatabaseMigrations private constructor() { } } + val MIGRATION_69_70: Migration = + object : Migration(69, 70) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("UPDATE raw_transactions SET state = 'spent' WHERE state = 'signed'") + db.execSQL("UPDATE raw_transactions SET state = 'signed' WHERE state = 'unspent'") + } + } + // If you add a new table, be sure to add a clear method to the DatabaseUtil } } diff --git a/app/src/main/java/one/mixin/android/db/RawTransactionDao.kt b/app/src/main/java/one/mixin/android/db/RawTransactionDao.kt index 49a849f4bb..3264bf96ac 100644 --- a/app/src/main/java/one/mixin/android/db/RawTransactionDao.kt +++ b/app/src/main/java/one/mixin/android/db/RawTransactionDao.kt @@ -6,10 +6,10 @@ import one.mixin.android.vo.safe.RawTransaction @Dao interface RawTransactionDao : BaseDao { - @Query("SELECT * FROM raw_transactions WHERE state = 'unspent' AND (type = 0 OR type = 1) ORDER BY rowid ASC LIMIT 1") + @Query("SELECT * FROM raw_transactions WHERE state = 'signed' AND (type = 0 OR type = 1) ORDER BY rowid ASC LIMIT 1") fun findUnspentTransaction(): RawTransaction? - @Query("SELECT count(1) FROM raw_transactions WHERE state = 'unspent' AND (type = 0 OR type = 1)") + @Query("SELECT count(1) FROM raw_transactions WHERE state = 'signed' AND (type = 0 OR type = 1)") suspend fun countUnspentTransaction(): Int @Query("SELECT * FROM raw_transactions WHERE request_id = :requestId") diff --git a/app/src/main/java/one/mixin/android/db/converter/RawTransactionStateConverter.kt b/app/src/main/java/one/mixin/android/db/converter/RawTransactionStateConverter.kt new file mode 100644 index 0000000000..5068c78ac1 --- /dev/null +++ b/app/src/main/java/one/mixin/android/db/converter/RawTransactionStateConverter.kt @@ -0,0 +1,17 @@ +package one.mixin.android.db.converter + +import androidx.room.TypeConverter +import one.mixin.android.vo.safe.RawTransactionState +import java.util.Locale + +class RawTransactionStateConverter { + @TypeConverter + fun toRawTransactionState(value: String): RawTransactionState { + return RawTransactionState.valueOf(value.lowercase(Locale.US)) + } + + @TypeConverter + fun fromRawTransactionState(state: RawTransactionState): String { + return state.name.lowercase(Locale.US) + } +} diff --git a/app/src/main/java/one/mixin/android/job/RestoreTransactionJob.kt b/app/src/main/java/one/mixin/android/job/RestoreTransactionJob.kt index 5ccfa1fa2c..ec60e8c540 100644 --- a/app/src/main/java/one/mixin/android/job/RestoreTransactionJob.kt +++ b/app/src/main/java/one/mixin/android/job/RestoreTransactionJob.kt @@ -21,7 +21,7 @@ import one.mixin.android.vo.createConversation import one.mixin.android.vo.createMessage import one.mixin.android.vo.generateConversationId import one.mixin.android.vo.notMessengerUser -import one.mixin.android.vo.safe.OutputState +import one.mixin.android.vo.safe.RawTransactionState import one.mixin.android.vo.safe.RawTransactionType import one.mixin.android.vo.safe.SafeSnapshotType import timber.log.Timber @@ -55,9 +55,9 @@ class RestoreTransactionJob : BaseJob( Timber.e("Restore Transaction(${transaction.requestId}): db begin") appDatabase.runInTransaction { Timber.e("Restore Transaction(${transaction.requestId}): update raw transaction ${transaction.requestId}") - rawTransactionDao.updateRawTransaction(transaction.requestId, OutputState.signed.name) + rawTransactionDao.updateRawTransaction(transaction.requestId, RawTransactionState.spent.name) Timber.e("Restore Transaction(${transaction.requestId}): update raw transaction $feeTraceId") - rawTransactionDao.updateRawTransaction(feeTraceId, OutputState.signed.name) + rawTransactionDao.updateRawTransaction(feeTraceId, RawTransactionState.spent.name) } Timber.e("Restore Transaction(${transaction.requestId}): db end") if (feeTransaction == null) { @@ -82,9 +82,9 @@ class RestoreTransactionJob : BaseJob( Timber.e("Restore Transaction(${transaction.requestId}): db begin") appDatabase.runInTransaction { Timber.e("Restore Transaction(${transaction.requestId}): update raw transaction ${transaction.requestId}") - rawTransactionDao.updateRawTransaction(transaction.requestId, OutputState.signed.name) + rawTransactionDao.updateRawTransaction(transaction.requestId, RawTransactionState.spent.name) Timber.e("Restore Transaction(${transaction.requestId}): update raw transaction $feeTraceId") - rawTransactionDao.updateRawTransaction(feeTraceId, OutputState.signed.name) + rawTransactionDao.updateRawTransaction(feeTraceId, RawTransactionState.spent.name) } Timber.e("Restore Transaction(${transaction.requestId}): db end") if (feeTransaction == null && transaction.receiverId.isNotBlank()) { @@ -93,8 +93,8 @@ class RestoreTransactionJob : BaseJob( } else { Timber.e("Restore Transaction(${transaction.requestId}): Post Transaction Error ${transactionRsp.errorDescription}") reportException(e = Throwable("Transaction Error ${transactionRsp.errorDescription}")) - rawTransactionDao.updateRawTransaction(transaction.requestId, OutputState.signed.name) - rawTransactionDao.updateRawTransaction(feeTraceId, OutputState.signed.name) + rawTransactionDao.updateRawTransaction(transaction.requestId, RawTransactionState.spent.name) + rawTransactionDao.updateRawTransaction(feeTraceId, RawTransactionState.spent.name) } jobManager.addJobInBackground(SyncOutputJob()) } else if (response.errorCode >= 500) { diff --git a/app/src/main/java/one/mixin/android/ui/common/BottomSheetViewModel.kt b/app/src/main/java/one/mixin/android/ui/common/BottomSheetViewModel.kt index 4ca2568de3..7722364f05 100644 --- a/app/src/main/java/one/mixin/android/ui/common/BottomSheetViewModel.kt +++ b/app/src/main/java/one/mixin/android/ui/common/BottomSheetViewModel.kt @@ -131,6 +131,7 @@ import one.mixin.android.vo.notMessengerUser import one.mixin.android.vo.safe.Output import one.mixin.android.vo.safe.OutputState import one.mixin.android.vo.safe.RawTransaction +import one.mixin.android.vo.safe.RawTransactionState import one.mixin.android.vo.safe.RawTransactionType import one.mixin.android.vo.safe.SafeSnapshot import one.mixin.android.vo.safe.SafeSnapshotType @@ -371,9 +372,9 @@ class BottomSheetViewModel Timber.e("Kernel Withdrawal($traceId): db insert fee snapshot") tokenRepository.insertSafeSnapshot(UUID.nameUUIDFromBytes("$senderId:$feeTransactionHash".toByteArray()).toString(), senderId, receiverId, feeTransactionHash, feeTraceId, feeAssetId, feeAmount, "", SafeSnapshotType.snapshot) Timber.e("Kernel Withdrawal($traceId): db raw transaction") - tokenRepository.insetRawTransaction(RawTransaction(withdrawalData.requestId, signWithdrawalResult.raw, formatDestination(destination, tag), RawTransactionType.WITHDRAWAL, OutputState.unspent, nowInUtc(), withdrawalUtxos.inscriptionHash)) + tokenRepository.insetRawTransaction(RawTransaction(withdrawalData.requestId, signWithdrawalResult.raw, formatDestination(destination, tag), RawTransactionType.WITHDRAWAL, RawTransactionState.signed, nowInUtc(), withdrawalUtxos.inscriptionHash)) Timber.e("Kernel Withdrawal($traceId): db insert fee raw transaction") - tokenRepository.insetRawTransaction(RawTransaction(feeData.requestId, signFeeResult.raw, receiverId, RawTransactionType.FEE, OutputState.unspent, nowInUtc(), null)) + tokenRepository.insetRawTransaction(RawTransaction(feeData.requestId, signFeeResult.raw, receiverId, RawTransactionType.FEE, RawTransactionState.signed, nowInUtc(), null)) } Timber.e("Kernel Withdrawal($traceId): db end") jobManager.addJobInBackground(CheckBalanceJob(arrayListOf(assetIdToAsset(assetId), assetIdToAsset(feeAssetId)))) @@ -406,7 +407,7 @@ class BottomSheetViewModel ), ) Timber.e("Kernel Withdrawal($traceId): db update raw transaction") - tokenRepository.insetRawTransaction(RawTransaction(withdrawalData.requestId, signWithdrawalResult.raw, formatDestination(destination, tag), RawTransactionType.WITHDRAWAL, OutputState.unspent, nowInUtc(), withdrawalUtxos.inscriptionHash)) + tokenRepository.insetRawTransaction(RawTransaction(withdrawalData.requestId, signWithdrawalResult.raw, formatDestination(destination, tag), RawTransactionType.WITHDRAWAL, RawTransactionState.signed, nowInUtc(), withdrawalUtxos.inscriptionHash)) } Timber.e("Kernel Withdrawal($traceId): db end") jobManager.addJobInBackground(CheckBalanceJob(arrayListOf(assetIdToAsset(assetId)))) @@ -422,12 +423,12 @@ class BottomSheetViewModel if (transactionRsp.error != null) { Timber.e("Kernel Withdrawal($traceId): withdrawal error ${transactionRsp.errorDescription}") reportException(Throwable("Transaction Error ${transactionRsp.errorDescription}")) - tokenRepository.updateRawTransaction(traceId, OutputState.signed.name) - tokenRepository.updateRawTransaction(feeTraceId, OutputState.signed.name) + tokenRepository.updateRawTransaction(traceId, RawTransactionState.spent.name) + tokenRepository.updateRawTransaction(feeTraceId, RawTransactionState.spent.name) return transactionRsp } else { - tokenRepository.updateRawTransaction(traceId, OutputState.signed.name) - tokenRepository.updateRawTransaction(feeTraceId, OutputState.signed.name) + tokenRepository.updateRawTransaction(traceId, RawTransactionState.spent.name) + tokenRepository.updateRawTransaction(feeTraceId, RawTransactionState.spent.name) } jobManager.addJobInBackground(SyncOutputJob()) Timber.e("Kernel Withdrawal($traceId): withdrawal end") @@ -452,7 +453,7 @@ class BottomSheetViewModel Timber.e("Kernel Address Transaction($trace): begin") val rawTransaction = tokenRepository.findRawTransaction(trace) - if (rawTransaction?.state == OutputState.unspent) { + if (rawTransaction?.state == RawTransactionState.signed) { Timber.e("Kernel Address Transaction($trace): sync restore") return innerTransaction(rawTransaction.rawTransaction, trace, listOf(), utxoWrapper.inscriptionHash) } @@ -501,7 +502,7 @@ class BottomSheetViewModel Timber.e("Kernel Address Transaction($trace): sign db insert snapshot") tokenRepository.insertSafeSnapshot(UUID.nameUUIDFromBytes("$senderId:$transactionHash".toByteArray()).toString(), senderId, kernelAddress, transactionHash, trace, assetId, amount, memo, SafeSnapshotType.snapshot, reference = reference) Timber.e("Kernel Address Transaction($trace): sign db insert raw transaction") - tokenRepository.insetRawTransaction(RawTransaction(transactionResponse.data!!.first().requestId, signResult.raw, "", RawTransactionType.TRANSFER, OutputState.unspent, nowInUtc(), utxoWrapper.inscriptionHash)) + tokenRepository.insetRawTransaction(RawTransaction(transactionResponse.data!!.first().requestId, signResult.raw, "", RawTransactionType.TRANSFER, RawTransactionState.signed, nowInUtc(), utxoWrapper.inscriptionHash)) Timber.e("Kernel Address Transaction($trace): sign db mark utxo ${utxoWrapper.ids.joinToString(", ")}") tokenRepository.updateUtxoToSigned(utxoWrapper.ids) Timber.e("Kernel Address Transaction: sign end") @@ -592,7 +593,7 @@ class BottomSheetViewModel tokenRepository.insertSafeSnapshot(UUID.nameUUIDFromBytes("${senderIds.first()}:$transactionHash".toByteArray()).toString(), senderIds.first(), opponentId, transactionHash, trace, assetId, amount, memo, SafeSnapshotType.snapshot, reference = reference ?: (if (release == true) null else inscriptionHash)) } Timber.e("Kernel Transaction($trace): sign db insert raw transaction") - tokenRepository.insetRawTransaction(RawTransaction(transactionResponse.data!!.first().requestId, signResult.raw, receiverIds.joinToString(","), RawTransactionType.TRANSFER, OutputState.unspent, nowInUtc(), if (release == true) null else utxoWrapper.inscriptionHash)) + tokenRepository.insetRawTransaction(RawTransaction(transactionResponse.data!!.first().requestId, signResult.raw, receiverIds.joinToString(","), RawTransactionType.TRANSFER, RawTransactionState.signed, nowInUtc(), if (release == true) null else utxoWrapper.inscriptionHash)) Timber.e("Kernel Transaction($trace): sign db mark utxo ${utxoWrapper.ids.joinToString(", ")}") tokenRepository.updateUtxoToSigned(utxoWrapper.ids) Timber.e("Kernel Transaction: sign end") @@ -622,11 +623,11 @@ class BottomSheetViewModel if (transactionRsp.error != null) { Timber.e("Kernel Transaction($traceId): innerTransaction error ${transactionRsp.errorDescription}") reportException(Throwable("Transaction Error ${transactionRsp.errorDescription}")) - tokenRepository.updateRawTransaction(transactionRsp.data!!.first().requestId, OutputState.signed.name) + tokenRepository.updateRawTransaction(transactionRsp.data!!.first().requestId, RawTransactionState.spent.name) return transactionRsp } else { Timber.e("Kernel Transaction($traceId): innerTransaction update raw transaction") - tokenRepository.updateRawTransaction(transactionRsp.data!!.first().requestId, OutputState.signed.name) + tokenRepository.updateRawTransaction(transactionRsp.data!!.first().requestId, RawTransactionState.spent.name) } if (receiverIds.size == 1 && !isConsolidation) { // Workaround with only the case of a single transfer @@ -813,7 +814,7 @@ class BottomSheetViewModel "" } else { recipient.uuidMembers.joinToString(",") - }, RawTransactionType.TRANSFER, OutputState.unspent, nowInUtc(), null + }, RawTransactionType.TRANSFER, RawTransactionState.signed, nowInUtc(), null ) ) Timber.e("Kernel Duplicate Invoice Transaction(${signedTransaction.trace}): sign db mark utxo ${signedTransaction.utxoWrapperIds.joinToString(", ")}") @@ -830,7 +831,7 @@ class BottomSheetViewModel signedResponse.data?.forEach { tokenRepository.updateRawTransaction( it.requestId, - OutputState.signed.name + RawTransactionState.spent.name ) } Timber.e("Kernel Duplicate Invoice Transaction(${signedTransaction.trace}): sign db end") @@ -1029,7 +1030,7 @@ class BottomSheetViewModel "" } else { invoice.recipient.uuidMembers.joinToString(",") - }, RawTransactionType.TRANSFER, OutputState.unspent, nowInUtc(), null + }, RawTransactionType.TRANSFER, RawTransactionState.signed, nowInUtc(), null ) ) Timber.e("Kernel Invoice Transaction(${t.trace}): sign db mark utxo ${t.utxoWrapperIds.joinToString(", ")}") @@ -1049,7 +1050,7 @@ class BottomSheetViewModel appDatabase.runInTransaction { Timber.e("Kernel Invoice Transaction: sign db begin") signedResponse.data?.forEach { - tokenRepository.updateRawTransaction(it.requestId, OutputState.signed.name) + tokenRepository.updateRawTransaction(it.requestId, RawTransactionState.spent.name) } Timber.e("Kernel Invoice Transaction: sign db end") } diff --git a/app/src/main/java/one/mixin/android/vo/safe/RawTransaction.kt b/app/src/main/java/one/mixin/android/vo/safe/RawTransaction.kt index 8410c2d80e..7cf437e2a4 100644 --- a/app/src/main/java/one/mixin/android/vo/safe/RawTransaction.kt +++ b/app/src/main/java/one/mixin/android/vo/safe/RawTransaction.kt @@ -22,7 +22,7 @@ data class RawTransaction( @ColumnInfo(name = "type") val type: RawTransactionType, @ColumnInfo(name = "state") - val state: OutputState, + val state: RawTransactionState, @ColumnInfo(name = "created_at") val createdAt: String, @ColumnInfo(name = "inscription_hash") diff --git a/app/src/main/java/one/mixin/android/vo/safe/RawTransactionState.kt b/app/src/main/java/one/mixin/android/vo/safe/RawTransactionState.kt new file mode 100644 index 0000000000..2617ca6f55 --- /dev/null +++ b/app/src/main/java/one/mixin/android/vo/safe/RawTransactionState.kt @@ -0,0 +1,6 @@ +package one.mixin.android.vo.safe + +enum class RawTransactionState { + signed, // Signed, pending broadcast + spent // Broadcast (terminal state) +} From 067fcc68edb94ee5765da037bbd02f9e772b57fb Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Tue, 9 Jun 2026 10:58:46 +0800 Subject: [PATCH 4/7] fix(trade): respect saved tab outside market detail --- .../java/one/mixin/android/ui/home/web3/trade/TradeFragment.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/TradeFragment.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/TradeFragment.kt index c5a9064938..840fd6112a 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/TradeFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/TradeFragment.kt @@ -1471,7 +1471,8 @@ class TradeFragment : BaseFragment() { private fun getInitialTabIndex(currentWalletId: String): Int { val entryType = arguments?.getString(ARGS_ENTRY_TYPE) - if (entryType == AnalyticsTracker.SpotTradeType.SIMPLE) return TAB_SIMPLE + val entrySource = arguments?.getString(ARGS_ENTRY_SOURCE) + if (entrySource == AnalyticsTracker.TradeSource.MARKET_DETAIL && entryType == AnalyticsTracker.SpotTradeType.SIMPLE) return TAB_SIMPLE val preferenceKey = "$PREF_TRADE_SELECTED_TAB_PREFIX$currentWalletId" return defaultSharedPreferences.getInt(preferenceKey, 0) } From 6ab5d28fb9b9868e99bfd62254821d0f8aefbd06 Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Tue, 9 Jun 2026 12:27:52 +0800 Subject: [PATCH 5/7] fix(wallet): hide missing market description (#6444) Co-authored-by: AI --- .../java/one/mixin/android/ui/wallet/MarketDescription.kt | 6 ++++++ .../one/mixin/android/ui/wallet/MarketDetailsFragment.kt | 4 +--- 2 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/one/mixin/android/ui/wallet/MarketDescription.kt diff --git a/app/src/main/java/one/mixin/android/ui/wallet/MarketDescription.kt b/app/src/main/java/one/mixin/android/ui/wallet/MarketDescription.kt new file mode 100644 index 0000000000..5564dd6bb0 --- /dev/null +++ b/app/src/main/java/one/mixin/android/ui/wallet/MarketDescription.kt @@ -0,0 +1,6 @@ +package one.mixin.android.ui.wallet + +fun selectLocalizedMarketDescription( + descriptions: Map, + language: String, +): String? = descriptions[language]?.takeIf { it.isNotBlank() } diff --git a/app/src/main/java/one/mixin/android/ui/wallet/MarketDetailsFragment.kt b/app/src/main/java/one/mixin/android/ui/wallet/MarketDetailsFragment.kt index 5372eebdcc..3769c636ae 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/MarketDetailsFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/MarketDetailsFragment.kt @@ -352,9 +352,7 @@ class MarketDetailsFragment : BaseFragment(R.layout.fragment_details_market) { val desc = info.descriptions?.let { map -> val lang = Locale.getDefault().language - (map[lang]?.takeIf { it.isNotBlank() } - ?: map["en"]?.takeIf { it.isNotBlank() } - ?: map.values.firstOrNull { it.isNotBlank() }) + selectLocalizedMarketDescription(map, lang) }?.let { HtmlCompat.fromHtml(it, HtmlCompat.FROM_HTML_MODE_LEGACY).toString().trim() } aboutContainer.isVisible = !desc.isNullOrBlank() aboutContent.text = desc.orEmpty() From 05be5fa482f1db4c4c542aa1c5ad0e6ca02bc3f1 Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Wed, 10 Jun 2026 12:43:01 +0800 Subject: [PATCH 6/7] fix(trade): limit amount inputs to eight decimals (#6430) * fix(trade): limit amount inputs to eight decimals * fix(trade): use token precision for common wallet inputs * fix(trade): limit price input decimals --------- Co-authored-by: Crossle Song --- .../ui/home/web3/components/InputArea.kt | 11 +++- .../ui/home/web3/components/PriceInputArea.kt | 2 + .../ui/home/web3/trade/InputTextField.kt | 47 ++++++++++++++++- .../ui/home/web3/trade/LimitOrderContent.kt | 47 +++++++++-------- .../android/ui/home/web3/trade/SwapContent.kt | 29 +++++++---- .../ui/home/web3/trade/TradeInputTest.kt | 51 +++++++++++++++++++ 6 files changed, 154 insertions(+), 33 deletions(-) create mode 100644 app/src/test/java/one/mixin/android/ui/home/web3/trade/TradeInputTest.kt diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/components/InputArea.kt b/app/src/main/java/one/mixin/android/ui/home/web3/components/InputArea.kt index c9852105b6..13ae090afa 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/components/InputArea.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/components/InputArea.kt @@ -60,6 +60,7 @@ fun InputArea( displayBalanceOverride: String? = null, bottomCompose: (@Composable () -> Unit)? = null, inlineEndCompose: (@Composable () -> Unit)? = null, + maxDecimalPlaces: Int? = null, ) { val viewModel = hiltViewModel() val balance = if (token == null) { @@ -92,7 +93,15 @@ fun InputArea( } } Box(modifier = Modifier.height(10.dp)) - InputContent(token = token, text = text, selectClick = selectClick, onInputChanged = onInputChanged, readOnly = readOnly, inlineEndCompose = inlineEndCompose) + InputContent( + token = token, + text = text, + selectClick = selectClick, + onInputChanged = onInputChanged, + readOnly = readOnly, + inlineEndCompose = inlineEndCompose, + maxDecimalPlaces = maxDecimalPlaces, + ) Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { token?.let { t -> val depositVisible = !readOnly && onDeposit != null && (balance?.toBigDecimalOrNull()?.compareTo(BigDecimal.ZERO) ?: 0) == 0 diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/components/PriceInputArea.kt b/app/src/main/java/one/mixin/android/ui/home/web3/components/PriceInputArea.kt index ee0a026909..9f0a9f3dec 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/components/PriceInputArea.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/components/PriceInputArea.kt @@ -33,6 +33,7 @@ import one.mixin.android.api.response.web3.QuoteResult import one.mixin.android.api.response.web3.SwapToken import one.mixin.android.compose.theme.MixinAppTheme import one.mixin.android.ui.home.web3.trade.SwapViewModel +import one.mixin.android.ui.home.web3.trade.tradePriceInputMaxDecimalPlaces import java.math.BigDecimal import java.math.RoundingMode @@ -175,6 +176,7 @@ fun PriceInputArea( title = stringResource(id = R.string.limit_price, priceDisplayState.displayChainName), readOnly = false, selectClick = null, + maxDecimalPlaces = tradePriceInputMaxDecimalPlaces(), onInputChanged = { userInput -> displayPrice = userInput val inputPrice = userInput.toBigDecimalOrNull() diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/InputTextField.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/InputTextField.kt index 03c17b26f2..979483e072 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/InputTextField.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/InputTextField.kt @@ -55,6 +55,47 @@ import one.mixin.android.ui.home.inscription.component.AutoSizeText import one.mixin.android.widget.CoilRoundedHexagonTransformation import java.math.BigDecimal +internal const val TRADE_INPUT_MAX_DECIMAL_PLACES = 8 + +internal fun tradePriceInputMaxDecimalPlaces(): Int = TRADE_INPUT_MAX_DECIMAL_PLACES + +internal fun tradeInputMaxDecimalPlaces( + isCommonWallet: Boolean, + precision: Int, +): Int { + return if (isCommonWallet && precision >= 0) { + precision + } else { + TRADE_INPUT_MAX_DECIMAL_PLACES + } +} + +internal fun SwapToken?.tradeInputMaxDecimalPlaces(): Int { + return this?.let { token -> + tradeInputMaxDecimalPlaces(token.walletId != null, token.decimals) + } ?: TRADE_INPUT_MAX_DECIMAL_PLACES +} + +internal fun isTradeInputDecimalAllowed( + value: String, + maxDecimalPlaces: Int? = TRADE_INPUT_MAX_DECIMAL_PLACES, +): Boolean { + maxDecimalPlaces ?: return true + val decimalIndex = value.indexOf('.') + return decimalIndex < 0 || value.length - decimalIndex - 1 <= maxDecimalPlaces +} + +internal fun limitTradeInputDecimalPlaces( + value: String, + maxDecimalPlaces: Int? = TRADE_INPUT_MAX_DECIMAL_PLACES, +): String { + maxDecimalPlaces ?: return value + val decimalIndex = value.indexOf('.') + if (decimalIndex < 0) return value + val endIndex = (decimalIndex + 1 + maxDecimalPlaces).coerceAtMost(value.length) + return value.substring(0, endIndex) +} + @SuppressLint("UnrememberedMutableState") @Composable fun InputContent( @@ -68,6 +109,7 @@ fun InputContent( inputFontSize: TextUnit = 24.sp, inputFontWeight: FontWeight = FontWeight.Black, autoFocus: Boolean = false, + maxDecimalPlaces: Int? = null, ) { if (readOnly) { Column(modifier = Modifier.fillMaxWidth()) { @@ -136,8 +178,11 @@ fun InputContent( BasicTextField( value = textFieldValue, onValueChange = { + if (!isTradeInputDecimalAllowed(it.text, maxDecimalPlaces)) { + return@BasicTextField + } textFieldValue = it - val v = try { + try { if (it.text.isBlank()) BigDecimal.ZERO else BigDecimal(it.text) } catch (e: Exception) { return@BasicTextField diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/LimitOrderContent.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/LimitOrderContent.kt index cc28745cc0..6393ece5ed 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/LimitOrderContent.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/LimitOrderContent.kt @@ -153,14 +153,6 @@ fun LimitOrderContent( val viewModel = hiltViewModel() - var inputText by remember { mutableStateOf(initialAmount ?: "") } - var outputText by remember { mutableStateOf("") } - - LaunchedEffect(lastOrderTime) { - inputText = initialAmount ?: "" - outputText = "" - } - var limitPriceText by remember { mutableStateOf("") } var marketPriceClickTime by remember { mutableStateOf(lastOrderTime) } var priceMultiplier by remember { mutableStateOf(null) } @@ -177,6 +169,18 @@ fun LimitOrderContent( var toToken by remember(from, to, isReverse) { mutableStateOf(if (isReverse) from else to) } + val fromMaxDecimalPlaces = fromToken.tradeInputMaxDecimalPlaces() + val toMaxDecimalPlaces = toToken.tradeInputMaxDecimalPlaces() + + var inputText by remember { + mutableStateOf(limitTradeInputDecimalPlaces(initialAmount ?: "", fromMaxDecimalPlaces)) + } + var outputText by remember { mutableStateOf("") } + + LaunchedEffect(lastOrderTime, fromMaxDecimalPlaces) { + inputText = limitTradeInputDecimalPlaces(initialAmount ?: "", fromMaxDecimalPlaces) + outputText = "" + } var isButtonEnabled by remember { mutableStateOf(true) } var isSubmitting by remember { mutableStateOf(false) } @@ -217,7 +221,7 @@ fun LimitOrderContent( if (fromAmount != null && standardPrice != null && fromAmount > BigDecimal.ZERO && standardPrice > BigDecimal.ZERO) { val toAmount = fromAmount.multiply(standardPrice).setScale(8, RoundingMode.DOWN) - outputText = toAmount.stripTrailingZeros().toPlainString() + outputText = limitTradeInputDecimalPlaces(toAmount.stripTrailingZeros().toPlainString(), toMaxDecimalPlaces) } else { outputText = "" } @@ -273,7 +277,8 @@ fun LimitOrderContent( .clickable { AnalyticsTracker.trackSpotSwitchSendReceive() isReverse = !isReverse - inputText = outputText + val nextFromMaxDecimalPlaces = toToken.tradeInputMaxDecimalPlaces() + inputText = limitTradeInputDecimalPlaces(outputText, nextFromMaxDecimalPlaces) val oldPrice = limitPriceText.toBigDecimalOrNull() if (oldPrice != null && oldPrice > BigDecimal.ZERO) { @@ -322,20 +327,20 @@ fun LimitOrderContent( val standardPrice = limitPriceText.toBigDecimalOrNull() if (fromAmount != null && standardPrice != null && fromAmount > BigDecimal.ZERO && standardPrice > BigDecimal.ZERO) { val calculatedOutput = fromAmount.multiply(standardPrice).setScale(8, RoundingMode.DOWN) - outputText = calculatedOutput.stripTrailingZeros().toPlainString() + outputText = limitTradeInputDecimalPlaces(calculatedOutput.stripTrailingZeros().toPlainString(), toMaxDecimalPlaces) } else if (fromAmount == null || fromAmount == BigDecimal.ZERO) { outputText = "" } } - }, onDeposit = onDeposit, displayBalanceOverride = if (it.isNativeSolAsset()) fromBalance else null, onMax = { + }, onDeposit = onDeposit, displayBalanceOverride = if (it.isNativeSolAsset()) fromBalance else null, maxDecimalPlaces = fromMaxDecimalPlaces, onMax = { AnalyticsTracker.trackSpotSendInputBalance() - inputText = formatBalanceInput(availableFromBalance, fromToken?.isWeb3 == true) + inputText = limitTradeInputDecimalPlaces(formatBalanceInput(availableFromBalance, fromToken?.isWeb3 == true), fromMaxDecimalPlaces) if (inputText.isNotBlank()) { val fromAmount = inputText.toBigDecimalOrNull() val standardPrice = limitPriceText.toBigDecimalOrNull() if (fromAmount != null && standardPrice != null && fromAmount > BigDecimal.ZERO && standardPrice > BigDecimal.ZERO) { val calculatedOutput = fromAmount.multiply(standardPrice).setScale(8, RoundingMode.DOWN) - outputText = calculatedOutput.stripTrailingZeros().toPlainString() + outputText = limitTradeInputDecimalPlaces(calculatedOutput.stripTrailingZeros().toPlainString(), toMaxDecimalPlaces) } else if (fromAmount == null || fromAmount == BigDecimal.ZERO) { outputText = "" } @@ -366,21 +371,22 @@ fun LimitOrderContent( val standardPrice = limitPriceText.toBigDecimalOrNull() if (toAmount != null && standardPrice != null && toAmount > BigDecimal.ZERO && standardPrice > BigDecimal.ZERO) { val calculatedInput = toAmount.divide(standardPrice, 8, RoundingMode.DOWN) - inputText = calculatedInput.stripTrailingZeros().toPlainString() + inputText = limitTradeInputDecimalPlaces(calculatedInput.stripTrailingZeros().toPlainString(), fromMaxDecimalPlaces) } else if (toAmount == null || toAmount == BigDecimal.ZERO) { inputText = "" } } }, onDeposit = null, + maxDecimalPlaces = toMaxDecimalPlaces, onMax = { - outputText = formatBalanceInput(toBalance, toToken?.isWeb3 == true) + outputText = limitTradeInputDecimalPlaces(formatBalanceInput(toBalance, toToken?.isWeb3 == true), toMaxDecimalPlaces) if (outputText.isNotBlank()) { val toAmount = outputText.toBigDecimalOrNull() val standardPrice = limitPriceText.toBigDecimalOrNull() if (toAmount != null && standardPrice != null && toAmount > BigDecimal.ZERO && standardPrice > BigDecimal.ZERO) { val calculatedInput = toAmount.divide(standardPrice, 8, RoundingMode.DOWN) - inputText = calculatedInput.stripTrailingZeros().toPlainString() + inputText = limitTradeInputDecimalPlaces(calculatedInput.stripTrailingZeros().toPlainString(), fromMaxDecimalPlaces) } else if (toAmount == null || toAmount == BigDecimal.ZERO) { inputText = "" } @@ -632,12 +638,13 @@ fun LimitOrderContent( AnalyticsTracker.trackSpotPriceInputPercent(label) }, onSetInput = { - inputText = it - val fromAmount = it.toBigDecimalOrNull() + val limitedInput = limitTradeInputDecimalPlaces(it, fromMaxDecimalPlaces) + inputText = limitedInput + val fromAmount = limitedInput.toBigDecimalOrNull() val standardPrice = limitPriceText.toBigDecimalOrNull() if (fromAmount != null && standardPrice != null && fromAmount > BigDecimal.ZERO && standardPrice > BigDecimal.ZERO) { val calculatedOutput = fromAmount.multiply(standardPrice).setScale(8, RoundingMode.DOWN) - outputText = calculatedOutput.stripTrailingZeros().toPlainString() + outputText = limitTradeInputDecimalPlaces(calculatedOutput.stripTrailingZeros().toPlainString(), toMaxDecimalPlaces) } else if (fromAmount == null || fromAmount == BigDecimal.ZERO) { outputText = "" } diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SwapContent.kt b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SwapContent.kt index c7805c4652..d8a55cd60c 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/trade/SwapContent.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/trade/SwapContent.kt @@ -113,11 +113,6 @@ fun SwapContent( var quoteMin by remember { mutableStateOf(null) } var quoteMax by remember { mutableStateOf(null) } - var inputText by remember { mutableStateOf(initialAmount ?: "") } - LaunchedEffect(lastOrderTime) { - inputText = initialAmount ?: "" - } - var isLoading by remember { mutableStateOf(false) } var isReverse by remember { mutableStateOf(false) } var invalidFlag by remember { mutableStateOf(false) } @@ -128,6 +123,14 @@ fun SwapContent( var toToken by remember(from, to, isReverse) { mutableStateOf(if (isReverse) from else to) } + val fromMaxDecimalPlaces = fromToken.tradeInputMaxDecimalPlaces() + + var inputText by remember { + mutableStateOf(limitTradeInputDecimalPlaces(initialAmount ?: "", fromMaxDecimalPlaces)) + } + LaunchedEffect(lastOrderTime, fromMaxDecimalPlaces) { + inputText = limitTradeInputDecimalPlaces(initialAmount ?: "", fromMaxDecimalPlaces) + } val shouldRefreshQuote = remember { MutableStateFlow(inputText) } var isButtonEnabled by remember { mutableStateOf(true) } @@ -242,7 +245,8 @@ fun SwapContent( } } quoteResult?.let { - inputText = it.outAmount + val nextFromMaxDecimalPlaces = toToken.tradeInputMaxDecimalPlaces() + inputText = limitTradeInputDecimalPlaces(it.outAmount, nextFromMaxDecimalPlaces) quoteResult = null } context.clickVibrate() @@ -268,11 +272,12 @@ fun SwapContent( onInputChanged = { inputText = it }, onDeposit = onDeposit, displayBalanceOverride = if (from.isNativeSolAsset()) fromBalance else null, + maxDecimalPlaces = fromMaxDecimalPlaces, onMax = { AnalyticsTracker.trackSpotSendInputBalance() val balance = availableFromBalanceValue if (balance > BigDecimal.ZERO) { - inputText = balance.stripTrailingZeros().toPlainString() + inputText = limitTradeInputDecimalPlaces(balance.stripTrailingZeros().toPlainString(), fromMaxDecimalPlaces) } else { inputText = "" } @@ -301,7 +306,9 @@ fun SwapContent( inputText = inputText, quoteMin = quoteMin, quoteMax = quoteMax, - onInputTextChange = { inputText = it }, + onInputTextChange = { + inputText = limitTradeInputDecimalPlaces(it, fromMaxDecimalPlaces) + }, onInvalidFlagChange = { invalidFlag = !invalidFlag }, onSwitchToLimitOrder = onSwitchToLimitOrder, ) @@ -341,7 +348,7 @@ fun SwapContent( InputAction("25%", showBorder = true) { AnalyticsTracker.trackSpotSendInputPercent("25%") if (balance > BigDecimal.ZERO) { - inputText = (balance * BigDecimal("0.25")).stripTrailingZeros().toPlainString() + inputText = limitTradeInputDecimalPlaces((balance * BigDecimal("0.25")).stripTrailingZeros().toPlainString(), fromMaxDecimalPlaces) } else { inputText = "" } @@ -349,7 +356,7 @@ fun SwapContent( InputAction("50%", showBorder = true) { AnalyticsTracker.trackSpotSendInputPercent("50%") if (balance > BigDecimal.ZERO) { - inputText = (balance * BigDecimal("0.5")).stripTrailingZeros().toPlainString() + inputText = limitTradeInputDecimalPlaces((balance * BigDecimal("0.5")).stripTrailingZeros().toPlainString(), fromMaxDecimalPlaces) } else { inputText = "" } @@ -357,7 +364,7 @@ fun SwapContent( InputAction(stringResource(R.string.Max), showBorder = true) { AnalyticsTracker.trackSpotSendInputPercent("max") if (balance > BigDecimal.ZERO) { - inputText = balance.stripTrailingZeros().toPlainString() + inputText = limitTradeInputDecimalPlaces(balance.stripTrailingZeros().toPlainString(), fromMaxDecimalPlaces) } else { inputText = "" } diff --git a/app/src/test/java/one/mixin/android/ui/home/web3/trade/TradeInputTest.kt b/app/src/test/java/one/mixin/android/ui/home/web3/trade/TradeInputTest.kt new file mode 100644 index 0000000000..8a29dd5092 --- /dev/null +++ b/app/src/test/java/one/mixin/android/ui/home/web3/trade/TradeInputTest.kt @@ -0,0 +1,51 @@ +package one.mixin.android.ui.home.web3.trade + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class TradeInputTest { + @Test + fun tradeAmountInputUsesWalletSpecificDecimalPlaces() { + assertEquals(8, tradeInputMaxDecimalPlaces(isCommonWallet = false, precision = 18)) + assertEquals(6, tradeInputMaxDecimalPlaces(isCommonWallet = true, precision = 6)) + assertEquals(0, tradeInputMaxDecimalPlaces(isCommonWallet = true, precision = 0)) + assertEquals(8, tradeInputMaxDecimalPlaces(isCommonWallet = true, precision = -1)) + } + + @Test + fun tradePriceInputAllowsAtMostEightDecimalPlaces() { + assertEquals(8, tradePriceInputMaxDecimalPlaces()) + assertTrue(isTradeInputDecimalAllowed("1.12345678", tradePriceInputMaxDecimalPlaces())) + assertFalse(isTradeInputDecimalAllowed("1.123456789", tradePriceInputMaxDecimalPlaces())) + } + + @Test + fun tradeAmountInputAllowsAtMostEightDecimalPlaces() { + assertTrue(isTradeInputDecimalAllowed("")) + assertTrue(isTradeInputDecimalAllowed("12")) + assertTrue(isTradeInputDecimalAllowed("12.")) + assertTrue(isTradeInputDecimalAllowed("12.12345678")) + assertTrue(isTradeInputDecimalAllowed("0.00000000")) + + assertFalse(isTradeInputDecimalAllowed("12.123456789")) + assertFalse(isTradeInputDecimalAllowed("0.000000001")) + + assertTrue(isTradeInputDecimalAllowed("12.123456789", maxDecimalPlaces = null)) + } + + @Test + fun tradeAmountInputLimitsProgrammaticValuesToEightDecimalPlaces() { + assertEquals("", limitTradeInputDecimalPlaces("")) + assertEquals("12", limitTradeInputDecimalPlaces("12")) + assertEquals("12.", limitTradeInputDecimalPlaces("12.")) + assertEquals("12.12345678", limitTradeInputDecimalPlaces("12.12345678")) + assertEquals("12.12345678", limitTradeInputDecimalPlaces("12.123456789")) + assertEquals("0.00000000", limitTradeInputDecimalPlaces("0.000000001")) + assertEquals( + "12.123456789", + limitTradeInputDecimalPlaces("12.123456789", maxDecimalPlaces = null) + ) + } +} From 18440e81dc2fae7c1ae42643e5d519b6851db2ca Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Wed, 10 Jun 2026 13:49:41 +0800 Subject: [PATCH 7/7] fix(wallet): align pending raw transaction state --- app/src/main/java/one/mixin/android/vo/SnapshotItem.kt | 4 ++-- .../test/java/one/mixin/android/vo/SnapshotItemTest.kt | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/one/mixin/android/vo/SnapshotItem.kt b/app/src/main/java/one/mixin/android/vo/SnapshotItem.kt index 0588b498f3..3ec540ea16 100644 --- a/app/src/main/java/one/mixin/android/vo/SnapshotItem.kt +++ b/app/src/main/java/one/mixin/android/vo/SnapshotItem.kt @@ -12,8 +12,8 @@ import kotlinx.serialization.SerialName import one.mixin.android.extension.hexString import one.mixin.android.extension.isByteArrayValidUtf8 import one.mixin.android.extension.isValidHex -import one.mixin.android.vo.safe.OutputState import one.mixin.android.vo.safe.RawTransaction +import one.mixin.android.vo.safe.RawTransactionState import one.mixin.android.vo.safe.SafeDeposit import one.mixin.android.vo.safe.SafeSnapshotType import one.mixin.android.vo.safe.SafeWithdrawal @@ -162,7 +162,7 @@ data class SnapshotItem( fun isPendingWithdrawal() = withdrawal != null && withdrawal.withdrawalHash.isNullOrBlank() - fun shouldShowPendingHash(rawTransaction: RawTransaction?) = rawTransaction?.state == OutputState.unspent + fun shouldShowPendingHash(rawTransaction: RawTransaction?) = rawTransaction?.state == RawTransactionState.signed } @Parcelize diff --git a/app/src/test/java/one/mixin/android/vo/SnapshotItemTest.kt b/app/src/test/java/one/mixin/android/vo/SnapshotItemTest.kt index a1445efba0..8ca98b5f33 100644 --- a/app/src/test/java/one/mixin/android/vo/SnapshotItemTest.kt +++ b/app/src/test/java/one/mixin/android/vo/SnapshotItemTest.kt @@ -2,8 +2,8 @@ package one.mixin.android.vo import kotlin.test.Test import kotlin.test.assertEquals -import one.mixin.android.vo.safe.OutputState import one.mixin.android.vo.safe.RawTransaction +import one.mixin.android.vo.safe.RawTransactionState import one.mixin.android.vo.safe.RawTransactionType import one.mixin.android.vo.safe.SafeSnapshotType @@ -11,7 +11,7 @@ class SnapshotItemTest { @Test fun `pending hash is shown when raw transaction is unspent`() { val snapshot = snapshotItem(type = SafeSnapshotType.snapshot.name, traceId = "trace-id") - val rawTransaction = rawTransaction("trace-id", OutputState.unspent) + val rawTransaction = rawTransaction("trace-id", RawTransactionState.signed) assertEquals(true, snapshot.shouldShowPendingHash(rawTransaction)) } @@ -19,7 +19,7 @@ class SnapshotItemTest { @Test fun `pending hash is hidden when raw transaction was sent`() { val snapshot = snapshotItem(type = SafeSnapshotType.snapshot.name, traceId = "trace-id") - val rawTransaction = rawTransaction("trace-id", OutputState.signed) + val rawTransaction = rawTransaction("trace-id", RawTransactionState.spent) assertEquals(false, snapshot.shouldShowPendingHash(rawTransaction)) } @@ -33,7 +33,7 @@ class SnapshotItemTest { private fun rawTransaction( requestId: String, - state: OutputState, + state: RawTransactionState, ) = RawTransaction( requestId = requestId, rawTransaction = "raw",