From 8238d37ffe7ae446e0c1415d7519ae2e39516360 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 17 Sep 2026 11:58:49 +0700 Subject: [PATCH 1/3] fix(kotlin-sdk): persist the contract bounds kind so group bounds round-trip Android stored a key's bounds as an id plus an optional document type name and inferred the kind on restore, so a key bound to a contract group (kind 3) came back as a single-contract bound to the group id and the local signing preflight refused valid batches until the identity was refreshed. - public_keys gains a nullable contractBoundsKind column (schema 13, manual MIGRATION_12_13 in the style of the earlier bumps; 12 is the key usage limits schema from #4811, whose exported 12.json is committed here too). The persist callback records the kind the native row carries; restore honours it and keeps the old inference for legacy rows with a NULL kind. A stored kind this build does not know, or a stored 0 next to a stale blob, restores unbounded, the same fallback the Swift restore path applies; a kind 2 without its document type demotes to 1 as Rust does. - ContractBounds.ContractGroup in the SDK type, kind 3 in IdentityPubkeyCodec (an id and no document type, ahead of the limits flags), kind docs updated in the bridges and the JNI blob layout. - Tests: kind 3 and every other kind round-trip through the handler, legacy rows still infer 0, 1 and 2, unknown and zero kinds restore unbounded, the codec bytes for kind 3, the schema version and column, and a 12 to 13 migration test (instrumented, compiled only). Co-Authored-By: Claude Fable 5.1 --- .../12.json | 4172 ++++++++++++++++ .../13.json | 4177 +++++++++++++++++ .../persistence/DashDatabaseMigrationTest.kt | 62 +- .../dashsdk/ffi/NativePersistenceBridge.kt | 17 +- .../dashsdk/ffi/TransactionsNative.kt | 6 +- .../dashsdk/identity/IdentityPubkeyCodec.kt | 13 +- .../dashsdk/identity/IdentityUpdates.kt | 26 +- .../dashsdk/persistence/DashDatabase.kt | 31 +- .../PlatformWalletPersistenceHandler.kt | 40 +- .../persistence/entities/PublicKeyEntity.kt | 7 + .../dashsdk/identity/RegistrationKeysTest.kt | 69 + .../dashsdk/persistence/DashDatabaseTest.kt | 28 +- .../PlatformWalletPersistenceHandlerTest.kt | 179 + 13 files changed, 8796 insertions(+), 31 deletions(-) create mode 100644 packages/kotlin-sdk/sdk/schemas/org.dashfoundation.dashsdk.persistence.DashDatabase/12.json create mode 100644 packages/kotlin-sdk/sdk/schemas/org.dashfoundation.dashsdk.persistence.DashDatabase/13.json diff --git a/packages/kotlin-sdk/sdk/schemas/org.dashfoundation.dashsdk.persistence.DashDatabase/12.json b/packages/kotlin-sdk/sdk/schemas/org.dashfoundation.dashsdk.persistence.DashDatabase/12.json new file mode 100644 index 00000000000..755d5afeb7b --- /dev/null +++ b/packages/kotlin-sdk/sdk/schemas/org.dashfoundation.dashsdk.persistence.DashDatabase/12.json @@ -0,0 +1,4172 @@ +{ + "formatVersion": 1, + "database": { + "version": 12, + "identityHash": "0ef5dd81ffff7648267f76db90ccfc11", + "entities": [ + { + "tableName": "wallets", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`walletId` BLOB NOT NULL, `walletGroupId` BLOB NOT NULL, `networkRaw` INTEGER, `name` TEXT, `walletDescription` TEXT, `birthHeight` INTEGER NOT NULL, `syncedHeight` INTEGER NOT NULL, `lastSynced` INTEGER NOT NULL, `lastAppliedChainLockBytes` BLOB, `lastAppliedChainLockHeight` INTEGER, `isImported` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`walletId`))", + "fields": [ + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "walletGroupId", + "columnName": "walletGroupId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "networkRaw", + "columnName": "networkRaw", + "affinity": "INTEGER" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT" + }, + { + "fieldPath": "walletDescription", + "columnName": "walletDescription", + "affinity": "TEXT" + }, + { + "fieldPath": "birthHeight", + "columnName": "birthHeight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "syncedHeight", + "columnName": "syncedHeight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastSynced", + "columnName": "lastSynced", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastAppliedChainLockBytes", + "columnName": "lastAppliedChainLockBytes", + "affinity": "BLOB" + }, + { + "fieldPath": "lastAppliedChainLockHeight", + "columnName": "lastAppliedChainLockHeight", + "affinity": "INTEGER" + }, + { + "fieldPath": "isImported", + "columnName": "isImported", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "walletId" + ] + }, + "indices": [ + { + "name": "index_wallets_networkRaw", + "unique": false, + "columnNames": [ + "networkRaw" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_wallets_networkRaw` ON `${TABLE_NAME}` (`networkRaw`)" + }, + { + "name": "index_wallets_walletGroupId", + "unique": false, + "columnNames": [ + "walletGroupId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_wallets_walletGroupId` ON `${TABLE_NAME}` (`walletGroupId`)" + } + ] + }, + { + "tableName": "accounts", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `walletId` BLOB NOT NULL, `accountType` INTEGER NOT NULL, `accountIndex` INTEGER NOT NULL, `accountTypeName` TEXT NOT NULL, `balanceConfirmed` INTEGER NOT NULL, `balanceUnconfirmed` INTEGER NOT NULL, `externalHighestUsed` INTEGER NOT NULL, `internalHighestUsed` INTEGER NOT NULL, `standardTag` INTEGER NOT NULL, `registrationIndex` INTEGER NOT NULL, `keyClass` INTEGER NOT NULL, `userIdentityId` BLOB NOT NULL, `friendIdentityId` BLOB NOT NULL, `accountExtendedPubKeyBytes` BLOB, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, FOREIGN KEY(`walletId`) REFERENCES `wallets`(`walletId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "accountType", + "columnName": "accountType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountIndex", + "columnName": "accountIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountTypeName", + "columnName": "accountTypeName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "balanceConfirmed", + "columnName": "balanceConfirmed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "balanceUnconfirmed", + "columnName": "balanceUnconfirmed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "externalHighestUsed", + "columnName": "externalHighestUsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "internalHighestUsed", + "columnName": "internalHighestUsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "standardTag", + "columnName": "standardTag", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationIndex", + "columnName": "registrationIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keyClass", + "columnName": "keyClass", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userIdentityId", + "columnName": "userIdentityId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "friendIdentityId", + "columnName": "friendIdentityId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "accountExtendedPubKeyBytes", + "columnName": "accountExtendedPubKeyBytes", + "affinity": "BLOB" + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_accounts_walletId", + "unique": false, + "columnNames": [ + "walletId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_accounts_walletId` ON `${TABLE_NAME}` (`walletId`)" + }, + { + "name": "index_accounts_walletId_accountType_accountIndex_standardTag_registrationIndex_keyClass_userIdentityId_friendIdentityId", + "unique": true, + "columnNames": [ + "walletId", + "accountType", + "accountIndex", + "standardTag", + "registrationIndex", + "keyClass", + "userIdentityId", + "friendIdentityId" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_accounts_walletId_accountType_accountIndex_standardTag_registrationIndex_keyClass_userIdentityId_friendIdentityId` ON `${TABLE_NAME}` (`walletId`, `accountType`, `accountIndex`, `standardTag`, `registrationIndex`, `keyClass`, `userIdentityId`, `friendIdentityId`)" + }, + { + "name": "index_accounts_accountExtendedPubKeyBytes", + "unique": true, + "columnNames": [ + "accountExtendedPubKeyBytes" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_accounts_accountExtendedPubKeyBytes` ON `${TABLE_NAME}` (`accountExtendedPubKeyBytes`)" + } + ], + "foreignKeys": [ + { + "table": "wallets", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "walletId" + ], + "referencedColumns": [ + "walletId" + ] + } + ] + }, + { + "tableName": "transactions", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`txid` BLOB NOT NULL, `transactionData` BLOB NOT NULL, `context` INTEGER NOT NULL, `blockHeight` INTEGER NOT NULL, `blockHash` BLOB, `blockTimestamp` INTEGER NOT NULL, `blockPosition` INTEGER NOT NULL, `hasBlockPosition` INTEGER NOT NULL, `direction` INTEGER NOT NULL, `transactionType` TEXT NOT NULL, `transactionTypeKind` INTEGER NOT NULL, `netAmount` INTEGER NOT NULL, `fee` INTEGER, `label` TEXT NOT NULL, `firstSeen` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`txid`))", + "fields": [ + { + "fieldPath": "txid", + "columnName": "txid", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "transactionData", + "columnName": "transactionData", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "context", + "columnName": "context", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "blockHeight", + "columnName": "blockHeight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "blockHash", + "columnName": "blockHash", + "affinity": "BLOB" + }, + { + "fieldPath": "blockTimestamp", + "columnName": "blockTimestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "blockPosition", + "columnName": "blockPosition", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasBlockPosition", + "columnName": "hasBlockPosition", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "direction", + "columnName": "direction", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "transactionType", + "columnName": "transactionType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "transactionTypeKind", + "columnName": "transactionTypeKind", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "netAmount", + "columnName": "netAmount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fee", + "columnName": "fee", + "affinity": "INTEGER" + }, + { + "fieldPath": "label", + "columnName": "label", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstSeen", + "columnName": "firstSeen", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "txid" + ] + }, + "indices": [ + { + "name": "index_transactions_firstSeen", + "unique": false, + "columnNames": [ + "firstSeen" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_transactions_firstSeen` ON `${TABLE_NAME}` (`firstSeen`)" + } + ] + }, + { + "tableName": "transaction_account_involvements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`transactionTxid` BLOB NOT NULL, `accountId` INTEGER NOT NULL, PRIMARY KEY(`transactionTxid`, `accountId`), FOREIGN KEY(`transactionTxid`) REFERENCES `transactions`(`txid`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`accountId`) REFERENCES `accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "transactionTxid", + "columnName": "transactionTxid", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "accountId", + "columnName": "accountId", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "transactionTxid", + "accountId" + ] + }, + "indices": [ + { + "name": "index_transaction_account_involvements_accountId", + "unique": false, + "columnNames": [ + "accountId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_transaction_account_involvements_accountId` ON `${TABLE_NAME}` (`accountId`)" + } + ], + "foreignKeys": [ + { + "table": "transactions", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "transactionTxid" + ], + "referencedColumns": [ + "txid" + ] + }, + { + "table": "accounts", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "accountId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "txos", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`outpoint` BLOB NOT NULL, `vout` INTEGER NOT NULL, `amount` INTEGER NOT NULL, `address` TEXT NOT NULL, `scriptPubKey` BLOB NOT NULL, `height` INTEGER NOT NULL, `isCoinbase` INTEGER NOT NULL, `isConfirmed` INTEGER NOT NULL, `isInstantLocked` INTEGER NOT NULL, `isLocked` INTEGER NOT NULL, `isSpent` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, `walletId` BLOB NOT NULL, `txid` BLOB, `spendingTxid` BLOB, `spendingInputIndex` INTEGER, `accountId` INTEGER, `coreAddressId` TEXT, `supersededByTxid` BLOB, PRIMARY KEY(`outpoint`), FOREIGN KEY(`txid`) REFERENCES `transactions`(`txid`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`spendingTxid`) REFERENCES `transactions`(`txid`) ON UPDATE NO ACTION ON DELETE SET NULL , FOREIGN KEY(`accountId`) REFERENCES `accounts`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL , FOREIGN KEY(`coreAddressId`) REFERENCES `core_addresses`(`address`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "outpoint", + "columnName": "outpoint", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "vout", + "columnName": "vout", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "scriptPubKey", + "columnName": "scriptPubKey", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "height", + "columnName": "height", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCoinbase", + "columnName": "isCoinbase", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isConfirmed", + "columnName": "isConfirmed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isInstantLocked", + "columnName": "isInstantLocked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isLocked", + "columnName": "isLocked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isSpent", + "columnName": "isSpent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "txid", + "columnName": "txid", + "affinity": "BLOB" + }, + { + "fieldPath": "spendingTxid", + "columnName": "spendingTxid", + "affinity": "BLOB" + }, + { + "fieldPath": "spendingInputIndex", + "columnName": "spendingInputIndex", + "affinity": "INTEGER" + }, + { + "fieldPath": "accountId", + "columnName": "accountId", + "affinity": "INTEGER" + }, + { + "fieldPath": "coreAddressId", + "columnName": "coreAddressId", + "affinity": "TEXT" + }, + { + "fieldPath": "supersededByTxid", + "columnName": "supersededByTxid", + "affinity": "BLOB" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "outpoint" + ] + }, + "indices": [ + { + "name": "index_txos_walletId", + "unique": false, + "columnNames": [ + "walletId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_txos_walletId` ON `${TABLE_NAME}` (`walletId`)" + }, + { + "name": "index_txos_txid", + "unique": false, + "columnNames": [ + "txid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_txos_txid` ON `${TABLE_NAME}` (`txid`)" + }, + { + "name": "index_txos_spendingTxid", + "unique": false, + "columnNames": [ + "spendingTxid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_txos_spendingTxid` ON `${TABLE_NAME}` (`spendingTxid`)" + }, + { + "name": "index_txos_accountId", + "unique": false, + "columnNames": [ + "accountId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_txos_accountId` ON `${TABLE_NAME}` (`accountId`)" + }, + { + "name": "index_txos_coreAddressId", + "unique": false, + "columnNames": [ + "coreAddressId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_txos_coreAddressId` ON `${TABLE_NAME}` (`coreAddressId`)" + } + ], + "foreignKeys": [ + { + "table": "transactions", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "txid" + ], + "referencedColumns": [ + "txid" + ] + }, + { + "table": "transactions", + "onDelete": "SET NULL", + "onUpdate": "NO ACTION", + "columns": [ + "spendingTxid" + ], + "referencedColumns": [ + "txid" + ] + }, + { + "table": "accounts", + "onDelete": "SET NULL", + "onUpdate": "NO ACTION", + "columns": [ + "accountId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "core_addresses", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "coreAddressId" + ], + "referencedColumns": [ + "address" + ] + } + ] + }, + { + "tableName": "core_addresses", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`address` TEXT NOT NULL, `publicKey` BLOB NOT NULL, `poolTypeTag` INTEGER NOT NULL, `addressIndex` INTEGER NOT NULL, `derivationPath` TEXT NOT NULL, `isUsed` INTEGER NOT NULL, `firstSeenHeight` INTEGER NOT NULL, `lastSeenHeight` INTEGER NOT NULL, `balance` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, `accountId` INTEGER, PRIMARY KEY(`address`), FOREIGN KEY(`accountId`) REFERENCES `accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "publicKey", + "columnName": "publicKey", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "poolTypeTag", + "columnName": "poolTypeTag", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addressIndex", + "columnName": "addressIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "derivationPath", + "columnName": "derivationPath", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isUsed", + "columnName": "isUsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstSeenHeight", + "columnName": "firstSeenHeight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastSeenHeight", + "columnName": "lastSeenHeight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "balance", + "columnName": "balance", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountId", + "columnName": "accountId", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "address" + ] + }, + "indices": [ + { + "name": "index_core_addresses_accountId", + "unique": false, + "columnNames": [ + "accountId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_core_addresses_accountId` ON `${TABLE_NAME}` (`accountId`)" + } + ], + "foreignKeys": [ + { + "table": "accounts", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "accountId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "asset_locks", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`outPointHex` TEXT NOT NULL, `walletId` BLOB NOT NULL, `transactionBytes` BLOB NOT NULL, `fundingTypeRaw` INTEGER NOT NULL, `identityIndexRaw` INTEGER NOT NULL, `accountIndexRaw` INTEGER NOT NULL, `amountDuffs` INTEGER NOT NULL, `statusRaw` INTEGER NOT NULL, `proofBytes` BLOB, `recipientPlatformAddressHash` BLOB, `recipientPlatformAddressType` INTEGER, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, PRIMARY KEY(`outPointHex`))", + "fields": [ + { + "fieldPath": "outPointHex", + "columnName": "outPointHex", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "transactionBytes", + "columnName": "transactionBytes", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "fundingTypeRaw", + "columnName": "fundingTypeRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "identityIndexRaw", + "columnName": "identityIndexRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountIndexRaw", + "columnName": "accountIndexRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "amountDuffs", + "columnName": "amountDuffs", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "statusRaw", + "columnName": "statusRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "proofBytes", + "columnName": "proofBytes", + "affinity": "BLOB" + }, + { + "fieldPath": "recipientPlatformAddressHash", + "columnName": "recipientPlatformAddressHash", + "affinity": "BLOB" + }, + { + "fieldPath": "recipientPlatformAddressType", + "columnName": "recipientPlatformAddressType", + "affinity": "INTEGER" + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updatedAt", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "outPointHex" + ] + }, + "indices": [ + { + "name": "index_asset_locks_walletId", + "unique": false, + "columnNames": [ + "walletId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_asset_locks_walletId` ON `${TABLE_NAME}` (`walletId`)" + } + ] + }, + { + "tableName": "invitations", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`outPointHex` TEXT NOT NULL, `rawOutPoint` BLOB NOT NULL, `walletId` BLOB NOT NULL, `fundingIndexRaw` INTEGER NOT NULL, `amountDuffs` INTEGER NOT NULL, `expiryUnix` INTEGER NOT NULL, `createdAtSecs` INTEGER NOT NULL, `hasInviter` INTEGER NOT NULL, `statusRaw` INTEGER NOT NULL, `reclaimInFlight` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, PRIMARY KEY(`outPointHex`))", + "fields": [ + { + "fieldPath": "outPointHex", + "columnName": "outPointHex", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "rawOutPoint", + "columnName": "rawOutPoint", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "fundingIndexRaw", + "columnName": "fundingIndexRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "amountDuffs", + "columnName": "amountDuffs", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "expiryUnix", + "columnName": "expiryUnix", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAtSecs", + "columnName": "createdAtSecs", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasInviter", + "columnName": "hasInviter", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "statusRaw", + "columnName": "statusRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "reclaimInFlight", + "columnName": "reclaimInFlight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updatedAt", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "outPointHex" + ] + }, + "indices": [ + { + "name": "index_invitations_walletId", + "unique": false, + "columnNames": [ + "walletId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_invitations_walletId` ON `${TABLE_NAME}` (`walletId`)" + } + ] + }, + { + "tableName": "identities", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`identityId` BLOB NOT NULL, `balance` INTEGER NOT NULL, `revision` INTEGER NOT NULL, `isLocal` INTEGER NOT NULL, `alias` TEXT, `dpnsName` TEXT, `mainDpnsName` TEXT, `identityType` TEXT NOT NULL, `votingPrivateKeyIdentifier` TEXT, `ownerPrivateKeyIdentifier` TEXT, `payoutPrivateKeyIdentifier` TEXT, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, `lastSyncedAt` INTEGER, `networkRaw` INTEGER NOT NULL, `walletId` BLOB, `identityIndex` INTEGER NOT NULL, PRIMARY KEY(`identityId`), FOREIGN KEY(`walletId`) REFERENCES `wallets`(`walletId`) ON UPDATE NO ACTION ON DELETE SET NULL )", + "fields": [ + { + "fieldPath": "identityId", + "columnName": "identityId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "balance", + "columnName": "balance", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "revision", + "columnName": "revision", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isLocal", + "columnName": "isLocal", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "alias", + "columnName": "alias", + "affinity": "TEXT" + }, + { + "fieldPath": "dpnsName", + "columnName": "dpnsName", + "affinity": "TEXT" + }, + { + "fieldPath": "mainDpnsName", + "columnName": "mainDpnsName", + "affinity": "TEXT" + }, + { + "fieldPath": "identityType", + "columnName": "identityType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "votingPrivateKeyIdentifier", + "columnName": "votingPrivateKeyIdentifier", + "affinity": "TEXT" + }, + { + "fieldPath": "ownerPrivateKeyIdentifier", + "columnName": "ownerPrivateKeyIdentifier", + "affinity": "TEXT" + }, + { + "fieldPath": "payoutPrivateKeyIdentifier", + "columnName": "payoutPrivateKeyIdentifier", + "affinity": "TEXT" + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastSyncedAt", + "columnName": "lastSyncedAt", + "affinity": "INTEGER" + }, + { + "fieldPath": "networkRaw", + "columnName": "networkRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "BLOB" + }, + { + "fieldPath": "identityIndex", + "columnName": "identityIndex", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "identityId" + ] + }, + "indices": [ + { + "name": "index_identities_networkRaw", + "unique": false, + "columnNames": [ + "networkRaw" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_identities_networkRaw` ON `${TABLE_NAME}` (`networkRaw`)" + }, + { + "name": "index_identities_walletId", + "unique": false, + "columnNames": [ + "walletId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_identities_walletId` ON `${TABLE_NAME}` (`walletId`)" + } + ], + "foreignKeys": [ + { + "table": "wallets", + "onDelete": "SET NULL", + "onUpdate": "NO ACTION", + "columns": [ + "walletId" + ], + "referencedColumns": [ + "walletId" + ] + } + ] + }, + { + "tableName": "public_keys", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `keyId` INTEGER NOT NULL, `purpose` TEXT NOT NULL, `securityLevel` TEXT NOT NULL, `keyType` TEXT NOT NULL, `readOnly` INTEGER NOT NULL, `disabledAt` INTEGER, `totalBudget` INTEGER, `expiresAt` INTEGER, `publicKeyData` BLOB NOT NULL, `contractBoundsData` BLOB, `contractBoundsDocumentTypeName` TEXT, `privateKeyKeychainIdentifier` TEXT, `derivationIdentityIndex` INTEGER, `derivationKeyIndex` INTEGER, `identityId` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `lastAccessed` INTEGER, `identityIdData` BLOB, FOREIGN KEY(`identityIdData`) REFERENCES `identities`(`identityId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keyId", + "columnName": "keyId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "purpose", + "columnName": "purpose", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "securityLevel", + "columnName": "securityLevel", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "keyType", + "columnName": "keyType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "readOnly", + "columnName": "readOnly", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "disabledAt", + "columnName": "disabledAt", + "affinity": "INTEGER" + }, + { + "fieldPath": "totalBudget", + "columnName": "totalBudget", + "affinity": "INTEGER" + }, + { + "fieldPath": "expiresAt", + "columnName": "expiresAt", + "affinity": "INTEGER" + }, + { + "fieldPath": "publicKeyData", + "columnName": "publicKeyData", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "contractBoundsData", + "columnName": "contractBoundsData", + "affinity": "BLOB" + }, + { + "fieldPath": "contractBoundsDocumentTypeName", + "columnName": "contractBoundsDocumentTypeName", + "affinity": "TEXT" + }, + { + "fieldPath": "privateKeyKeychainIdentifier", + "columnName": "privateKeyKeychainIdentifier", + "affinity": "TEXT" + }, + { + "fieldPath": "derivationIdentityIndex", + "columnName": "derivationIdentityIndex", + "affinity": "INTEGER" + }, + { + "fieldPath": "derivationKeyIndex", + "columnName": "derivationKeyIndex", + "affinity": "INTEGER" + }, + { + "fieldPath": "identityId", + "columnName": "identityId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastAccessed", + "columnName": "lastAccessed", + "affinity": "INTEGER" + }, + { + "fieldPath": "identityIdData", + "columnName": "identityIdData", + "affinity": "BLOB" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_public_keys_identityId_keyId", + "unique": false, + "columnNames": [ + "identityId", + "keyId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_public_keys_identityId_keyId` ON `${TABLE_NAME}` (`identityId`, `keyId`)" + }, + { + "name": "index_public_keys_identityIdData", + "unique": false, + "columnNames": [ + "identityIdData" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_public_keys_identityIdData` ON `${TABLE_NAME}` (`identityIdData`)" + }, + { + "name": "index_public_keys_publicKeyData", + "unique": false, + "columnNames": [ + "publicKeyData" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_public_keys_publicKeyData` ON `${TABLE_NAME}` (`publicKeyData`)" + } + ], + "foreignKeys": [ + { + "table": "identities", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "identityIdData" + ], + "referencedColumns": [ + "identityId" + ] + } + ] + }, + { + "tableName": "dpns_names", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`networkRaw` INTEGER NOT NULL, `label` TEXT NOT NULL, `normalizedLabel` TEXT NOT NULL, `parentDomainName` TEXT NOT NULL, `normalizedParentDomainName` TEXT NOT NULL, `acquiredAt` INTEGER NOT NULL, `identityId` BLOB NOT NULL, `documentId` BLOB, `isOwned` INTEGER NOT NULL, `priceCredits` INTEGER, `saleStatusRaw` INTEGER NOT NULL, `counterpartyIdentityId` BLOB, `documentCreatedAtMs` INTEGER NOT NULL, `documentUpdatedAtMs` INTEGER NOT NULL, `documentTransferredAtMs` INTEGER NOT NULL, `marketplaceUpdatedAt` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`networkRaw`, `normalizedParentDomainName`, `normalizedLabel`), FOREIGN KEY(`identityId`) REFERENCES `identities`(`identityId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "networkRaw", + "columnName": "networkRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "label", + "columnName": "label", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "normalizedLabel", + "columnName": "normalizedLabel", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentDomainName", + "columnName": "parentDomainName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "normalizedParentDomainName", + "columnName": "normalizedParentDomainName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "acquiredAt", + "columnName": "acquiredAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "identityId", + "columnName": "identityId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "documentId", + "columnName": "documentId", + "affinity": "BLOB" + }, + { + "fieldPath": "isOwned", + "columnName": "isOwned", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "priceCredits", + "columnName": "priceCredits", + "affinity": "INTEGER" + }, + { + "fieldPath": "saleStatusRaw", + "columnName": "saleStatusRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "counterpartyIdentityId", + "columnName": "counterpartyIdentityId", + "affinity": "BLOB" + }, + { + "fieldPath": "documentCreatedAtMs", + "columnName": "documentCreatedAtMs", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "documentUpdatedAtMs", + "columnName": "documentUpdatedAtMs", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "documentTransferredAtMs", + "columnName": "documentTransferredAtMs", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "marketplaceUpdatedAt", + "columnName": "marketplaceUpdatedAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "networkRaw", + "normalizedParentDomainName", + "normalizedLabel" + ] + }, + "indices": [ + { + "name": "index_dpns_names_identityId", + "unique": false, + "columnNames": [ + "identityId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_dpns_names_identityId` ON `${TABLE_NAME}` (`identityId`)" + }, + { + "name": "index_dpns_names_documentId", + "unique": false, + "columnNames": [ + "documentId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_dpns_names_documentId` ON `${TABLE_NAME}` (`documentId`)" + } + ], + "foreignKeys": [ + { + "table": "identities", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "identityId" + ], + "referencedColumns": [ + "identityId" + ] + } + ] + }, + { + "tableName": "dashpay_profiles", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`networkRaw` INTEGER NOT NULL, `identityId` BLOB NOT NULL, `displayName` TEXT, `publicMessage` TEXT, `bio` TEXT, `avatarUrl` TEXT, `avatarHash` BLOB, `avatarFingerprint` BLOB, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`networkRaw`, `identityId`), FOREIGN KEY(`identityId`) REFERENCES `identities`(`identityId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "networkRaw", + "columnName": "networkRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "identityId", + "columnName": "identityId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "displayName", + "columnName": "displayName", + "affinity": "TEXT" + }, + { + "fieldPath": "publicMessage", + "columnName": "publicMessage", + "affinity": "TEXT" + }, + { + "fieldPath": "bio", + "columnName": "bio", + "affinity": "TEXT" + }, + { + "fieldPath": "avatarUrl", + "columnName": "avatarUrl", + "affinity": "TEXT" + }, + { + "fieldPath": "avatarHash", + "columnName": "avatarHash", + "affinity": "BLOB" + }, + { + "fieldPath": "avatarFingerprint", + "columnName": "avatarFingerprint", + "affinity": "BLOB" + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "networkRaw", + "identityId" + ] + }, + "indices": [ + { + "name": "index_dashpay_profiles_identityId", + "unique": false, + "columnNames": [ + "identityId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_dashpay_profiles_identityId` ON `${TABLE_NAME}` (`identityId`)" + } + ], + "foreignKeys": [ + { + "table": "identities", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "identityId" + ], + "referencedColumns": [ + "identityId" + ] + } + ] + }, + { + "tableName": "dashpay_contact_requests", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`networkRaw` INTEGER NOT NULL, `ownerIdentityId` BLOB NOT NULL, `contactIdentityId` BLOB NOT NULL, `isOutgoing` INTEGER NOT NULL, `senderKeyIndex` INTEGER NOT NULL, `recipientKeyIndex` INTEGER NOT NULL, `accountReference` INTEGER NOT NULL, `encryptedPublicKey` BLOB NOT NULL, `encryptedAccountLabel` BLOB, `autoAcceptProof` BLOB, `coreHeightCreatedAt` INTEGER NOT NULL, `createdAtMillis` INTEGER NOT NULL, `paymentChannelBroken` INTEGER NOT NULL DEFAULT 0, `contactAlias` TEXT, `contactNote` TEXT, `contactHidden` INTEGER NOT NULL DEFAULT 0, `contactAccountLabel` TEXT, `contactAcceptedAccounts` BLOB, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`networkRaw`, `ownerIdentityId`, `contactIdentityId`, `isOutgoing`), FOREIGN KEY(`ownerIdentityId`) REFERENCES `identities`(`identityId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "networkRaw", + "columnName": "networkRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ownerIdentityId", + "columnName": "ownerIdentityId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "contactIdentityId", + "columnName": "contactIdentityId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "isOutgoing", + "columnName": "isOutgoing", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderKeyIndex", + "columnName": "senderKeyIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipientKeyIndex", + "columnName": "recipientKeyIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountReference", + "columnName": "accountReference", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "encryptedPublicKey", + "columnName": "encryptedPublicKey", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "encryptedAccountLabel", + "columnName": "encryptedAccountLabel", + "affinity": "BLOB" + }, + { + "fieldPath": "autoAcceptProof", + "columnName": "autoAcceptProof", + "affinity": "BLOB" + }, + { + "fieldPath": "coreHeightCreatedAt", + "columnName": "coreHeightCreatedAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAtMillis", + "columnName": "createdAtMillis", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "paymentChannelBroken", + "columnName": "paymentChannelBroken", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "contactAlias", + "columnName": "contactAlias", + "affinity": "TEXT" + }, + { + "fieldPath": "contactNote", + "columnName": "contactNote", + "affinity": "TEXT" + }, + { + "fieldPath": "contactHidden", + "columnName": "contactHidden", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "contactAccountLabel", + "columnName": "contactAccountLabel", + "affinity": "TEXT" + }, + { + "fieldPath": "contactAcceptedAccounts", + "columnName": "contactAcceptedAccounts", + "affinity": "BLOB" + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "networkRaw", + "ownerIdentityId", + "contactIdentityId", + "isOutgoing" + ] + }, + "indices": [ + { + "name": "index_dashpay_contact_requests_ownerIdentityId", + "unique": false, + "columnNames": [ + "ownerIdentityId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_dashpay_contact_requests_ownerIdentityId` ON `${TABLE_NAME}` (`ownerIdentityId`)" + } + ], + "foreignKeys": [ + { + "table": "identities", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "ownerIdentityId" + ], + "referencedColumns": [ + "identityId" + ] + } + ] + }, + { + "tableName": "dashpay_ignored_senders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`networkRaw` INTEGER NOT NULL, `ownerIdentityId` BLOB NOT NULL, `ignoredSenderId` BLOB NOT NULL, `ignoredAt` INTEGER NOT NULL, PRIMARY KEY(`networkRaw`, `ownerIdentityId`, `ignoredSenderId`), FOREIGN KEY(`ownerIdentityId`) REFERENCES `identities`(`identityId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "networkRaw", + "columnName": "networkRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ownerIdentityId", + "columnName": "ownerIdentityId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "ignoredSenderId", + "columnName": "ignoredSenderId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "ignoredAt", + "columnName": "ignoredAt", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "networkRaw", + "ownerIdentityId", + "ignoredSenderId" + ] + }, + "indices": [ + { + "name": "index_dashpay_ignored_senders_ownerIdentityId", + "unique": false, + "columnNames": [ + "ownerIdentityId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_dashpay_ignored_senders_ownerIdentityId` ON `${TABLE_NAME}` (`ownerIdentityId`)" + } + ], + "foreignKeys": [ + { + "table": "identities", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "ownerIdentityId" + ], + "referencedColumns": [ + "identityId" + ] + } + ] + }, + { + "tableName": "dashpay_contact_profiles", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`networkRaw` INTEGER NOT NULL, `ownerIdentityId` BLOB NOT NULL, `contactIdentityId` BLOB NOT NULL, `displayName` TEXT, `publicMessage` TEXT, `bio` TEXT, `avatarUrl` TEXT, `avatarHash` BLOB, `avatarFingerprint` BLOB, `checkedAtMs` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`networkRaw`, `ownerIdentityId`, `contactIdentityId`), FOREIGN KEY(`ownerIdentityId`) REFERENCES `identities`(`identityId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "networkRaw", + "columnName": "networkRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ownerIdentityId", + "columnName": "ownerIdentityId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "contactIdentityId", + "columnName": "contactIdentityId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "displayName", + "columnName": "displayName", + "affinity": "TEXT" + }, + { + "fieldPath": "publicMessage", + "columnName": "publicMessage", + "affinity": "TEXT" + }, + { + "fieldPath": "bio", + "columnName": "bio", + "affinity": "TEXT" + }, + { + "fieldPath": "avatarUrl", + "columnName": "avatarUrl", + "affinity": "TEXT" + }, + { + "fieldPath": "avatarHash", + "columnName": "avatarHash", + "affinity": "BLOB" + }, + { + "fieldPath": "avatarFingerprint", + "columnName": "avatarFingerprint", + "affinity": "BLOB" + }, + { + "fieldPath": "checkedAtMs", + "columnName": "checkedAtMs", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "networkRaw", + "ownerIdentityId", + "contactIdentityId" + ] + }, + "indices": [ + { + "name": "index_dashpay_contact_profiles_ownerIdentityId", + "unique": false, + "columnNames": [ + "ownerIdentityId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_dashpay_contact_profiles_ownerIdentityId` ON `${TABLE_NAME}` (`ownerIdentityId`)" + } + ], + "foreignKeys": [ + { + "table": "identities", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "ownerIdentityId" + ], + "referencedColumns": [ + "identityId" + ] + } + ] + }, + { + "tableName": "dashpay_payments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`networkRaw` INTEGER NOT NULL, `ownerIdentityId` BLOB NOT NULL, `counterpartyIdentityId` BLOB NOT NULL, `amountDuffs` INTEGER NOT NULL, `directionRaw` INTEGER NOT NULL, `statusRaw` INTEGER NOT NULL, `txid` TEXT NOT NULL, `memo` TEXT, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`networkRaw`, `ownerIdentityId`, `txid`), FOREIGN KEY(`ownerIdentityId`) REFERENCES `identities`(`identityId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "networkRaw", + "columnName": "networkRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ownerIdentityId", + "columnName": "ownerIdentityId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "counterpartyIdentityId", + "columnName": "counterpartyIdentityId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "amountDuffs", + "columnName": "amountDuffs", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "directionRaw", + "columnName": "directionRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "statusRaw", + "columnName": "statusRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "txid", + "columnName": "txid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "memo", + "columnName": "memo", + "affinity": "TEXT" + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "networkRaw", + "ownerIdentityId", + "txid" + ] + }, + "indices": [ + { + "name": "index_dashpay_payments_ownerIdentityId", + "unique": false, + "columnNames": [ + "ownerIdentityId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_dashpay_payments_ownerIdentityId` ON `${TABLE_NAME}` (`ownerIdentityId`)" + } + ], + "foreignKeys": [ + { + "table": "identities", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "ownerIdentityId" + ], + "referencedColumns": [ + "identityId" + ] + } + ] + }, + { + "tableName": "data_contracts", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` BLOB NOT NULL, `name` TEXT NOT NULL, `serializedContract` BLOB NOT NULL, `createdAt` INTEGER NOT NULL, `lastAccessedAt` INTEGER NOT NULL, `binarySerialization` BLOB, `version` INTEGER, `ownerId` BLOB, `contractDescription` TEXT, `schemaData` BLOB NOT NULL, `documentTypesData` BLOB NOT NULL, `groupsData` BLOB, `networkRaw` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, `lastSyncedAt` INTEGER, `canBeDeleted` INTEGER NOT NULL, `readonly` INTEGER NOT NULL, `keepsHistory` INTEGER NOT NULL, `schemaDefs` INTEGER, `documentsKeepHistoryContractDefault` INTEGER NOT NULL, `documentsMutableContractDefault` INTEGER NOT NULL, `documentsCanBeDeletedContractDefault` INTEGER NOT NULL, `hasTokens` INTEGER NOT NULL, `tokensData` BLOB, `ownerIdentityId` BLOB, PRIMARY KEY(`id`), FOREIGN KEY(`ownerIdentityId`) REFERENCES `identities`(`identityId`) ON UPDATE NO ACTION ON DELETE SET NULL )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serializedContract", + "columnName": "serializedContract", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastAccessedAt", + "columnName": "lastAccessedAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "binarySerialization", + "columnName": "binarySerialization", + "affinity": "BLOB" + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER" + }, + { + "fieldPath": "ownerId", + "columnName": "ownerId", + "affinity": "BLOB" + }, + { + "fieldPath": "contractDescription", + "columnName": "contractDescription", + "affinity": "TEXT" + }, + { + "fieldPath": "schemaData", + "columnName": "schemaData", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "documentTypesData", + "columnName": "documentTypesData", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "groupsData", + "columnName": "groupsData", + "affinity": "BLOB" + }, + { + "fieldPath": "networkRaw", + "columnName": "networkRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastSyncedAt", + "columnName": "lastSyncedAt", + "affinity": "INTEGER" + }, + { + "fieldPath": "canBeDeleted", + "columnName": "canBeDeleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readonly", + "columnName": "readonly", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keepsHistory", + "columnName": "keepsHistory", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "schemaDefs", + "columnName": "schemaDefs", + "affinity": "INTEGER" + }, + { + "fieldPath": "documentsKeepHistoryContractDefault", + "columnName": "documentsKeepHistoryContractDefault", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "documentsMutableContractDefault", + "columnName": "documentsMutableContractDefault", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "documentsCanBeDeletedContractDefault", + "columnName": "documentsCanBeDeletedContractDefault", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasTokens", + "columnName": "hasTokens", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tokensData", + "columnName": "tokensData", + "affinity": "BLOB" + }, + { + "fieldPath": "ownerIdentityId", + "columnName": "ownerIdentityId", + "affinity": "BLOB" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_data_contracts_networkRaw", + "unique": false, + "columnNames": [ + "networkRaw" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_data_contracts_networkRaw` ON `${TABLE_NAME}` (`networkRaw`)" + }, + { + "name": "index_data_contracts_ownerIdentityId", + "unique": false, + "columnNames": [ + "ownerIdentityId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_data_contracts_ownerIdentityId` ON `${TABLE_NAME}` (`ownerIdentityId`)" + } + ], + "foreignKeys": [ + { + "table": "identities", + "onDelete": "SET NULL", + "onUpdate": "NO ACTION", + "columns": [ + "ownerIdentityId" + ], + "referencedColumns": [ + "identityId" + ] + } + ] + }, + { + "tableName": "document_types", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` BLOB NOT NULL, `contractId` BLOB NOT NULL, `name` TEXT NOT NULL, `schemaJSON` BLOB NOT NULL, `propertiesJSON` BLOB NOT NULL, `documentsKeepHistory` INTEGER NOT NULL, `documentsMutable` INTEGER NOT NULL, `documentsCanBeDeleted` INTEGER NOT NULL, `documentsTransferable` INTEGER NOT NULL, `requiredFieldsJSON` BLOB, `securityLevel` INTEGER NOT NULL, `tradeMode` INTEGER NOT NULL, `creationRestrictionMode` INTEGER NOT NULL, `requiresIdentityEncryptionBoundedKey` INTEGER NOT NULL, `requiresIdentityDecryptionBoundedKey` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `lastAccessedAt` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`contractId`) REFERENCES `data_contracts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "contractId", + "columnName": "contractId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schemaJSON", + "columnName": "schemaJSON", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "propertiesJSON", + "columnName": "propertiesJSON", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "documentsKeepHistory", + "columnName": "documentsKeepHistory", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "documentsMutable", + "columnName": "documentsMutable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "documentsCanBeDeleted", + "columnName": "documentsCanBeDeleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "documentsTransferable", + "columnName": "documentsTransferable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "requiredFieldsJSON", + "columnName": "requiredFieldsJSON", + "affinity": "BLOB" + }, + { + "fieldPath": "securityLevel", + "columnName": "securityLevel", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tradeMode", + "columnName": "tradeMode", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "creationRestrictionMode", + "columnName": "creationRestrictionMode", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "requiresIdentityEncryptionBoundedKey", + "columnName": "requiresIdentityEncryptionBoundedKey", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "requiresIdentityDecryptionBoundedKey", + "columnName": "requiresIdentityDecryptionBoundedKey", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastAccessedAt", + "columnName": "lastAccessedAt", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_document_types_contractId", + "unique": false, + "columnNames": [ + "contractId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_document_types_contractId` ON `${TABLE_NAME}` (`contractId`)" + } + ], + "foreignKeys": [ + { + "table": "data_contracts", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "contractId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "documents", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`documentId` TEXT NOT NULL, `documentType` TEXT NOT NULL, `revision` INTEGER NOT NULL, `data` BLOB NOT NULL, `contractId` TEXT NOT NULL, `ownerId` TEXT NOT NULL, `contractIdData` BLOB NOT NULL, `ownerIdData` BLOB NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `transferredAt` INTEGER, `createdAtBlockHeight` INTEGER, `updatedAtBlockHeight` INTEGER, `transferredAtBlockHeight` INTEGER, `createdAtCoreBlockHeight` INTEGER, `updatedAtCoreBlockHeight` INTEGER, `transferredAtCoreBlockHeight` INTEGER, `networkRaw` INTEGER NOT NULL, `isDeleted` INTEGER NOT NULL, `localCreatedAt` INTEGER NOT NULL, `localUpdatedAt` INTEGER NOT NULL, `documentTypeRelationId` BLOB, `dataContractId` BLOB, `ownerIdentityId` BLOB, PRIMARY KEY(`documentId`), FOREIGN KEY(`documentTypeRelationId`) REFERENCES `document_types`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`dataContractId`) REFERENCES `data_contracts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`ownerIdentityId`) REFERENCES `identities`(`identityId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "documentId", + "columnName": "documentId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "documentType", + "columnName": "documentType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "revision", + "columnName": "revision", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "contractId", + "columnName": "contractId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ownerId", + "columnName": "ownerId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contractIdData", + "columnName": "contractIdData", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "ownerIdData", + "columnName": "ownerIdData", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updatedAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "transferredAt", + "columnName": "transferredAt", + "affinity": "INTEGER" + }, + { + "fieldPath": "createdAtBlockHeight", + "columnName": "createdAtBlockHeight", + "affinity": "INTEGER" + }, + { + "fieldPath": "updatedAtBlockHeight", + "columnName": "updatedAtBlockHeight", + "affinity": "INTEGER" + }, + { + "fieldPath": "transferredAtBlockHeight", + "columnName": "transferredAtBlockHeight", + "affinity": "INTEGER" + }, + { + "fieldPath": "createdAtCoreBlockHeight", + "columnName": "createdAtCoreBlockHeight", + "affinity": "INTEGER" + }, + { + "fieldPath": "updatedAtCoreBlockHeight", + "columnName": "updatedAtCoreBlockHeight", + "affinity": "INTEGER" + }, + { + "fieldPath": "transferredAtCoreBlockHeight", + "columnName": "transferredAtCoreBlockHeight", + "affinity": "INTEGER" + }, + { + "fieldPath": "networkRaw", + "columnName": "networkRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDeleted", + "columnName": "isDeleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "localCreatedAt", + "columnName": "localCreatedAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "localUpdatedAt", + "columnName": "localUpdatedAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "documentTypeRelationId", + "columnName": "documentTypeRelationId", + "affinity": "BLOB" + }, + { + "fieldPath": "dataContractId", + "columnName": "dataContractId", + "affinity": "BLOB" + }, + { + "fieldPath": "ownerIdentityId", + "columnName": "ownerIdentityId", + "affinity": "BLOB" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "documentId" + ] + }, + "indices": [ + { + "name": "index_documents_networkRaw", + "unique": false, + "columnNames": [ + "networkRaw" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_documents_networkRaw` ON `${TABLE_NAME}` (`networkRaw`)" + }, + { + "name": "index_documents_contractId", + "unique": false, + "columnNames": [ + "contractId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_documents_contractId` ON `${TABLE_NAME}` (`contractId`)" + }, + { + "name": "index_documents_ownerId", + "unique": false, + "columnNames": [ + "ownerId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_documents_ownerId` ON `${TABLE_NAME}` (`ownerId`)" + }, + { + "name": "index_documents_documentTypeRelationId", + "unique": false, + "columnNames": [ + "documentTypeRelationId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_documents_documentTypeRelationId` ON `${TABLE_NAME}` (`documentTypeRelationId`)" + }, + { + "name": "index_documents_dataContractId", + "unique": false, + "columnNames": [ + "dataContractId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_documents_dataContractId` ON `${TABLE_NAME}` (`dataContractId`)" + }, + { + "name": "index_documents_ownerIdentityId", + "unique": false, + "columnNames": [ + "ownerIdentityId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_documents_ownerIdentityId` ON `${TABLE_NAME}` (`ownerIdentityId`)" + } + ], + "foreignKeys": [ + { + "table": "document_types", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "documentTypeRelationId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "data_contracts", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "dataContractId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "identities", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "ownerIdentityId" + ], + "referencedColumns": [ + "identityId" + ] + } + ] + }, + { + "tableName": "indices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` BLOB NOT NULL, `contractId` BLOB NOT NULL, `documentTypeName` TEXT NOT NULL, `name` TEXT NOT NULL, `unique` INTEGER NOT NULL, `nullSearchable` INTEGER NOT NULL, `contested` INTEGER NOT NULL, `propertiesJSON` BLOB NOT NULL, `contestedDetailsJSON` BLOB, `createdAt` INTEGER NOT NULL, `documentTypeId` BLOB, PRIMARY KEY(`id`), FOREIGN KEY(`documentTypeId`) REFERENCES `document_types`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "contractId", + "columnName": "contractId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "documentTypeName", + "columnName": "documentTypeName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "unique", + "columnName": "unique", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nullSearchable", + "columnName": "nullSearchable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "contested", + "columnName": "contested", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "propertiesJSON", + "columnName": "propertiesJSON", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "contestedDetailsJSON", + "columnName": "contestedDetailsJSON", + "affinity": "BLOB" + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "documentTypeId", + "columnName": "documentTypeId", + "affinity": "BLOB" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_indices_documentTypeId", + "unique": false, + "columnNames": [ + "documentTypeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_indices_documentTypeId` ON `${TABLE_NAME}` (`documentTypeId`)" + } + ], + "foreignKeys": [ + { + "table": "document_types", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "documentTypeId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "keywords", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `keyword` TEXT NOT NULL, `contractId` TEXT NOT NULL, `dataContractId` BLOB, PRIMARY KEY(`id`), FOREIGN KEY(`dataContractId`) REFERENCES `data_contracts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "keyword", + "columnName": "keyword", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contractId", + "columnName": "contractId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dataContractId", + "columnName": "dataContractId", + "affinity": "BLOB" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_keywords_contractId", + "unique": false, + "columnNames": [ + "contractId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_keywords_contractId` ON `${TABLE_NAME}` (`contractId`)" + }, + { + "name": "index_keywords_dataContractId", + "unique": false, + "columnNames": [ + "dataContractId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_keywords_dataContractId` ON `${TABLE_NAME}` (`dataContractId`)" + } + ], + "foreignKeys": [ + { + "table": "data_contracts", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "dataContractId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "properties", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` BLOB NOT NULL, `contractId` BLOB NOT NULL, `documentTypeName` TEXT NOT NULL, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `format` TEXT, `contentMediaType` TEXT, `byteArray` INTEGER NOT NULL, `minItems` INTEGER, `maxItems` INTEGER, `pattern` TEXT, `minLength` INTEGER, `maxLength` INTEGER, `minValue` INTEGER, `maxValue` INTEGER, `fieldDescription` TEXT, `transient` INTEGER NOT NULL, `isRequired` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `documentTypeId` BLOB, PRIMARY KEY(`id`), FOREIGN KEY(`documentTypeId`) REFERENCES `document_types`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "contractId", + "columnName": "contractId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "documentTypeName", + "columnName": "documentTypeName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "format", + "columnName": "format", + "affinity": "TEXT" + }, + { + "fieldPath": "contentMediaType", + "columnName": "contentMediaType", + "affinity": "TEXT" + }, + { + "fieldPath": "byteArray", + "columnName": "byteArray", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "minItems", + "columnName": "minItems", + "affinity": "INTEGER" + }, + { + "fieldPath": "maxItems", + "columnName": "maxItems", + "affinity": "INTEGER" + }, + { + "fieldPath": "pattern", + "columnName": "pattern", + "affinity": "TEXT" + }, + { + "fieldPath": "minLength", + "columnName": "minLength", + "affinity": "INTEGER" + }, + { + "fieldPath": "maxLength", + "columnName": "maxLength", + "affinity": "INTEGER" + }, + { + "fieldPath": "minValue", + "columnName": "minValue", + "affinity": "INTEGER" + }, + { + "fieldPath": "maxValue", + "columnName": "maxValue", + "affinity": "INTEGER" + }, + { + "fieldPath": "fieldDescription", + "columnName": "fieldDescription", + "affinity": "TEXT" + }, + { + "fieldPath": "transient", + "columnName": "transient", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRequired", + "columnName": "isRequired", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "documentTypeId", + "columnName": "documentTypeId", + "affinity": "BLOB" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_properties_documentTypeId", + "unique": false, + "columnNames": [ + "documentTypeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_properties_documentTypeId` ON `${TABLE_NAME}` (`documentTypeId`)" + } + ], + "foreignKeys": [ + { + "table": "document_types", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "documentTypeId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "pending_inputs", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `outpoint` BLOB NOT NULL, `inputIndex` INTEGER NOT NULL, `spendingTxid` BLOB NOT NULL, `spendingTransactionTxid` BLOB, `walletId` BLOB NOT NULL, `createdAt` INTEGER NOT NULL, `isSweptTombstone` INTEGER NOT NULL DEFAULT 0, `winnerMinedHeight` INTEGER, FOREIGN KEY(`spendingTransactionTxid`) REFERENCES `transactions`(`txid`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "outpoint", + "columnName": "outpoint", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "inputIndex", + "columnName": "inputIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "spendingTxid", + "columnName": "spendingTxid", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "spendingTransactionTxid", + "columnName": "spendingTransactionTxid", + "affinity": "BLOB" + }, + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isSweptTombstone", + "columnName": "isSweptTombstone", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "winnerMinedHeight", + "columnName": "winnerMinedHeight", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_pending_inputs_outpoint", + "unique": false, + "columnNames": [ + "outpoint" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_pending_inputs_outpoint` ON `${TABLE_NAME}` (`outpoint`)" + }, + { + "name": "index_pending_inputs_walletId", + "unique": false, + "columnNames": [ + "walletId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_pending_inputs_walletId` ON `${TABLE_NAME}` (`walletId`)" + }, + { + "name": "index_pending_inputs_spendingTransactionTxid", + "unique": false, + "columnNames": [ + "spendingTransactionTxid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_pending_inputs_spendingTransactionTxid` ON `${TABLE_NAME}` (`spendingTransactionTxid`)" + }, + { + "name": "index_pending_inputs_spendingTxid", + "unique": false, + "columnNames": [ + "spendingTxid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_pending_inputs_spendingTxid` ON `${TABLE_NAME}` (`spendingTxid`)" + }, + { + "name": "index_pending_inputs_walletId_isSweptTombstone_winnerMinedHeight", + "unique": false, + "columnNames": [ + "walletId", + "isSweptTombstone", + "winnerMinedHeight" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_pending_inputs_walletId_isSweptTombstone_winnerMinedHeight` ON `${TABLE_NAME}` (`walletId`, `isSweptTombstone`, `winnerMinedHeight`)" + } + ], + "foreignKeys": [ + { + "table": "transactions", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "spendingTransactionTxid" + ], + "referencedColumns": [ + "txid" + ] + } + ] + }, + { + "tableName": "tokens", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` BLOB NOT NULL, `contractId` BLOB NOT NULL, `position` INTEGER NOT NULL, `name` TEXT NOT NULL, `baseSupply` TEXT NOT NULL, `maxSupply` TEXT, `decimals` INTEGER NOT NULL, `localizations` TEXT, `isPaused` INTEGER NOT NULL, `allowTransferToFrozenBalance` INTEGER NOT NULL, `keepsTransferHistory` INTEGER NOT NULL, `keepsFreezingHistory` INTEGER NOT NULL, `keepsMintingHistory` INTEGER NOT NULL, `keepsBurningHistory` INTEGER NOT NULL, `keepsDirectPricingHistory` INTEGER NOT NULL, `keepsDirectPurchaseHistory` INTEGER NOT NULL, `conventionsChangeRules` TEXT, `maxSupplyChangeRules` TEXT, `manualMintingRules` TEXT, `manualBurningRules` TEXT, `freezeRules` TEXT, `unfreezeRules` TEXT, `destroyFrozenFundsRules` TEXT, `emergencyActionRules` TEXT, `perpetualDistribution` TEXT, `preProgrammedDistribution` TEXT, `newTokensDestinationIdentity` BLOB, `mintingAllowChoosingDestination` INTEGER NOT NULL, `distributionChangeRules` TEXT, `tradeMode` TEXT NOT NULL, `tradeModeChangeRules` TEXT, `mainControlGroupPosition` INTEGER, `mainControlGroupCanBeModified` TEXT, `tokenDescription` TEXT, `createdAt` INTEGER NOT NULL, `lastUpdatedAt` INTEGER NOT NULL, `canManuallyMint` INTEGER NOT NULL, `canManuallyBurn` INTEGER NOT NULL, `canFreeze` INTEGER NOT NULL, `canUnfreeze` INTEGER NOT NULL, `canDestroyFrozenFunds` INTEGER NOT NULL, `hasEmergencyActions` INTEGER NOT NULL, `canChangeMaxSupply` INTEGER NOT NULL, `canChangeConventions` INTEGER NOT NULL, `canChangeTradeMode` INTEGER NOT NULL, `hasDistribution` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`contractId`) REFERENCES `data_contracts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "contractId", + "columnName": "contractId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "baseSupply", + "columnName": "baseSupply", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "maxSupply", + "columnName": "maxSupply", + "affinity": "TEXT" + }, + { + "fieldPath": "decimals", + "columnName": "decimals", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "localizations", + "columnName": "localizations", + "affinity": "TEXT" + }, + { + "fieldPath": "isPaused", + "columnName": "isPaused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "allowTransferToFrozenBalance", + "columnName": "allowTransferToFrozenBalance", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keepsTransferHistory", + "columnName": "keepsTransferHistory", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keepsFreezingHistory", + "columnName": "keepsFreezingHistory", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keepsMintingHistory", + "columnName": "keepsMintingHistory", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keepsBurningHistory", + "columnName": "keepsBurningHistory", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keepsDirectPricingHistory", + "columnName": "keepsDirectPricingHistory", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keepsDirectPurchaseHistory", + "columnName": "keepsDirectPurchaseHistory", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "conventionsChangeRules", + "columnName": "conventionsChangeRules", + "affinity": "TEXT" + }, + { + "fieldPath": "maxSupplyChangeRules", + "columnName": "maxSupplyChangeRules", + "affinity": "TEXT" + }, + { + "fieldPath": "manualMintingRules", + "columnName": "manualMintingRules", + "affinity": "TEXT" + }, + { + "fieldPath": "manualBurningRules", + "columnName": "manualBurningRules", + "affinity": "TEXT" + }, + { + "fieldPath": "freezeRules", + "columnName": "freezeRules", + "affinity": "TEXT" + }, + { + "fieldPath": "unfreezeRules", + "columnName": "unfreezeRules", + "affinity": "TEXT" + }, + { + "fieldPath": "destroyFrozenFundsRules", + "columnName": "destroyFrozenFundsRules", + "affinity": "TEXT" + }, + { + "fieldPath": "emergencyActionRules", + "columnName": "emergencyActionRules", + "affinity": "TEXT" + }, + { + "fieldPath": "perpetualDistribution", + "columnName": "perpetualDistribution", + "affinity": "TEXT" + }, + { + "fieldPath": "preProgrammedDistribution", + "columnName": "preProgrammedDistribution", + "affinity": "TEXT" + }, + { + "fieldPath": "newTokensDestinationIdentity", + "columnName": "newTokensDestinationIdentity", + "affinity": "BLOB" + }, + { + "fieldPath": "mintingAllowChoosingDestination", + "columnName": "mintingAllowChoosingDestination", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "distributionChangeRules", + "columnName": "distributionChangeRules", + "affinity": "TEXT" + }, + { + "fieldPath": "tradeMode", + "columnName": "tradeMode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "tradeModeChangeRules", + "columnName": "tradeModeChangeRules", + "affinity": "TEXT" + }, + { + "fieldPath": "mainControlGroupPosition", + "columnName": "mainControlGroupPosition", + "affinity": "INTEGER" + }, + { + "fieldPath": "mainControlGroupCanBeModified", + "columnName": "mainControlGroupCanBeModified", + "affinity": "TEXT" + }, + { + "fieldPath": "tokenDescription", + "columnName": "tokenDescription", + "affinity": "TEXT" + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdatedAt", + "columnName": "lastUpdatedAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canManuallyMint", + "columnName": "canManuallyMint", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canManuallyBurn", + "columnName": "canManuallyBurn", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canFreeze", + "columnName": "canFreeze", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canUnfreeze", + "columnName": "canUnfreeze", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canDestroyFrozenFunds", + "columnName": "canDestroyFrozenFunds", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasEmergencyActions", + "columnName": "hasEmergencyActions", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canChangeMaxSupply", + "columnName": "canChangeMaxSupply", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canChangeConventions", + "columnName": "canChangeConventions", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canChangeTradeMode", + "columnName": "canChangeTradeMode", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasDistribution", + "columnName": "hasDistribution", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_tokens_contractId", + "unique": false, + "columnNames": [ + "contractId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_tokens_contractId` ON `${TABLE_NAME}` (`contractId`)" + } + ], + "foreignKeys": [ + { + "table": "data_contracts", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "contractId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "token_balances", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `tokenId` TEXT NOT NULL, `identityId` BLOB NOT NULL, `balance` BLOB NOT NULL, `frozen` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, `lastSyncedAt` INTEGER, `tokenName` TEXT, `tokenSymbol` TEXT, `tokenDecimals` INTEGER, `networkRaw` INTEGER NOT NULL, `identityRef` BLOB, `tokenRef` BLOB, FOREIGN KEY(`identityRef`) REFERENCES `identities`(`identityId`) ON UPDATE NO ACTION ON DELETE SET NULL , FOREIGN KEY(`tokenRef`) REFERENCES `tokens`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tokenId", + "columnName": "tokenId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "identityId", + "columnName": "identityId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "balance", + "columnName": "balance", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "frozen", + "columnName": "frozen", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastSyncedAt", + "columnName": "lastSyncedAt", + "affinity": "INTEGER" + }, + { + "fieldPath": "tokenName", + "columnName": "tokenName", + "affinity": "TEXT" + }, + { + "fieldPath": "tokenSymbol", + "columnName": "tokenSymbol", + "affinity": "TEXT" + }, + { + "fieldPath": "tokenDecimals", + "columnName": "tokenDecimals", + "affinity": "INTEGER" + }, + { + "fieldPath": "networkRaw", + "columnName": "networkRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "identityRef", + "columnName": "identityRef", + "affinity": "BLOB" + }, + { + "fieldPath": "tokenRef", + "columnName": "tokenRef", + "affinity": "BLOB" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_token_balances_networkRaw", + "unique": false, + "columnNames": [ + "networkRaw" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_token_balances_networkRaw` ON `${TABLE_NAME}` (`networkRaw`)" + }, + { + "name": "index_token_balances_tokenId_identityId", + "unique": false, + "columnNames": [ + "tokenId", + "identityId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_token_balances_tokenId_identityId` ON `${TABLE_NAME}` (`tokenId`, `identityId`)" + }, + { + "name": "index_token_balances_identityId", + "unique": false, + "columnNames": [ + "identityId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_token_balances_identityId` ON `${TABLE_NAME}` (`identityId`)" + }, + { + "name": "index_token_balances_identityRef", + "unique": false, + "columnNames": [ + "identityRef" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_token_balances_identityRef` ON `${TABLE_NAME}` (`identityRef`)" + }, + { + "name": "index_token_balances_tokenRef", + "unique": false, + "columnNames": [ + "tokenRef" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_token_balances_tokenRef` ON `${TABLE_NAME}` (`tokenRef`)" + } + ], + "foreignKeys": [ + { + "table": "identities", + "onDelete": "SET NULL", + "onUpdate": "NO ACTION", + "columns": [ + "identityRef" + ], + "referencedColumns": [ + "identityId" + ] + }, + { + "table": "tokens", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "tokenRef" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "token_history_events", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `eventType` TEXT NOT NULL, `transactionId` BLOB, `blockHeight` INTEGER, `coreBlockHeight` INTEGER, `fromIdentity` BLOB, `toIdentity` BLOB, `performedByIdentity` BLOB NOT NULL, `amount` TEXT, `balanceBefore` TEXT, `balanceAfter` TEXT, `additionalDataJSON` BLOB, `eventDescription` TEXT, `createdAt` INTEGER NOT NULL, `eventTimestamp` INTEGER NOT NULL, `tokenRef` BLOB, PRIMARY KEY(`id`), FOREIGN KEY(`tokenRef`) REFERENCES `tokens`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "eventType", + "columnName": "eventType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "transactionId", + "columnName": "transactionId", + "affinity": "BLOB" + }, + { + "fieldPath": "blockHeight", + "columnName": "blockHeight", + "affinity": "INTEGER" + }, + { + "fieldPath": "coreBlockHeight", + "columnName": "coreBlockHeight", + "affinity": "INTEGER" + }, + { + "fieldPath": "fromIdentity", + "columnName": "fromIdentity", + "affinity": "BLOB" + }, + { + "fieldPath": "toIdentity", + "columnName": "toIdentity", + "affinity": "BLOB" + }, + { + "fieldPath": "performedByIdentity", + "columnName": "performedByIdentity", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "TEXT" + }, + { + "fieldPath": "balanceBefore", + "columnName": "balanceBefore", + "affinity": "TEXT" + }, + { + "fieldPath": "balanceAfter", + "columnName": "balanceAfter", + "affinity": "TEXT" + }, + { + "fieldPath": "additionalDataJSON", + "columnName": "additionalDataJSON", + "affinity": "BLOB" + }, + { + "fieldPath": "eventDescription", + "columnName": "eventDescription", + "affinity": "TEXT" + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "eventTimestamp", + "columnName": "eventTimestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tokenRef", + "columnName": "tokenRef", + "affinity": "BLOB" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_token_history_events_tokenRef", + "unique": false, + "columnNames": [ + "tokenRef" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_token_history_events_tokenRef` ON `${TABLE_NAME}` (`tokenRef`)" + } + ], + "foreignKeys": [ + { + "table": "tokens", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "tokenRef" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "platform_addresses", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`address` TEXT NOT NULL, `addressType` INTEGER NOT NULL, `addressHash` BLOB NOT NULL, `publicKey` BLOB NOT NULL, `accountIndex` INTEGER NOT NULL, `addressIndex` INTEGER NOT NULL, `derivationPath` TEXT NOT NULL, `isUsed` INTEGER NOT NULL, `balance` INTEGER NOT NULL, `nonce` INTEGER NOT NULL, `firstSeenHeight` INTEGER NOT NULL, `lastSeenHeight` INTEGER NOT NULL, `walletId` BLOB NOT NULL, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, `accountId` INTEGER, PRIMARY KEY(`walletId`, `address`), FOREIGN KEY(`accountId`) REFERENCES `accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "addressType", + "columnName": "addressType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addressHash", + "columnName": "addressHash", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "publicKey", + "columnName": "publicKey", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "accountIndex", + "columnName": "accountIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addressIndex", + "columnName": "addressIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "derivationPath", + "columnName": "derivationPath", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isUsed", + "columnName": "isUsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "balance", + "columnName": "balance", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nonce", + "columnName": "nonce", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstSeenHeight", + "columnName": "firstSeenHeight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastSeenHeight", + "columnName": "lastSeenHeight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountId", + "columnName": "accountId", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "walletId", + "address" + ] + }, + "indices": [ + { + "name": "index_platform_addresses_walletId_addressHash", + "unique": true, + "columnNames": [ + "walletId", + "addressHash" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_platform_addresses_walletId_addressHash` ON `${TABLE_NAME}` (`walletId`, `addressHash`)" + }, + { + "name": "index_platform_addresses_accountId", + "unique": false, + "columnNames": [ + "accountId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_platform_addresses_accountId` ON `${TABLE_NAME}` (`accountId`)" + } + ], + "foreignKeys": [ + { + "table": "accounts", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "accountId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "platform_addresses_sync_states", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`walletId` BLOB NOT NULL, `networkRaw` INTEGER NOT NULL, `syncHeight` INTEGER NOT NULL, `syncTimestamp` INTEGER NOT NULL, `lastKnownRecentBlock` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`walletId`))", + "fields": [ + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "networkRaw", + "columnName": "networkRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "syncHeight", + "columnName": "syncHeight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "syncTimestamp", + "columnName": "syncTimestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastKnownRecentBlock", + "columnName": "lastKnownRecentBlock", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "walletId" + ] + }, + "indices": [ + { + "name": "index_platform_addresses_sync_states_networkRaw", + "unique": false, + "columnNames": [ + "networkRaw" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_platform_addresses_sync_states_networkRaw` ON `${TABLE_NAME}` (`networkRaw`)" + } + ] + }, + { + "tableName": "shielded_notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`nullifier` BLOB NOT NULL, `walletId` BLOB NOT NULL, `accountIndex` INTEGER NOT NULL, `position` INTEGER NOT NULL, `cmx` BLOB NOT NULL, `blockHeight` INTEGER NOT NULL, `isSpent` INTEGER NOT NULL, `value` INTEGER NOT NULL, `noteData` BLOB NOT NULL, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`nullifier`))", + "fields": [ + { + "fieldPath": "nullifier", + "columnName": "nullifier", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "accountIndex", + "columnName": "accountIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "cmx", + "columnName": "cmx", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "blockHeight", + "columnName": "blockHeight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isSpent", + "columnName": "isSpent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "noteData", + "columnName": "noteData", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "nullifier" + ] + }, + "indices": [ + { + "name": "index_shielded_notes_walletId_accountIndex", + "unique": false, + "columnNames": [ + "walletId", + "accountIndex" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_shielded_notes_walletId_accountIndex` ON `${TABLE_NAME}` (`walletId`, `accountIndex`)" + } + ] + }, + { + "tableName": "shielded_outgoing_notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`walletId` BLOB NOT NULL, `accountIndex` INTEGER NOT NULL, `cmx` BLOB NOT NULL, `recipient` BLOB NOT NULL, `value` INTEGER NOT NULL, `memo` BLOB NOT NULL, `blockHeight` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`walletId`, `accountIndex`, `cmx`))", + "fields": [ + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "accountIndex", + "columnName": "accountIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "cmx", + "columnName": "cmx", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "memo", + "columnName": "memo", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "blockHeight", + "columnName": "blockHeight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "walletId", + "accountIndex", + "cmx" + ] + }, + "indices": [ + { + "name": "index_shielded_outgoing_notes_walletId_accountIndex", + "unique": false, + "columnNames": [ + "walletId", + "accountIndex" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_shielded_outgoing_notes_walletId_accountIndex` ON `${TABLE_NAME}` (`walletId`, `accountIndex`)" + } + ] + }, + { + "tableName": "shielded_activities", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`walletId` BLOB NOT NULL, `accountIndex` INTEGER NOT NULL, `entryId` BLOB NOT NULL, `kindTag` INTEGER NOT NULL, `direction` INTEGER NOT NULL, `status` INTEGER NOT NULL, `amount` INTEGER NOT NULL, `fee` INTEGER NOT NULL, `hasFee` INTEGER NOT NULL, `blockHeight` INTEGER NOT NULL, `hasBlockHeight` INTEGER NOT NULL, `createdAtMs` INTEGER NOT NULL, `identityId` BLOB NOT NULL, `counterparty` BLOB NOT NULL, `memo` BLOB NOT NULL, `noteCmxs` BLOB NOT NULL, `spentNullifiers` BLOB NOT NULL, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`walletId`, `accountIndex`, `entryId`))", + "fields": [ + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "accountIndex", + "columnName": "accountIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryId", + "columnName": "entryId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "kindTag", + "columnName": "kindTag", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "direction", + "columnName": "direction", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fee", + "columnName": "fee", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasFee", + "columnName": "hasFee", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "blockHeight", + "columnName": "blockHeight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasBlockHeight", + "columnName": "hasBlockHeight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAtMs", + "columnName": "createdAtMs", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "identityId", + "columnName": "identityId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "counterparty", + "columnName": "counterparty", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "memo", + "columnName": "memo", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "noteCmxs", + "columnName": "noteCmxs", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "spentNullifiers", + "columnName": "spentNullifiers", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "walletId", + "accountIndex", + "entryId" + ] + }, + "indices": [ + { + "name": "index_shielded_activities_walletId_accountIndex", + "unique": false, + "columnNames": [ + "walletId", + "accountIndex" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_shielded_activities_walletId_accountIndex` ON `${TABLE_NAME}` (`walletId`, `accountIndex`)" + } + ] + }, + { + "tableName": "shielded_sync_states", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`walletId` BLOB NOT NULL, `accountIndex` INTEGER NOT NULL, `lastSyncedIndex` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`walletId`, `accountIndex`))", + "fields": [ + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "accountIndex", + "columnName": "accountIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastSyncedIndex", + "columnName": "lastSyncedIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "walletId", + "accountIndex" + ] + }, + "indices": [ + { + "name": "index_shielded_sync_states_walletId", + "unique": false, + "columnNames": [ + "walletId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_shielded_sync_states_walletId` ON `${TABLE_NAME}` (`walletId`)" + } + ] + }, + { + "tableName": "shielded_viewing_keys", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`walletId` BLOB NOT NULL, `accountIndex` INTEGER NOT NULL, `fvkBytes` BLOB NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`walletId`, `accountIndex`))", + "fields": [ + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "accountIndex", + "columnName": "accountIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fvkBytes", + "columnName": "fvkBytes", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "walletId", + "accountIndex" + ] + }, + "indices": [ + { + "name": "index_shielded_viewing_keys_walletId", + "unique": false, + "columnNames": [ + "walletId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_shielded_viewing_keys_walletId` ON `${TABLE_NAME}` (`walletId`)" + } + ] + }, + { + "tableName": "wallet_manager_metadata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`networkRaw` INTEGER NOT NULL, `combinedSyncHeight` INTEGER NOT NULL, `combinedSyncBlockHash` BLOB, `walletCount` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`networkRaw`))", + "fields": [ + { + "fieldPath": "networkRaw", + "columnName": "networkRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "combinedSyncHeight", + "columnName": "combinedSyncHeight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "combinedSyncBlockHash", + "columnName": "combinedSyncBlockHash", + "affinity": "BLOB" + }, + { + "fieldPath": "walletCount", + "columnName": "walletCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "networkRaw" + ] + } + } + ], + "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, '0ef5dd81ffff7648267f76db90ccfc11')" + ] + } +} \ No newline at end of file diff --git a/packages/kotlin-sdk/sdk/schemas/org.dashfoundation.dashsdk.persistence.DashDatabase/13.json b/packages/kotlin-sdk/sdk/schemas/org.dashfoundation.dashsdk.persistence.DashDatabase/13.json new file mode 100644 index 00000000000..2a6e6031a47 --- /dev/null +++ b/packages/kotlin-sdk/sdk/schemas/org.dashfoundation.dashsdk.persistence.DashDatabase/13.json @@ -0,0 +1,4177 @@ +{ + "formatVersion": 1, + "database": { + "version": 13, + "identityHash": "504cdea5b956240386df04640d2e82bf", + "entities": [ + { + "tableName": "wallets", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`walletId` BLOB NOT NULL, `walletGroupId` BLOB NOT NULL, `networkRaw` INTEGER, `name` TEXT, `walletDescription` TEXT, `birthHeight` INTEGER NOT NULL, `syncedHeight` INTEGER NOT NULL, `lastSynced` INTEGER NOT NULL, `lastAppliedChainLockBytes` BLOB, `lastAppliedChainLockHeight` INTEGER, `isImported` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`walletId`))", + "fields": [ + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "walletGroupId", + "columnName": "walletGroupId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "networkRaw", + "columnName": "networkRaw", + "affinity": "INTEGER" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT" + }, + { + "fieldPath": "walletDescription", + "columnName": "walletDescription", + "affinity": "TEXT" + }, + { + "fieldPath": "birthHeight", + "columnName": "birthHeight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "syncedHeight", + "columnName": "syncedHeight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastSynced", + "columnName": "lastSynced", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastAppliedChainLockBytes", + "columnName": "lastAppliedChainLockBytes", + "affinity": "BLOB" + }, + { + "fieldPath": "lastAppliedChainLockHeight", + "columnName": "lastAppliedChainLockHeight", + "affinity": "INTEGER" + }, + { + "fieldPath": "isImported", + "columnName": "isImported", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "walletId" + ] + }, + "indices": [ + { + "name": "index_wallets_networkRaw", + "unique": false, + "columnNames": [ + "networkRaw" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_wallets_networkRaw` ON `${TABLE_NAME}` (`networkRaw`)" + }, + { + "name": "index_wallets_walletGroupId", + "unique": false, + "columnNames": [ + "walletGroupId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_wallets_walletGroupId` ON `${TABLE_NAME}` (`walletGroupId`)" + } + ] + }, + { + "tableName": "accounts", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `walletId` BLOB NOT NULL, `accountType` INTEGER NOT NULL, `accountIndex` INTEGER NOT NULL, `accountTypeName` TEXT NOT NULL, `balanceConfirmed` INTEGER NOT NULL, `balanceUnconfirmed` INTEGER NOT NULL, `externalHighestUsed` INTEGER NOT NULL, `internalHighestUsed` INTEGER NOT NULL, `standardTag` INTEGER NOT NULL, `registrationIndex` INTEGER NOT NULL, `keyClass` INTEGER NOT NULL, `userIdentityId` BLOB NOT NULL, `friendIdentityId` BLOB NOT NULL, `accountExtendedPubKeyBytes` BLOB, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, FOREIGN KEY(`walletId`) REFERENCES `wallets`(`walletId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "accountType", + "columnName": "accountType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountIndex", + "columnName": "accountIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountTypeName", + "columnName": "accountTypeName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "balanceConfirmed", + "columnName": "balanceConfirmed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "balanceUnconfirmed", + "columnName": "balanceUnconfirmed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "externalHighestUsed", + "columnName": "externalHighestUsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "internalHighestUsed", + "columnName": "internalHighestUsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "standardTag", + "columnName": "standardTag", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationIndex", + "columnName": "registrationIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keyClass", + "columnName": "keyClass", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userIdentityId", + "columnName": "userIdentityId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "friendIdentityId", + "columnName": "friendIdentityId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "accountExtendedPubKeyBytes", + "columnName": "accountExtendedPubKeyBytes", + "affinity": "BLOB" + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_accounts_walletId", + "unique": false, + "columnNames": [ + "walletId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_accounts_walletId` ON `${TABLE_NAME}` (`walletId`)" + }, + { + "name": "index_accounts_walletId_accountType_accountIndex_standardTag_registrationIndex_keyClass_userIdentityId_friendIdentityId", + "unique": true, + "columnNames": [ + "walletId", + "accountType", + "accountIndex", + "standardTag", + "registrationIndex", + "keyClass", + "userIdentityId", + "friendIdentityId" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_accounts_walletId_accountType_accountIndex_standardTag_registrationIndex_keyClass_userIdentityId_friendIdentityId` ON `${TABLE_NAME}` (`walletId`, `accountType`, `accountIndex`, `standardTag`, `registrationIndex`, `keyClass`, `userIdentityId`, `friendIdentityId`)" + }, + { + "name": "index_accounts_accountExtendedPubKeyBytes", + "unique": true, + "columnNames": [ + "accountExtendedPubKeyBytes" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_accounts_accountExtendedPubKeyBytes` ON `${TABLE_NAME}` (`accountExtendedPubKeyBytes`)" + } + ], + "foreignKeys": [ + { + "table": "wallets", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "walletId" + ], + "referencedColumns": [ + "walletId" + ] + } + ] + }, + { + "tableName": "transactions", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`txid` BLOB NOT NULL, `transactionData` BLOB NOT NULL, `context` INTEGER NOT NULL, `blockHeight` INTEGER NOT NULL, `blockHash` BLOB, `blockTimestamp` INTEGER NOT NULL, `blockPosition` INTEGER NOT NULL, `hasBlockPosition` INTEGER NOT NULL, `direction` INTEGER NOT NULL, `transactionType` TEXT NOT NULL, `transactionTypeKind` INTEGER NOT NULL, `netAmount` INTEGER NOT NULL, `fee` INTEGER, `label` TEXT NOT NULL, `firstSeen` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`txid`))", + "fields": [ + { + "fieldPath": "txid", + "columnName": "txid", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "transactionData", + "columnName": "transactionData", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "context", + "columnName": "context", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "blockHeight", + "columnName": "blockHeight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "blockHash", + "columnName": "blockHash", + "affinity": "BLOB" + }, + { + "fieldPath": "blockTimestamp", + "columnName": "blockTimestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "blockPosition", + "columnName": "blockPosition", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasBlockPosition", + "columnName": "hasBlockPosition", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "direction", + "columnName": "direction", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "transactionType", + "columnName": "transactionType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "transactionTypeKind", + "columnName": "transactionTypeKind", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "netAmount", + "columnName": "netAmount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fee", + "columnName": "fee", + "affinity": "INTEGER" + }, + { + "fieldPath": "label", + "columnName": "label", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstSeen", + "columnName": "firstSeen", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "txid" + ] + }, + "indices": [ + { + "name": "index_transactions_firstSeen", + "unique": false, + "columnNames": [ + "firstSeen" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_transactions_firstSeen` ON `${TABLE_NAME}` (`firstSeen`)" + } + ] + }, + { + "tableName": "transaction_account_involvements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`transactionTxid` BLOB NOT NULL, `accountId` INTEGER NOT NULL, PRIMARY KEY(`transactionTxid`, `accountId`), FOREIGN KEY(`transactionTxid`) REFERENCES `transactions`(`txid`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`accountId`) REFERENCES `accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "transactionTxid", + "columnName": "transactionTxid", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "accountId", + "columnName": "accountId", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "transactionTxid", + "accountId" + ] + }, + "indices": [ + { + "name": "index_transaction_account_involvements_accountId", + "unique": false, + "columnNames": [ + "accountId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_transaction_account_involvements_accountId` ON `${TABLE_NAME}` (`accountId`)" + } + ], + "foreignKeys": [ + { + "table": "transactions", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "transactionTxid" + ], + "referencedColumns": [ + "txid" + ] + }, + { + "table": "accounts", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "accountId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "txos", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`outpoint` BLOB NOT NULL, `vout` INTEGER NOT NULL, `amount` INTEGER NOT NULL, `address` TEXT NOT NULL, `scriptPubKey` BLOB NOT NULL, `height` INTEGER NOT NULL, `isCoinbase` INTEGER NOT NULL, `isConfirmed` INTEGER NOT NULL, `isInstantLocked` INTEGER NOT NULL, `isLocked` INTEGER NOT NULL, `isSpent` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, `walletId` BLOB NOT NULL, `txid` BLOB, `spendingTxid` BLOB, `spendingInputIndex` INTEGER, `accountId` INTEGER, `coreAddressId` TEXT, `supersededByTxid` BLOB, PRIMARY KEY(`outpoint`), FOREIGN KEY(`txid`) REFERENCES `transactions`(`txid`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`spendingTxid`) REFERENCES `transactions`(`txid`) ON UPDATE NO ACTION ON DELETE SET NULL , FOREIGN KEY(`accountId`) REFERENCES `accounts`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL , FOREIGN KEY(`coreAddressId`) REFERENCES `core_addresses`(`address`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "outpoint", + "columnName": "outpoint", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "vout", + "columnName": "vout", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "scriptPubKey", + "columnName": "scriptPubKey", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "height", + "columnName": "height", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCoinbase", + "columnName": "isCoinbase", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isConfirmed", + "columnName": "isConfirmed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isInstantLocked", + "columnName": "isInstantLocked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isLocked", + "columnName": "isLocked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isSpent", + "columnName": "isSpent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "txid", + "columnName": "txid", + "affinity": "BLOB" + }, + { + "fieldPath": "spendingTxid", + "columnName": "spendingTxid", + "affinity": "BLOB" + }, + { + "fieldPath": "spendingInputIndex", + "columnName": "spendingInputIndex", + "affinity": "INTEGER" + }, + { + "fieldPath": "accountId", + "columnName": "accountId", + "affinity": "INTEGER" + }, + { + "fieldPath": "coreAddressId", + "columnName": "coreAddressId", + "affinity": "TEXT" + }, + { + "fieldPath": "supersededByTxid", + "columnName": "supersededByTxid", + "affinity": "BLOB" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "outpoint" + ] + }, + "indices": [ + { + "name": "index_txos_walletId", + "unique": false, + "columnNames": [ + "walletId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_txos_walletId` ON `${TABLE_NAME}` (`walletId`)" + }, + { + "name": "index_txos_txid", + "unique": false, + "columnNames": [ + "txid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_txos_txid` ON `${TABLE_NAME}` (`txid`)" + }, + { + "name": "index_txos_spendingTxid", + "unique": false, + "columnNames": [ + "spendingTxid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_txos_spendingTxid` ON `${TABLE_NAME}` (`spendingTxid`)" + }, + { + "name": "index_txos_accountId", + "unique": false, + "columnNames": [ + "accountId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_txos_accountId` ON `${TABLE_NAME}` (`accountId`)" + }, + { + "name": "index_txos_coreAddressId", + "unique": false, + "columnNames": [ + "coreAddressId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_txos_coreAddressId` ON `${TABLE_NAME}` (`coreAddressId`)" + } + ], + "foreignKeys": [ + { + "table": "transactions", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "txid" + ], + "referencedColumns": [ + "txid" + ] + }, + { + "table": "transactions", + "onDelete": "SET NULL", + "onUpdate": "NO ACTION", + "columns": [ + "spendingTxid" + ], + "referencedColumns": [ + "txid" + ] + }, + { + "table": "accounts", + "onDelete": "SET NULL", + "onUpdate": "NO ACTION", + "columns": [ + "accountId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "core_addresses", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "coreAddressId" + ], + "referencedColumns": [ + "address" + ] + } + ] + }, + { + "tableName": "core_addresses", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`address` TEXT NOT NULL, `publicKey` BLOB NOT NULL, `poolTypeTag` INTEGER NOT NULL, `addressIndex` INTEGER NOT NULL, `derivationPath` TEXT NOT NULL, `isUsed` INTEGER NOT NULL, `firstSeenHeight` INTEGER NOT NULL, `lastSeenHeight` INTEGER NOT NULL, `balance` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, `accountId` INTEGER, PRIMARY KEY(`address`), FOREIGN KEY(`accountId`) REFERENCES `accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "publicKey", + "columnName": "publicKey", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "poolTypeTag", + "columnName": "poolTypeTag", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addressIndex", + "columnName": "addressIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "derivationPath", + "columnName": "derivationPath", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isUsed", + "columnName": "isUsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstSeenHeight", + "columnName": "firstSeenHeight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastSeenHeight", + "columnName": "lastSeenHeight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "balance", + "columnName": "balance", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountId", + "columnName": "accountId", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "address" + ] + }, + "indices": [ + { + "name": "index_core_addresses_accountId", + "unique": false, + "columnNames": [ + "accountId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_core_addresses_accountId` ON `${TABLE_NAME}` (`accountId`)" + } + ], + "foreignKeys": [ + { + "table": "accounts", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "accountId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "asset_locks", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`outPointHex` TEXT NOT NULL, `walletId` BLOB NOT NULL, `transactionBytes` BLOB NOT NULL, `fundingTypeRaw` INTEGER NOT NULL, `identityIndexRaw` INTEGER NOT NULL, `accountIndexRaw` INTEGER NOT NULL, `amountDuffs` INTEGER NOT NULL, `statusRaw` INTEGER NOT NULL, `proofBytes` BLOB, `recipientPlatformAddressHash` BLOB, `recipientPlatformAddressType` INTEGER, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, PRIMARY KEY(`outPointHex`))", + "fields": [ + { + "fieldPath": "outPointHex", + "columnName": "outPointHex", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "transactionBytes", + "columnName": "transactionBytes", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "fundingTypeRaw", + "columnName": "fundingTypeRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "identityIndexRaw", + "columnName": "identityIndexRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountIndexRaw", + "columnName": "accountIndexRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "amountDuffs", + "columnName": "amountDuffs", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "statusRaw", + "columnName": "statusRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "proofBytes", + "columnName": "proofBytes", + "affinity": "BLOB" + }, + { + "fieldPath": "recipientPlatformAddressHash", + "columnName": "recipientPlatformAddressHash", + "affinity": "BLOB" + }, + { + "fieldPath": "recipientPlatformAddressType", + "columnName": "recipientPlatformAddressType", + "affinity": "INTEGER" + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updatedAt", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "outPointHex" + ] + }, + "indices": [ + { + "name": "index_asset_locks_walletId", + "unique": false, + "columnNames": [ + "walletId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_asset_locks_walletId` ON `${TABLE_NAME}` (`walletId`)" + } + ] + }, + { + "tableName": "invitations", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`outPointHex` TEXT NOT NULL, `rawOutPoint` BLOB NOT NULL, `walletId` BLOB NOT NULL, `fundingIndexRaw` INTEGER NOT NULL, `amountDuffs` INTEGER NOT NULL, `expiryUnix` INTEGER NOT NULL, `createdAtSecs` INTEGER NOT NULL, `hasInviter` INTEGER NOT NULL, `statusRaw` INTEGER NOT NULL, `reclaimInFlight` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, PRIMARY KEY(`outPointHex`))", + "fields": [ + { + "fieldPath": "outPointHex", + "columnName": "outPointHex", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "rawOutPoint", + "columnName": "rawOutPoint", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "fundingIndexRaw", + "columnName": "fundingIndexRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "amountDuffs", + "columnName": "amountDuffs", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "expiryUnix", + "columnName": "expiryUnix", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAtSecs", + "columnName": "createdAtSecs", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasInviter", + "columnName": "hasInviter", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "statusRaw", + "columnName": "statusRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "reclaimInFlight", + "columnName": "reclaimInFlight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updatedAt", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "outPointHex" + ] + }, + "indices": [ + { + "name": "index_invitations_walletId", + "unique": false, + "columnNames": [ + "walletId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_invitations_walletId` ON `${TABLE_NAME}` (`walletId`)" + } + ] + }, + { + "tableName": "identities", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`identityId` BLOB NOT NULL, `balance` INTEGER NOT NULL, `revision` INTEGER NOT NULL, `isLocal` INTEGER NOT NULL, `alias` TEXT, `dpnsName` TEXT, `mainDpnsName` TEXT, `identityType` TEXT NOT NULL, `votingPrivateKeyIdentifier` TEXT, `ownerPrivateKeyIdentifier` TEXT, `payoutPrivateKeyIdentifier` TEXT, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, `lastSyncedAt` INTEGER, `networkRaw` INTEGER NOT NULL, `walletId` BLOB, `identityIndex` INTEGER NOT NULL, PRIMARY KEY(`identityId`), FOREIGN KEY(`walletId`) REFERENCES `wallets`(`walletId`) ON UPDATE NO ACTION ON DELETE SET NULL )", + "fields": [ + { + "fieldPath": "identityId", + "columnName": "identityId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "balance", + "columnName": "balance", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "revision", + "columnName": "revision", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isLocal", + "columnName": "isLocal", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "alias", + "columnName": "alias", + "affinity": "TEXT" + }, + { + "fieldPath": "dpnsName", + "columnName": "dpnsName", + "affinity": "TEXT" + }, + { + "fieldPath": "mainDpnsName", + "columnName": "mainDpnsName", + "affinity": "TEXT" + }, + { + "fieldPath": "identityType", + "columnName": "identityType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "votingPrivateKeyIdentifier", + "columnName": "votingPrivateKeyIdentifier", + "affinity": "TEXT" + }, + { + "fieldPath": "ownerPrivateKeyIdentifier", + "columnName": "ownerPrivateKeyIdentifier", + "affinity": "TEXT" + }, + { + "fieldPath": "payoutPrivateKeyIdentifier", + "columnName": "payoutPrivateKeyIdentifier", + "affinity": "TEXT" + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastSyncedAt", + "columnName": "lastSyncedAt", + "affinity": "INTEGER" + }, + { + "fieldPath": "networkRaw", + "columnName": "networkRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "BLOB" + }, + { + "fieldPath": "identityIndex", + "columnName": "identityIndex", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "identityId" + ] + }, + "indices": [ + { + "name": "index_identities_networkRaw", + "unique": false, + "columnNames": [ + "networkRaw" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_identities_networkRaw` ON `${TABLE_NAME}` (`networkRaw`)" + }, + { + "name": "index_identities_walletId", + "unique": false, + "columnNames": [ + "walletId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_identities_walletId` ON `${TABLE_NAME}` (`walletId`)" + } + ], + "foreignKeys": [ + { + "table": "wallets", + "onDelete": "SET NULL", + "onUpdate": "NO ACTION", + "columns": [ + "walletId" + ], + "referencedColumns": [ + "walletId" + ] + } + ] + }, + { + "tableName": "public_keys", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `keyId` INTEGER NOT NULL, `purpose` TEXT NOT NULL, `securityLevel` TEXT NOT NULL, `keyType` TEXT NOT NULL, `readOnly` INTEGER NOT NULL, `disabledAt` INTEGER, `totalBudget` INTEGER, `expiresAt` INTEGER, `publicKeyData` BLOB NOT NULL, `contractBoundsData` BLOB, `contractBoundsDocumentTypeName` TEXT, `contractBoundsKind` INTEGER, `privateKeyKeychainIdentifier` TEXT, `derivationIdentityIndex` INTEGER, `derivationKeyIndex` INTEGER, `identityId` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `lastAccessed` INTEGER, `identityIdData` BLOB, FOREIGN KEY(`identityIdData`) REFERENCES `identities`(`identityId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keyId", + "columnName": "keyId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "purpose", + "columnName": "purpose", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "securityLevel", + "columnName": "securityLevel", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "keyType", + "columnName": "keyType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "readOnly", + "columnName": "readOnly", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "disabledAt", + "columnName": "disabledAt", + "affinity": "INTEGER" + }, + { + "fieldPath": "totalBudget", + "columnName": "totalBudget", + "affinity": "INTEGER" + }, + { + "fieldPath": "expiresAt", + "columnName": "expiresAt", + "affinity": "INTEGER" + }, + { + "fieldPath": "publicKeyData", + "columnName": "publicKeyData", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "contractBoundsData", + "columnName": "contractBoundsData", + "affinity": "BLOB" + }, + { + "fieldPath": "contractBoundsDocumentTypeName", + "columnName": "contractBoundsDocumentTypeName", + "affinity": "TEXT" + }, + { + "fieldPath": "contractBoundsKind", + "columnName": "contractBoundsKind", + "affinity": "INTEGER" + }, + { + "fieldPath": "privateKeyKeychainIdentifier", + "columnName": "privateKeyKeychainIdentifier", + "affinity": "TEXT" + }, + { + "fieldPath": "derivationIdentityIndex", + "columnName": "derivationIdentityIndex", + "affinity": "INTEGER" + }, + { + "fieldPath": "derivationKeyIndex", + "columnName": "derivationKeyIndex", + "affinity": "INTEGER" + }, + { + "fieldPath": "identityId", + "columnName": "identityId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastAccessed", + "columnName": "lastAccessed", + "affinity": "INTEGER" + }, + { + "fieldPath": "identityIdData", + "columnName": "identityIdData", + "affinity": "BLOB" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_public_keys_identityId_keyId", + "unique": false, + "columnNames": [ + "identityId", + "keyId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_public_keys_identityId_keyId` ON `${TABLE_NAME}` (`identityId`, `keyId`)" + }, + { + "name": "index_public_keys_identityIdData", + "unique": false, + "columnNames": [ + "identityIdData" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_public_keys_identityIdData` ON `${TABLE_NAME}` (`identityIdData`)" + }, + { + "name": "index_public_keys_publicKeyData", + "unique": false, + "columnNames": [ + "publicKeyData" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_public_keys_publicKeyData` ON `${TABLE_NAME}` (`publicKeyData`)" + } + ], + "foreignKeys": [ + { + "table": "identities", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "identityIdData" + ], + "referencedColumns": [ + "identityId" + ] + } + ] + }, + { + "tableName": "dpns_names", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`networkRaw` INTEGER NOT NULL, `label` TEXT NOT NULL, `normalizedLabel` TEXT NOT NULL, `parentDomainName` TEXT NOT NULL, `normalizedParentDomainName` TEXT NOT NULL, `acquiredAt` INTEGER NOT NULL, `identityId` BLOB NOT NULL, `documentId` BLOB, `isOwned` INTEGER NOT NULL, `priceCredits` INTEGER, `saleStatusRaw` INTEGER NOT NULL, `counterpartyIdentityId` BLOB, `documentCreatedAtMs` INTEGER NOT NULL, `documentUpdatedAtMs` INTEGER NOT NULL, `documentTransferredAtMs` INTEGER NOT NULL, `marketplaceUpdatedAt` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`networkRaw`, `normalizedParentDomainName`, `normalizedLabel`), FOREIGN KEY(`identityId`) REFERENCES `identities`(`identityId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "networkRaw", + "columnName": "networkRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "label", + "columnName": "label", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "normalizedLabel", + "columnName": "normalizedLabel", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentDomainName", + "columnName": "parentDomainName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "normalizedParentDomainName", + "columnName": "normalizedParentDomainName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "acquiredAt", + "columnName": "acquiredAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "identityId", + "columnName": "identityId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "documentId", + "columnName": "documentId", + "affinity": "BLOB" + }, + { + "fieldPath": "isOwned", + "columnName": "isOwned", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "priceCredits", + "columnName": "priceCredits", + "affinity": "INTEGER" + }, + { + "fieldPath": "saleStatusRaw", + "columnName": "saleStatusRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "counterpartyIdentityId", + "columnName": "counterpartyIdentityId", + "affinity": "BLOB" + }, + { + "fieldPath": "documentCreatedAtMs", + "columnName": "documentCreatedAtMs", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "documentUpdatedAtMs", + "columnName": "documentUpdatedAtMs", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "documentTransferredAtMs", + "columnName": "documentTransferredAtMs", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "marketplaceUpdatedAt", + "columnName": "marketplaceUpdatedAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "networkRaw", + "normalizedParentDomainName", + "normalizedLabel" + ] + }, + "indices": [ + { + "name": "index_dpns_names_identityId", + "unique": false, + "columnNames": [ + "identityId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_dpns_names_identityId` ON `${TABLE_NAME}` (`identityId`)" + }, + { + "name": "index_dpns_names_documentId", + "unique": false, + "columnNames": [ + "documentId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_dpns_names_documentId` ON `${TABLE_NAME}` (`documentId`)" + } + ], + "foreignKeys": [ + { + "table": "identities", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "identityId" + ], + "referencedColumns": [ + "identityId" + ] + } + ] + }, + { + "tableName": "dashpay_profiles", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`networkRaw` INTEGER NOT NULL, `identityId` BLOB NOT NULL, `displayName` TEXT, `publicMessage` TEXT, `bio` TEXT, `avatarUrl` TEXT, `avatarHash` BLOB, `avatarFingerprint` BLOB, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`networkRaw`, `identityId`), FOREIGN KEY(`identityId`) REFERENCES `identities`(`identityId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "networkRaw", + "columnName": "networkRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "identityId", + "columnName": "identityId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "displayName", + "columnName": "displayName", + "affinity": "TEXT" + }, + { + "fieldPath": "publicMessage", + "columnName": "publicMessage", + "affinity": "TEXT" + }, + { + "fieldPath": "bio", + "columnName": "bio", + "affinity": "TEXT" + }, + { + "fieldPath": "avatarUrl", + "columnName": "avatarUrl", + "affinity": "TEXT" + }, + { + "fieldPath": "avatarHash", + "columnName": "avatarHash", + "affinity": "BLOB" + }, + { + "fieldPath": "avatarFingerprint", + "columnName": "avatarFingerprint", + "affinity": "BLOB" + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "networkRaw", + "identityId" + ] + }, + "indices": [ + { + "name": "index_dashpay_profiles_identityId", + "unique": false, + "columnNames": [ + "identityId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_dashpay_profiles_identityId` ON `${TABLE_NAME}` (`identityId`)" + } + ], + "foreignKeys": [ + { + "table": "identities", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "identityId" + ], + "referencedColumns": [ + "identityId" + ] + } + ] + }, + { + "tableName": "dashpay_contact_requests", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`networkRaw` INTEGER NOT NULL, `ownerIdentityId` BLOB NOT NULL, `contactIdentityId` BLOB NOT NULL, `isOutgoing` INTEGER NOT NULL, `senderKeyIndex` INTEGER NOT NULL, `recipientKeyIndex` INTEGER NOT NULL, `accountReference` INTEGER NOT NULL, `encryptedPublicKey` BLOB NOT NULL, `encryptedAccountLabel` BLOB, `autoAcceptProof` BLOB, `coreHeightCreatedAt` INTEGER NOT NULL, `createdAtMillis` INTEGER NOT NULL, `paymentChannelBroken` INTEGER NOT NULL DEFAULT 0, `contactAlias` TEXT, `contactNote` TEXT, `contactHidden` INTEGER NOT NULL DEFAULT 0, `contactAccountLabel` TEXT, `contactAcceptedAccounts` BLOB, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`networkRaw`, `ownerIdentityId`, `contactIdentityId`, `isOutgoing`), FOREIGN KEY(`ownerIdentityId`) REFERENCES `identities`(`identityId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "networkRaw", + "columnName": "networkRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ownerIdentityId", + "columnName": "ownerIdentityId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "contactIdentityId", + "columnName": "contactIdentityId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "isOutgoing", + "columnName": "isOutgoing", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderKeyIndex", + "columnName": "senderKeyIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipientKeyIndex", + "columnName": "recipientKeyIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountReference", + "columnName": "accountReference", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "encryptedPublicKey", + "columnName": "encryptedPublicKey", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "encryptedAccountLabel", + "columnName": "encryptedAccountLabel", + "affinity": "BLOB" + }, + { + "fieldPath": "autoAcceptProof", + "columnName": "autoAcceptProof", + "affinity": "BLOB" + }, + { + "fieldPath": "coreHeightCreatedAt", + "columnName": "coreHeightCreatedAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAtMillis", + "columnName": "createdAtMillis", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "paymentChannelBroken", + "columnName": "paymentChannelBroken", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "contactAlias", + "columnName": "contactAlias", + "affinity": "TEXT" + }, + { + "fieldPath": "contactNote", + "columnName": "contactNote", + "affinity": "TEXT" + }, + { + "fieldPath": "contactHidden", + "columnName": "contactHidden", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "contactAccountLabel", + "columnName": "contactAccountLabel", + "affinity": "TEXT" + }, + { + "fieldPath": "contactAcceptedAccounts", + "columnName": "contactAcceptedAccounts", + "affinity": "BLOB" + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "networkRaw", + "ownerIdentityId", + "contactIdentityId", + "isOutgoing" + ] + }, + "indices": [ + { + "name": "index_dashpay_contact_requests_ownerIdentityId", + "unique": false, + "columnNames": [ + "ownerIdentityId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_dashpay_contact_requests_ownerIdentityId` ON `${TABLE_NAME}` (`ownerIdentityId`)" + } + ], + "foreignKeys": [ + { + "table": "identities", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "ownerIdentityId" + ], + "referencedColumns": [ + "identityId" + ] + } + ] + }, + { + "tableName": "dashpay_ignored_senders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`networkRaw` INTEGER NOT NULL, `ownerIdentityId` BLOB NOT NULL, `ignoredSenderId` BLOB NOT NULL, `ignoredAt` INTEGER NOT NULL, PRIMARY KEY(`networkRaw`, `ownerIdentityId`, `ignoredSenderId`), FOREIGN KEY(`ownerIdentityId`) REFERENCES `identities`(`identityId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "networkRaw", + "columnName": "networkRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ownerIdentityId", + "columnName": "ownerIdentityId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "ignoredSenderId", + "columnName": "ignoredSenderId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "ignoredAt", + "columnName": "ignoredAt", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "networkRaw", + "ownerIdentityId", + "ignoredSenderId" + ] + }, + "indices": [ + { + "name": "index_dashpay_ignored_senders_ownerIdentityId", + "unique": false, + "columnNames": [ + "ownerIdentityId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_dashpay_ignored_senders_ownerIdentityId` ON `${TABLE_NAME}` (`ownerIdentityId`)" + } + ], + "foreignKeys": [ + { + "table": "identities", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "ownerIdentityId" + ], + "referencedColumns": [ + "identityId" + ] + } + ] + }, + { + "tableName": "dashpay_contact_profiles", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`networkRaw` INTEGER NOT NULL, `ownerIdentityId` BLOB NOT NULL, `contactIdentityId` BLOB NOT NULL, `displayName` TEXT, `publicMessage` TEXT, `bio` TEXT, `avatarUrl` TEXT, `avatarHash` BLOB, `avatarFingerprint` BLOB, `checkedAtMs` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`networkRaw`, `ownerIdentityId`, `contactIdentityId`), FOREIGN KEY(`ownerIdentityId`) REFERENCES `identities`(`identityId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "networkRaw", + "columnName": "networkRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ownerIdentityId", + "columnName": "ownerIdentityId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "contactIdentityId", + "columnName": "contactIdentityId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "displayName", + "columnName": "displayName", + "affinity": "TEXT" + }, + { + "fieldPath": "publicMessage", + "columnName": "publicMessage", + "affinity": "TEXT" + }, + { + "fieldPath": "bio", + "columnName": "bio", + "affinity": "TEXT" + }, + { + "fieldPath": "avatarUrl", + "columnName": "avatarUrl", + "affinity": "TEXT" + }, + { + "fieldPath": "avatarHash", + "columnName": "avatarHash", + "affinity": "BLOB" + }, + { + "fieldPath": "avatarFingerprint", + "columnName": "avatarFingerprint", + "affinity": "BLOB" + }, + { + "fieldPath": "checkedAtMs", + "columnName": "checkedAtMs", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "networkRaw", + "ownerIdentityId", + "contactIdentityId" + ] + }, + "indices": [ + { + "name": "index_dashpay_contact_profiles_ownerIdentityId", + "unique": false, + "columnNames": [ + "ownerIdentityId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_dashpay_contact_profiles_ownerIdentityId` ON `${TABLE_NAME}` (`ownerIdentityId`)" + } + ], + "foreignKeys": [ + { + "table": "identities", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "ownerIdentityId" + ], + "referencedColumns": [ + "identityId" + ] + } + ] + }, + { + "tableName": "dashpay_payments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`networkRaw` INTEGER NOT NULL, `ownerIdentityId` BLOB NOT NULL, `counterpartyIdentityId` BLOB NOT NULL, `amountDuffs` INTEGER NOT NULL, `directionRaw` INTEGER NOT NULL, `statusRaw` INTEGER NOT NULL, `txid` TEXT NOT NULL, `memo` TEXT, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`networkRaw`, `ownerIdentityId`, `txid`), FOREIGN KEY(`ownerIdentityId`) REFERENCES `identities`(`identityId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "networkRaw", + "columnName": "networkRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ownerIdentityId", + "columnName": "ownerIdentityId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "counterpartyIdentityId", + "columnName": "counterpartyIdentityId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "amountDuffs", + "columnName": "amountDuffs", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "directionRaw", + "columnName": "directionRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "statusRaw", + "columnName": "statusRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "txid", + "columnName": "txid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "memo", + "columnName": "memo", + "affinity": "TEXT" + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "networkRaw", + "ownerIdentityId", + "txid" + ] + }, + "indices": [ + { + "name": "index_dashpay_payments_ownerIdentityId", + "unique": false, + "columnNames": [ + "ownerIdentityId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_dashpay_payments_ownerIdentityId` ON `${TABLE_NAME}` (`ownerIdentityId`)" + } + ], + "foreignKeys": [ + { + "table": "identities", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "ownerIdentityId" + ], + "referencedColumns": [ + "identityId" + ] + } + ] + }, + { + "tableName": "data_contracts", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` BLOB NOT NULL, `name` TEXT NOT NULL, `serializedContract` BLOB NOT NULL, `createdAt` INTEGER NOT NULL, `lastAccessedAt` INTEGER NOT NULL, `binarySerialization` BLOB, `version` INTEGER, `ownerId` BLOB, `contractDescription` TEXT, `schemaData` BLOB NOT NULL, `documentTypesData` BLOB NOT NULL, `groupsData` BLOB, `networkRaw` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, `lastSyncedAt` INTEGER, `canBeDeleted` INTEGER NOT NULL, `readonly` INTEGER NOT NULL, `keepsHistory` INTEGER NOT NULL, `schemaDefs` INTEGER, `documentsKeepHistoryContractDefault` INTEGER NOT NULL, `documentsMutableContractDefault` INTEGER NOT NULL, `documentsCanBeDeletedContractDefault` INTEGER NOT NULL, `hasTokens` INTEGER NOT NULL, `tokensData` BLOB, `ownerIdentityId` BLOB, PRIMARY KEY(`id`), FOREIGN KEY(`ownerIdentityId`) REFERENCES `identities`(`identityId`) ON UPDATE NO ACTION ON DELETE SET NULL )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serializedContract", + "columnName": "serializedContract", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastAccessedAt", + "columnName": "lastAccessedAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "binarySerialization", + "columnName": "binarySerialization", + "affinity": "BLOB" + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER" + }, + { + "fieldPath": "ownerId", + "columnName": "ownerId", + "affinity": "BLOB" + }, + { + "fieldPath": "contractDescription", + "columnName": "contractDescription", + "affinity": "TEXT" + }, + { + "fieldPath": "schemaData", + "columnName": "schemaData", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "documentTypesData", + "columnName": "documentTypesData", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "groupsData", + "columnName": "groupsData", + "affinity": "BLOB" + }, + { + "fieldPath": "networkRaw", + "columnName": "networkRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastSyncedAt", + "columnName": "lastSyncedAt", + "affinity": "INTEGER" + }, + { + "fieldPath": "canBeDeleted", + "columnName": "canBeDeleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readonly", + "columnName": "readonly", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keepsHistory", + "columnName": "keepsHistory", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "schemaDefs", + "columnName": "schemaDefs", + "affinity": "INTEGER" + }, + { + "fieldPath": "documentsKeepHistoryContractDefault", + "columnName": "documentsKeepHistoryContractDefault", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "documentsMutableContractDefault", + "columnName": "documentsMutableContractDefault", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "documentsCanBeDeletedContractDefault", + "columnName": "documentsCanBeDeletedContractDefault", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasTokens", + "columnName": "hasTokens", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tokensData", + "columnName": "tokensData", + "affinity": "BLOB" + }, + { + "fieldPath": "ownerIdentityId", + "columnName": "ownerIdentityId", + "affinity": "BLOB" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_data_contracts_networkRaw", + "unique": false, + "columnNames": [ + "networkRaw" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_data_contracts_networkRaw` ON `${TABLE_NAME}` (`networkRaw`)" + }, + { + "name": "index_data_contracts_ownerIdentityId", + "unique": false, + "columnNames": [ + "ownerIdentityId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_data_contracts_ownerIdentityId` ON `${TABLE_NAME}` (`ownerIdentityId`)" + } + ], + "foreignKeys": [ + { + "table": "identities", + "onDelete": "SET NULL", + "onUpdate": "NO ACTION", + "columns": [ + "ownerIdentityId" + ], + "referencedColumns": [ + "identityId" + ] + } + ] + }, + { + "tableName": "document_types", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` BLOB NOT NULL, `contractId` BLOB NOT NULL, `name` TEXT NOT NULL, `schemaJSON` BLOB NOT NULL, `propertiesJSON` BLOB NOT NULL, `documentsKeepHistory` INTEGER NOT NULL, `documentsMutable` INTEGER NOT NULL, `documentsCanBeDeleted` INTEGER NOT NULL, `documentsTransferable` INTEGER NOT NULL, `requiredFieldsJSON` BLOB, `securityLevel` INTEGER NOT NULL, `tradeMode` INTEGER NOT NULL, `creationRestrictionMode` INTEGER NOT NULL, `requiresIdentityEncryptionBoundedKey` INTEGER NOT NULL, `requiresIdentityDecryptionBoundedKey` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `lastAccessedAt` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`contractId`) REFERENCES `data_contracts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "contractId", + "columnName": "contractId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schemaJSON", + "columnName": "schemaJSON", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "propertiesJSON", + "columnName": "propertiesJSON", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "documentsKeepHistory", + "columnName": "documentsKeepHistory", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "documentsMutable", + "columnName": "documentsMutable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "documentsCanBeDeleted", + "columnName": "documentsCanBeDeleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "documentsTransferable", + "columnName": "documentsTransferable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "requiredFieldsJSON", + "columnName": "requiredFieldsJSON", + "affinity": "BLOB" + }, + { + "fieldPath": "securityLevel", + "columnName": "securityLevel", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tradeMode", + "columnName": "tradeMode", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "creationRestrictionMode", + "columnName": "creationRestrictionMode", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "requiresIdentityEncryptionBoundedKey", + "columnName": "requiresIdentityEncryptionBoundedKey", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "requiresIdentityDecryptionBoundedKey", + "columnName": "requiresIdentityDecryptionBoundedKey", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastAccessedAt", + "columnName": "lastAccessedAt", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_document_types_contractId", + "unique": false, + "columnNames": [ + "contractId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_document_types_contractId` ON `${TABLE_NAME}` (`contractId`)" + } + ], + "foreignKeys": [ + { + "table": "data_contracts", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "contractId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "documents", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`documentId` TEXT NOT NULL, `documentType` TEXT NOT NULL, `revision` INTEGER NOT NULL, `data` BLOB NOT NULL, `contractId` TEXT NOT NULL, `ownerId` TEXT NOT NULL, `contractIdData` BLOB NOT NULL, `ownerIdData` BLOB NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `transferredAt` INTEGER, `createdAtBlockHeight` INTEGER, `updatedAtBlockHeight` INTEGER, `transferredAtBlockHeight` INTEGER, `createdAtCoreBlockHeight` INTEGER, `updatedAtCoreBlockHeight` INTEGER, `transferredAtCoreBlockHeight` INTEGER, `networkRaw` INTEGER NOT NULL, `isDeleted` INTEGER NOT NULL, `localCreatedAt` INTEGER NOT NULL, `localUpdatedAt` INTEGER NOT NULL, `documentTypeRelationId` BLOB, `dataContractId` BLOB, `ownerIdentityId` BLOB, PRIMARY KEY(`documentId`), FOREIGN KEY(`documentTypeRelationId`) REFERENCES `document_types`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`dataContractId`) REFERENCES `data_contracts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`ownerIdentityId`) REFERENCES `identities`(`identityId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "documentId", + "columnName": "documentId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "documentType", + "columnName": "documentType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "revision", + "columnName": "revision", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "contractId", + "columnName": "contractId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ownerId", + "columnName": "ownerId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contractIdData", + "columnName": "contractIdData", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "ownerIdData", + "columnName": "ownerIdData", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updatedAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "transferredAt", + "columnName": "transferredAt", + "affinity": "INTEGER" + }, + { + "fieldPath": "createdAtBlockHeight", + "columnName": "createdAtBlockHeight", + "affinity": "INTEGER" + }, + { + "fieldPath": "updatedAtBlockHeight", + "columnName": "updatedAtBlockHeight", + "affinity": "INTEGER" + }, + { + "fieldPath": "transferredAtBlockHeight", + "columnName": "transferredAtBlockHeight", + "affinity": "INTEGER" + }, + { + "fieldPath": "createdAtCoreBlockHeight", + "columnName": "createdAtCoreBlockHeight", + "affinity": "INTEGER" + }, + { + "fieldPath": "updatedAtCoreBlockHeight", + "columnName": "updatedAtCoreBlockHeight", + "affinity": "INTEGER" + }, + { + "fieldPath": "transferredAtCoreBlockHeight", + "columnName": "transferredAtCoreBlockHeight", + "affinity": "INTEGER" + }, + { + "fieldPath": "networkRaw", + "columnName": "networkRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDeleted", + "columnName": "isDeleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "localCreatedAt", + "columnName": "localCreatedAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "localUpdatedAt", + "columnName": "localUpdatedAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "documentTypeRelationId", + "columnName": "documentTypeRelationId", + "affinity": "BLOB" + }, + { + "fieldPath": "dataContractId", + "columnName": "dataContractId", + "affinity": "BLOB" + }, + { + "fieldPath": "ownerIdentityId", + "columnName": "ownerIdentityId", + "affinity": "BLOB" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "documentId" + ] + }, + "indices": [ + { + "name": "index_documents_networkRaw", + "unique": false, + "columnNames": [ + "networkRaw" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_documents_networkRaw` ON `${TABLE_NAME}` (`networkRaw`)" + }, + { + "name": "index_documents_contractId", + "unique": false, + "columnNames": [ + "contractId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_documents_contractId` ON `${TABLE_NAME}` (`contractId`)" + }, + { + "name": "index_documents_ownerId", + "unique": false, + "columnNames": [ + "ownerId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_documents_ownerId` ON `${TABLE_NAME}` (`ownerId`)" + }, + { + "name": "index_documents_documentTypeRelationId", + "unique": false, + "columnNames": [ + "documentTypeRelationId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_documents_documentTypeRelationId` ON `${TABLE_NAME}` (`documentTypeRelationId`)" + }, + { + "name": "index_documents_dataContractId", + "unique": false, + "columnNames": [ + "dataContractId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_documents_dataContractId` ON `${TABLE_NAME}` (`dataContractId`)" + }, + { + "name": "index_documents_ownerIdentityId", + "unique": false, + "columnNames": [ + "ownerIdentityId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_documents_ownerIdentityId` ON `${TABLE_NAME}` (`ownerIdentityId`)" + } + ], + "foreignKeys": [ + { + "table": "document_types", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "documentTypeRelationId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "data_contracts", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "dataContractId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "identities", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "ownerIdentityId" + ], + "referencedColumns": [ + "identityId" + ] + } + ] + }, + { + "tableName": "indices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` BLOB NOT NULL, `contractId` BLOB NOT NULL, `documentTypeName` TEXT NOT NULL, `name` TEXT NOT NULL, `unique` INTEGER NOT NULL, `nullSearchable` INTEGER NOT NULL, `contested` INTEGER NOT NULL, `propertiesJSON` BLOB NOT NULL, `contestedDetailsJSON` BLOB, `createdAt` INTEGER NOT NULL, `documentTypeId` BLOB, PRIMARY KEY(`id`), FOREIGN KEY(`documentTypeId`) REFERENCES `document_types`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "contractId", + "columnName": "contractId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "documentTypeName", + "columnName": "documentTypeName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "unique", + "columnName": "unique", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nullSearchable", + "columnName": "nullSearchable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "contested", + "columnName": "contested", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "propertiesJSON", + "columnName": "propertiesJSON", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "contestedDetailsJSON", + "columnName": "contestedDetailsJSON", + "affinity": "BLOB" + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "documentTypeId", + "columnName": "documentTypeId", + "affinity": "BLOB" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_indices_documentTypeId", + "unique": false, + "columnNames": [ + "documentTypeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_indices_documentTypeId` ON `${TABLE_NAME}` (`documentTypeId`)" + } + ], + "foreignKeys": [ + { + "table": "document_types", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "documentTypeId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "keywords", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `keyword` TEXT NOT NULL, `contractId` TEXT NOT NULL, `dataContractId` BLOB, PRIMARY KEY(`id`), FOREIGN KEY(`dataContractId`) REFERENCES `data_contracts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "keyword", + "columnName": "keyword", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contractId", + "columnName": "contractId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dataContractId", + "columnName": "dataContractId", + "affinity": "BLOB" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_keywords_contractId", + "unique": false, + "columnNames": [ + "contractId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_keywords_contractId` ON `${TABLE_NAME}` (`contractId`)" + }, + { + "name": "index_keywords_dataContractId", + "unique": false, + "columnNames": [ + "dataContractId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_keywords_dataContractId` ON `${TABLE_NAME}` (`dataContractId`)" + } + ], + "foreignKeys": [ + { + "table": "data_contracts", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "dataContractId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "properties", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` BLOB NOT NULL, `contractId` BLOB NOT NULL, `documentTypeName` TEXT NOT NULL, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `format` TEXT, `contentMediaType` TEXT, `byteArray` INTEGER NOT NULL, `minItems` INTEGER, `maxItems` INTEGER, `pattern` TEXT, `minLength` INTEGER, `maxLength` INTEGER, `minValue` INTEGER, `maxValue` INTEGER, `fieldDescription` TEXT, `transient` INTEGER NOT NULL, `isRequired` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `documentTypeId` BLOB, PRIMARY KEY(`id`), FOREIGN KEY(`documentTypeId`) REFERENCES `document_types`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "contractId", + "columnName": "contractId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "documentTypeName", + "columnName": "documentTypeName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "format", + "columnName": "format", + "affinity": "TEXT" + }, + { + "fieldPath": "contentMediaType", + "columnName": "contentMediaType", + "affinity": "TEXT" + }, + { + "fieldPath": "byteArray", + "columnName": "byteArray", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "minItems", + "columnName": "minItems", + "affinity": "INTEGER" + }, + { + "fieldPath": "maxItems", + "columnName": "maxItems", + "affinity": "INTEGER" + }, + { + "fieldPath": "pattern", + "columnName": "pattern", + "affinity": "TEXT" + }, + { + "fieldPath": "minLength", + "columnName": "minLength", + "affinity": "INTEGER" + }, + { + "fieldPath": "maxLength", + "columnName": "maxLength", + "affinity": "INTEGER" + }, + { + "fieldPath": "minValue", + "columnName": "minValue", + "affinity": "INTEGER" + }, + { + "fieldPath": "maxValue", + "columnName": "maxValue", + "affinity": "INTEGER" + }, + { + "fieldPath": "fieldDescription", + "columnName": "fieldDescription", + "affinity": "TEXT" + }, + { + "fieldPath": "transient", + "columnName": "transient", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRequired", + "columnName": "isRequired", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "documentTypeId", + "columnName": "documentTypeId", + "affinity": "BLOB" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_properties_documentTypeId", + "unique": false, + "columnNames": [ + "documentTypeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_properties_documentTypeId` ON `${TABLE_NAME}` (`documentTypeId`)" + } + ], + "foreignKeys": [ + { + "table": "document_types", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "documentTypeId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "pending_inputs", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `outpoint` BLOB NOT NULL, `inputIndex` INTEGER NOT NULL, `spendingTxid` BLOB NOT NULL, `spendingTransactionTxid` BLOB, `walletId` BLOB NOT NULL, `createdAt` INTEGER NOT NULL, `isSweptTombstone` INTEGER NOT NULL DEFAULT 0, `winnerMinedHeight` INTEGER, FOREIGN KEY(`spendingTransactionTxid`) REFERENCES `transactions`(`txid`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "outpoint", + "columnName": "outpoint", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "inputIndex", + "columnName": "inputIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "spendingTxid", + "columnName": "spendingTxid", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "spendingTransactionTxid", + "columnName": "spendingTransactionTxid", + "affinity": "BLOB" + }, + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isSweptTombstone", + "columnName": "isSweptTombstone", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "winnerMinedHeight", + "columnName": "winnerMinedHeight", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_pending_inputs_outpoint", + "unique": false, + "columnNames": [ + "outpoint" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_pending_inputs_outpoint` ON `${TABLE_NAME}` (`outpoint`)" + }, + { + "name": "index_pending_inputs_walletId", + "unique": false, + "columnNames": [ + "walletId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_pending_inputs_walletId` ON `${TABLE_NAME}` (`walletId`)" + }, + { + "name": "index_pending_inputs_spendingTransactionTxid", + "unique": false, + "columnNames": [ + "spendingTransactionTxid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_pending_inputs_spendingTransactionTxid` ON `${TABLE_NAME}` (`spendingTransactionTxid`)" + }, + { + "name": "index_pending_inputs_spendingTxid", + "unique": false, + "columnNames": [ + "spendingTxid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_pending_inputs_spendingTxid` ON `${TABLE_NAME}` (`spendingTxid`)" + }, + { + "name": "index_pending_inputs_walletId_isSweptTombstone_winnerMinedHeight", + "unique": false, + "columnNames": [ + "walletId", + "isSweptTombstone", + "winnerMinedHeight" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_pending_inputs_walletId_isSweptTombstone_winnerMinedHeight` ON `${TABLE_NAME}` (`walletId`, `isSweptTombstone`, `winnerMinedHeight`)" + } + ], + "foreignKeys": [ + { + "table": "transactions", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "spendingTransactionTxid" + ], + "referencedColumns": [ + "txid" + ] + } + ] + }, + { + "tableName": "tokens", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` BLOB NOT NULL, `contractId` BLOB NOT NULL, `position` INTEGER NOT NULL, `name` TEXT NOT NULL, `baseSupply` TEXT NOT NULL, `maxSupply` TEXT, `decimals` INTEGER NOT NULL, `localizations` TEXT, `isPaused` INTEGER NOT NULL, `allowTransferToFrozenBalance` INTEGER NOT NULL, `keepsTransferHistory` INTEGER NOT NULL, `keepsFreezingHistory` INTEGER NOT NULL, `keepsMintingHistory` INTEGER NOT NULL, `keepsBurningHistory` INTEGER NOT NULL, `keepsDirectPricingHistory` INTEGER NOT NULL, `keepsDirectPurchaseHistory` INTEGER NOT NULL, `conventionsChangeRules` TEXT, `maxSupplyChangeRules` TEXT, `manualMintingRules` TEXT, `manualBurningRules` TEXT, `freezeRules` TEXT, `unfreezeRules` TEXT, `destroyFrozenFundsRules` TEXT, `emergencyActionRules` TEXT, `perpetualDistribution` TEXT, `preProgrammedDistribution` TEXT, `newTokensDestinationIdentity` BLOB, `mintingAllowChoosingDestination` INTEGER NOT NULL, `distributionChangeRules` TEXT, `tradeMode` TEXT NOT NULL, `tradeModeChangeRules` TEXT, `mainControlGroupPosition` INTEGER, `mainControlGroupCanBeModified` TEXT, `tokenDescription` TEXT, `createdAt` INTEGER NOT NULL, `lastUpdatedAt` INTEGER NOT NULL, `canManuallyMint` INTEGER NOT NULL, `canManuallyBurn` INTEGER NOT NULL, `canFreeze` INTEGER NOT NULL, `canUnfreeze` INTEGER NOT NULL, `canDestroyFrozenFunds` INTEGER NOT NULL, `hasEmergencyActions` INTEGER NOT NULL, `canChangeMaxSupply` INTEGER NOT NULL, `canChangeConventions` INTEGER NOT NULL, `canChangeTradeMode` INTEGER NOT NULL, `hasDistribution` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`contractId`) REFERENCES `data_contracts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "contractId", + "columnName": "contractId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "baseSupply", + "columnName": "baseSupply", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "maxSupply", + "columnName": "maxSupply", + "affinity": "TEXT" + }, + { + "fieldPath": "decimals", + "columnName": "decimals", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "localizations", + "columnName": "localizations", + "affinity": "TEXT" + }, + { + "fieldPath": "isPaused", + "columnName": "isPaused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "allowTransferToFrozenBalance", + "columnName": "allowTransferToFrozenBalance", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keepsTransferHistory", + "columnName": "keepsTransferHistory", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keepsFreezingHistory", + "columnName": "keepsFreezingHistory", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keepsMintingHistory", + "columnName": "keepsMintingHistory", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keepsBurningHistory", + "columnName": "keepsBurningHistory", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keepsDirectPricingHistory", + "columnName": "keepsDirectPricingHistory", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keepsDirectPurchaseHistory", + "columnName": "keepsDirectPurchaseHistory", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "conventionsChangeRules", + "columnName": "conventionsChangeRules", + "affinity": "TEXT" + }, + { + "fieldPath": "maxSupplyChangeRules", + "columnName": "maxSupplyChangeRules", + "affinity": "TEXT" + }, + { + "fieldPath": "manualMintingRules", + "columnName": "manualMintingRules", + "affinity": "TEXT" + }, + { + "fieldPath": "manualBurningRules", + "columnName": "manualBurningRules", + "affinity": "TEXT" + }, + { + "fieldPath": "freezeRules", + "columnName": "freezeRules", + "affinity": "TEXT" + }, + { + "fieldPath": "unfreezeRules", + "columnName": "unfreezeRules", + "affinity": "TEXT" + }, + { + "fieldPath": "destroyFrozenFundsRules", + "columnName": "destroyFrozenFundsRules", + "affinity": "TEXT" + }, + { + "fieldPath": "emergencyActionRules", + "columnName": "emergencyActionRules", + "affinity": "TEXT" + }, + { + "fieldPath": "perpetualDistribution", + "columnName": "perpetualDistribution", + "affinity": "TEXT" + }, + { + "fieldPath": "preProgrammedDistribution", + "columnName": "preProgrammedDistribution", + "affinity": "TEXT" + }, + { + "fieldPath": "newTokensDestinationIdentity", + "columnName": "newTokensDestinationIdentity", + "affinity": "BLOB" + }, + { + "fieldPath": "mintingAllowChoosingDestination", + "columnName": "mintingAllowChoosingDestination", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "distributionChangeRules", + "columnName": "distributionChangeRules", + "affinity": "TEXT" + }, + { + "fieldPath": "tradeMode", + "columnName": "tradeMode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "tradeModeChangeRules", + "columnName": "tradeModeChangeRules", + "affinity": "TEXT" + }, + { + "fieldPath": "mainControlGroupPosition", + "columnName": "mainControlGroupPosition", + "affinity": "INTEGER" + }, + { + "fieldPath": "mainControlGroupCanBeModified", + "columnName": "mainControlGroupCanBeModified", + "affinity": "TEXT" + }, + { + "fieldPath": "tokenDescription", + "columnName": "tokenDescription", + "affinity": "TEXT" + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdatedAt", + "columnName": "lastUpdatedAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canManuallyMint", + "columnName": "canManuallyMint", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canManuallyBurn", + "columnName": "canManuallyBurn", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canFreeze", + "columnName": "canFreeze", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canUnfreeze", + "columnName": "canUnfreeze", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canDestroyFrozenFunds", + "columnName": "canDestroyFrozenFunds", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasEmergencyActions", + "columnName": "hasEmergencyActions", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canChangeMaxSupply", + "columnName": "canChangeMaxSupply", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canChangeConventions", + "columnName": "canChangeConventions", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canChangeTradeMode", + "columnName": "canChangeTradeMode", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasDistribution", + "columnName": "hasDistribution", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_tokens_contractId", + "unique": false, + "columnNames": [ + "contractId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_tokens_contractId` ON `${TABLE_NAME}` (`contractId`)" + } + ], + "foreignKeys": [ + { + "table": "data_contracts", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "contractId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "token_balances", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `tokenId` TEXT NOT NULL, `identityId` BLOB NOT NULL, `balance` BLOB NOT NULL, `frozen` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, `lastSyncedAt` INTEGER, `tokenName` TEXT, `tokenSymbol` TEXT, `tokenDecimals` INTEGER, `networkRaw` INTEGER NOT NULL, `identityRef` BLOB, `tokenRef` BLOB, FOREIGN KEY(`identityRef`) REFERENCES `identities`(`identityId`) ON UPDATE NO ACTION ON DELETE SET NULL , FOREIGN KEY(`tokenRef`) REFERENCES `tokens`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tokenId", + "columnName": "tokenId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "identityId", + "columnName": "identityId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "balance", + "columnName": "balance", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "frozen", + "columnName": "frozen", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastSyncedAt", + "columnName": "lastSyncedAt", + "affinity": "INTEGER" + }, + { + "fieldPath": "tokenName", + "columnName": "tokenName", + "affinity": "TEXT" + }, + { + "fieldPath": "tokenSymbol", + "columnName": "tokenSymbol", + "affinity": "TEXT" + }, + { + "fieldPath": "tokenDecimals", + "columnName": "tokenDecimals", + "affinity": "INTEGER" + }, + { + "fieldPath": "networkRaw", + "columnName": "networkRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "identityRef", + "columnName": "identityRef", + "affinity": "BLOB" + }, + { + "fieldPath": "tokenRef", + "columnName": "tokenRef", + "affinity": "BLOB" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_token_balances_networkRaw", + "unique": false, + "columnNames": [ + "networkRaw" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_token_balances_networkRaw` ON `${TABLE_NAME}` (`networkRaw`)" + }, + { + "name": "index_token_balances_tokenId_identityId", + "unique": false, + "columnNames": [ + "tokenId", + "identityId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_token_balances_tokenId_identityId` ON `${TABLE_NAME}` (`tokenId`, `identityId`)" + }, + { + "name": "index_token_balances_identityId", + "unique": false, + "columnNames": [ + "identityId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_token_balances_identityId` ON `${TABLE_NAME}` (`identityId`)" + }, + { + "name": "index_token_balances_identityRef", + "unique": false, + "columnNames": [ + "identityRef" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_token_balances_identityRef` ON `${TABLE_NAME}` (`identityRef`)" + }, + { + "name": "index_token_balances_tokenRef", + "unique": false, + "columnNames": [ + "tokenRef" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_token_balances_tokenRef` ON `${TABLE_NAME}` (`tokenRef`)" + } + ], + "foreignKeys": [ + { + "table": "identities", + "onDelete": "SET NULL", + "onUpdate": "NO ACTION", + "columns": [ + "identityRef" + ], + "referencedColumns": [ + "identityId" + ] + }, + { + "table": "tokens", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "tokenRef" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "token_history_events", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `eventType` TEXT NOT NULL, `transactionId` BLOB, `blockHeight` INTEGER, `coreBlockHeight` INTEGER, `fromIdentity` BLOB, `toIdentity` BLOB, `performedByIdentity` BLOB NOT NULL, `amount` TEXT, `balanceBefore` TEXT, `balanceAfter` TEXT, `additionalDataJSON` BLOB, `eventDescription` TEXT, `createdAt` INTEGER NOT NULL, `eventTimestamp` INTEGER NOT NULL, `tokenRef` BLOB, PRIMARY KEY(`id`), FOREIGN KEY(`tokenRef`) REFERENCES `tokens`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "eventType", + "columnName": "eventType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "transactionId", + "columnName": "transactionId", + "affinity": "BLOB" + }, + { + "fieldPath": "blockHeight", + "columnName": "blockHeight", + "affinity": "INTEGER" + }, + { + "fieldPath": "coreBlockHeight", + "columnName": "coreBlockHeight", + "affinity": "INTEGER" + }, + { + "fieldPath": "fromIdentity", + "columnName": "fromIdentity", + "affinity": "BLOB" + }, + { + "fieldPath": "toIdentity", + "columnName": "toIdentity", + "affinity": "BLOB" + }, + { + "fieldPath": "performedByIdentity", + "columnName": "performedByIdentity", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "TEXT" + }, + { + "fieldPath": "balanceBefore", + "columnName": "balanceBefore", + "affinity": "TEXT" + }, + { + "fieldPath": "balanceAfter", + "columnName": "balanceAfter", + "affinity": "TEXT" + }, + { + "fieldPath": "additionalDataJSON", + "columnName": "additionalDataJSON", + "affinity": "BLOB" + }, + { + "fieldPath": "eventDescription", + "columnName": "eventDescription", + "affinity": "TEXT" + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "eventTimestamp", + "columnName": "eventTimestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tokenRef", + "columnName": "tokenRef", + "affinity": "BLOB" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_token_history_events_tokenRef", + "unique": false, + "columnNames": [ + "tokenRef" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_token_history_events_tokenRef` ON `${TABLE_NAME}` (`tokenRef`)" + } + ], + "foreignKeys": [ + { + "table": "tokens", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "tokenRef" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "platform_addresses", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`address` TEXT NOT NULL, `addressType` INTEGER NOT NULL, `addressHash` BLOB NOT NULL, `publicKey` BLOB NOT NULL, `accountIndex` INTEGER NOT NULL, `addressIndex` INTEGER NOT NULL, `derivationPath` TEXT NOT NULL, `isUsed` INTEGER NOT NULL, `balance` INTEGER NOT NULL, `nonce` INTEGER NOT NULL, `firstSeenHeight` INTEGER NOT NULL, `lastSeenHeight` INTEGER NOT NULL, `walletId` BLOB NOT NULL, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, `accountId` INTEGER, PRIMARY KEY(`walletId`, `address`), FOREIGN KEY(`accountId`) REFERENCES `accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "addressType", + "columnName": "addressType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addressHash", + "columnName": "addressHash", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "publicKey", + "columnName": "publicKey", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "accountIndex", + "columnName": "accountIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addressIndex", + "columnName": "addressIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "derivationPath", + "columnName": "derivationPath", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isUsed", + "columnName": "isUsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "balance", + "columnName": "balance", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nonce", + "columnName": "nonce", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstSeenHeight", + "columnName": "firstSeenHeight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastSeenHeight", + "columnName": "lastSeenHeight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountId", + "columnName": "accountId", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "walletId", + "address" + ] + }, + "indices": [ + { + "name": "index_platform_addresses_walletId_addressHash", + "unique": true, + "columnNames": [ + "walletId", + "addressHash" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_platform_addresses_walletId_addressHash` ON `${TABLE_NAME}` (`walletId`, `addressHash`)" + }, + { + "name": "index_platform_addresses_accountId", + "unique": false, + "columnNames": [ + "accountId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_platform_addresses_accountId` ON `${TABLE_NAME}` (`accountId`)" + } + ], + "foreignKeys": [ + { + "table": "accounts", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "accountId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "platform_addresses_sync_states", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`walletId` BLOB NOT NULL, `networkRaw` INTEGER NOT NULL, `syncHeight` INTEGER NOT NULL, `syncTimestamp` INTEGER NOT NULL, `lastKnownRecentBlock` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`walletId`))", + "fields": [ + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "networkRaw", + "columnName": "networkRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "syncHeight", + "columnName": "syncHeight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "syncTimestamp", + "columnName": "syncTimestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastKnownRecentBlock", + "columnName": "lastKnownRecentBlock", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "walletId" + ] + }, + "indices": [ + { + "name": "index_platform_addresses_sync_states_networkRaw", + "unique": false, + "columnNames": [ + "networkRaw" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_platform_addresses_sync_states_networkRaw` ON `${TABLE_NAME}` (`networkRaw`)" + } + ] + }, + { + "tableName": "shielded_notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`nullifier` BLOB NOT NULL, `walletId` BLOB NOT NULL, `accountIndex` INTEGER NOT NULL, `position` INTEGER NOT NULL, `cmx` BLOB NOT NULL, `blockHeight` INTEGER NOT NULL, `isSpent` INTEGER NOT NULL, `value` INTEGER NOT NULL, `noteData` BLOB NOT NULL, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`nullifier`))", + "fields": [ + { + "fieldPath": "nullifier", + "columnName": "nullifier", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "accountIndex", + "columnName": "accountIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "cmx", + "columnName": "cmx", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "blockHeight", + "columnName": "blockHeight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isSpent", + "columnName": "isSpent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "noteData", + "columnName": "noteData", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "nullifier" + ] + }, + "indices": [ + { + "name": "index_shielded_notes_walletId_accountIndex", + "unique": false, + "columnNames": [ + "walletId", + "accountIndex" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_shielded_notes_walletId_accountIndex` ON `${TABLE_NAME}` (`walletId`, `accountIndex`)" + } + ] + }, + { + "tableName": "shielded_outgoing_notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`walletId` BLOB NOT NULL, `accountIndex` INTEGER NOT NULL, `cmx` BLOB NOT NULL, `recipient` BLOB NOT NULL, `value` INTEGER NOT NULL, `memo` BLOB NOT NULL, `blockHeight` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`walletId`, `accountIndex`, `cmx`))", + "fields": [ + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "accountIndex", + "columnName": "accountIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "cmx", + "columnName": "cmx", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "memo", + "columnName": "memo", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "blockHeight", + "columnName": "blockHeight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "walletId", + "accountIndex", + "cmx" + ] + }, + "indices": [ + { + "name": "index_shielded_outgoing_notes_walletId_accountIndex", + "unique": false, + "columnNames": [ + "walletId", + "accountIndex" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_shielded_outgoing_notes_walletId_accountIndex` ON `${TABLE_NAME}` (`walletId`, `accountIndex`)" + } + ] + }, + { + "tableName": "shielded_activities", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`walletId` BLOB NOT NULL, `accountIndex` INTEGER NOT NULL, `entryId` BLOB NOT NULL, `kindTag` INTEGER NOT NULL, `direction` INTEGER NOT NULL, `status` INTEGER NOT NULL, `amount` INTEGER NOT NULL, `fee` INTEGER NOT NULL, `hasFee` INTEGER NOT NULL, `blockHeight` INTEGER NOT NULL, `hasBlockHeight` INTEGER NOT NULL, `createdAtMs` INTEGER NOT NULL, `identityId` BLOB NOT NULL, `counterparty` BLOB NOT NULL, `memo` BLOB NOT NULL, `noteCmxs` BLOB NOT NULL, `spentNullifiers` BLOB NOT NULL, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`walletId`, `accountIndex`, `entryId`))", + "fields": [ + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "accountIndex", + "columnName": "accountIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryId", + "columnName": "entryId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "kindTag", + "columnName": "kindTag", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "direction", + "columnName": "direction", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fee", + "columnName": "fee", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasFee", + "columnName": "hasFee", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "blockHeight", + "columnName": "blockHeight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasBlockHeight", + "columnName": "hasBlockHeight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAtMs", + "columnName": "createdAtMs", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "identityId", + "columnName": "identityId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "counterparty", + "columnName": "counterparty", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "memo", + "columnName": "memo", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "noteCmxs", + "columnName": "noteCmxs", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "spentNullifiers", + "columnName": "spentNullifiers", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "walletId", + "accountIndex", + "entryId" + ] + }, + "indices": [ + { + "name": "index_shielded_activities_walletId_accountIndex", + "unique": false, + "columnNames": [ + "walletId", + "accountIndex" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_shielded_activities_walletId_accountIndex` ON `${TABLE_NAME}` (`walletId`, `accountIndex`)" + } + ] + }, + { + "tableName": "shielded_sync_states", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`walletId` BLOB NOT NULL, `accountIndex` INTEGER NOT NULL, `lastSyncedIndex` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`walletId`, `accountIndex`))", + "fields": [ + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "accountIndex", + "columnName": "accountIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastSyncedIndex", + "columnName": "lastSyncedIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "walletId", + "accountIndex" + ] + }, + "indices": [ + { + "name": "index_shielded_sync_states_walletId", + "unique": false, + "columnNames": [ + "walletId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_shielded_sync_states_walletId` ON `${TABLE_NAME}` (`walletId`)" + } + ] + }, + { + "tableName": "shielded_viewing_keys", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`walletId` BLOB NOT NULL, `accountIndex` INTEGER NOT NULL, `fvkBytes` BLOB NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`walletId`, `accountIndex`))", + "fields": [ + { + "fieldPath": "walletId", + "columnName": "walletId", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "accountIndex", + "columnName": "accountIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fvkBytes", + "columnName": "fvkBytes", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "walletId", + "accountIndex" + ] + }, + "indices": [ + { + "name": "index_shielded_viewing_keys_walletId", + "unique": false, + "columnNames": [ + "walletId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_shielded_viewing_keys_walletId` ON `${TABLE_NAME}` (`walletId`)" + } + ] + }, + { + "tableName": "wallet_manager_metadata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`networkRaw` INTEGER NOT NULL, `combinedSyncHeight` INTEGER NOT NULL, `combinedSyncBlockHash` BLOB, `walletCount` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`networkRaw`))", + "fields": [ + { + "fieldPath": "networkRaw", + "columnName": "networkRaw", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "combinedSyncHeight", + "columnName": "combinedSyncHeight", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "combinedSyncBlockHash", + "columnName": "combinedSyncBlockHash", + "affinity": "BLOB" + }, + { + "fieldPath": "walletCount", + "columnName": "walletCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "networkRaw" + ] + } + } + ], + "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, '504cdea5b956240386df04640d2e82bf')" + ] + } +} \ No newline at end of file diff --git a/packages/kotlin-sdk/sdk/src/androidTest/kotlin/org/dashfoundation/dashsdk/persistence/DashDatabaseMigrationTest.kt b/packages/kotlin-sdk/sdk/src/androidTest/kotlin/org/dashfoundation/dashsdk/persistence/DashDatabaseMigrationTest.kt index 6f5064c5c50..6af8bd25a5c 100644 --- a/packages/kotlin-sdk/sdk/src/androidTest/kotlin/org/dashfoundation/dashsdk/persistence/DashDatabaseMigrationTest.kt +++ b/packages/kotlin-sdk/sdk/src/androidTest/kotlin/org/dashfoundation/dashsdk/persistence/DashDatabaseMigrationTest.kt @@ -505,13 +505,67 @@ class DashDatabaseMigrationTest { db.close() } + /** + * v12 → v13 adds the nullable `public_keys.contractBoundsKind` column + * (additive). Pre-existing keys, bounded or not, must survive with a + * NULL kind (the restore path infers a legacy row's kind), and new rows + * must accept an explicit kind, including 3 (ContractGroup). + */ + @Test + fun migrate12To13AddsContractBoundsKindColumn() { + helper.createDatabase(dbName, 12).apply { + execSQL( + "INSERT INTO wallets (walletId, walletGroupId, networkRaw, name, birthHeight, " + + "syncedHeight, lastSynced, isImported, createdAt, lastUpdated) " + + "VALUES (x'01', x'02', 1, 'w', 0, 0, 0, 0, 0, 0)", + ) + execSQL( + "INSERT INTO identities (identityId, balance, revision, isLocal, identityType, " + + "createdAt, lastUpdated, networkRaw, identityIndex, walletId) " + + "VALUES (x'0A', 0, 0, 1, 'User', 0, 0, 1, 0, x'01')", + ) + execSQL( + "INSERT INTO public_keys (keyId, purpose, securityLevel, keyType, readOnly, " + + "publicKeyData, contractBoundsData, contractBoundsDocumentTypeName, " + + "identityId, createdAt, identityIdData) " + + "VALUES (0, '1', '3', '0', 0, x'02AB', x'5B5D', 'contactRequest', " + + "'id-base58', 0, x'0A')", + ) + close() + } + + val db = helper.runMigrationsAndValidate(dbName, 13, true, DashDatabase.MIGRATION_12_13) + + // Pre-existing rows survive with a NULL kind and their bounds intact. + db.query( + "SELECT contractBoundsKind, contractBoundsDocumentTypeName FROM public_keys " + + "WHERE keyId = 0", + ).use { c -> + assertTrue(c.moveToFirst()) + assertTrue(c.isNull(0)) + assertEquals("contactRequest", c.getString(1)) + } + // New rows accept an explicit kind. + db.execSQL( + "INSERT INTO public_keys (keyId, purpose, securityLevel, keyType, readOnly, " + + "publicKeyData, contractBoundsData, contractBoundsKind, identityId, " + + "createdAt, identityIdData) " + + "VALUES (1, '0', '2', '0', 0, x'02CD', x'5B5D', 3, 'id-base58', 0, x'0A')", + ) + db.query("SELECT contractBoundsKind FROM public_keys WHERE keyId = 1").use { c -> + assertTrue(c.moveToFirst()) + assertEquals(3, c.getInt(0)) + } + db.close() + } + /** The requested contiguous path from the pre-u64 v4 schema to latest. */ @Test fun migrate4ToLatest() { helper.createDatabase(dbName, 4).close() helper.runMigrationsAndValidate( dbName, - 12, + 13, true, DashDatabase.MIGRATION_4_5, DashDatabase.MIGRATION_5_6, @@ -521,16 +575,17 @@ class DashDatabaseMigrationTest { DashDatabase.MIGRATION_9_10, DashDatabase.MIGRATION_10_11, DashDatabase.MIGRATION_11_12, + DashDatabase.MIGRATION_12_13, ).close() } - /** The full chain from v1 must also land on a valid v12 schema. */ + /** The full chain from v1 must also land on a valid v13 schema. */ @Test fun migrateAllTheWayFrom1() { helper.createDatabase(dbName, 1).close() helper.runMigrationsAndValidate( dbName, - 12, + 13, true, DashDatabase.MIGRATION_1_2, DashDatabase.MIGRATION_2_3, @@ -543,6 +598,7 @@ class DashDatabaseMigrationTest { DashDatabase.MIGRATION_9_10, DashDatabase.MIGRATION_10_11, DashDatabase.MIGRATION_11_12, + DashDatabase.MIGRATION_12_13, ).close() } } diff --git a/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/ffi/NativePersistenceBridge.kt b/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/ffi/NativePersistenceBridge.kt index 11ff284d1c6..76759d63ed3 100644 --- a/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/ffi/NativePersistenceBridge.kt +++ b/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/ffi/NativePersistenceBridge.kt @@ -504,10 +504,13 @@ abstract class NativePersistenceBridge { /** * One `IdentityKeyEntryFFI` upsert. Descriptor - * `([B[BIBBBZZJ[B[BZ[BZIIB[BLjava/lang/String;ZJZJ)I`. The last four - * arguments are the key's usage limits (protocol version 14): the credits it - * may spend over its lifetime when [totalBudgetIsSome], and the block time in - * milliseconds from which it can no longer sign when [expiresAtIsSome]. + * `([B[BIBBBZZJ[B[BZ[BZIIB[BLjava/lang/String;ZJZJ)I`. + * `contractBoundsKind`: 0 none, 1 SingleContract, 2 SingleContractDocumentType, + * 3 ContractGroup (`contractBoundsId` is then the contract group id and + * `contractBoundsDocumentType` is null). The last four arguments are the + * key's usage limits (protocol version 14): the credits it may spend over + * its lifetime when [totalBudgetIsSome], and the block time in milliseconds + * from which it can no longer sign when [expiresAtIsSome]. */ @Suppress("LongParameterList") open fun onPersistIdentityKeyUpsert( @@ -1263,8 +1266,10 @@ class ContactRequestRestoreData( * `keyType` / `purpose` / `securityLevel` are DPP `repr(u8)` discriminants * (out-of-range = 255 sentinel → Rust drops the row rather than coercing to * MASTER/AUTHENTICATION, matching the Swift loader's `UInt8.max` fallback). - * `contractBoundsKind`: 0 none, 1 SingleContract, 2 SingleContractDocumentType; - * `contractBoundsId` is 32 bytes (or empty for kind 0); + * `contractBoundsKind`: 0 none, 1 SingleContract, 2 SingleContractDocumentType, + * 3 ContractGroup; + * `contractBoundsId` is 32 bytes (or empty for kind 0): the contract id, or + * the contract group id for kind 3; * `contractBoundsDocumentType` is non-null only for kind 2. * `totalBudget` / `expiresAt` are the key's usage limits (protocol version 14), * meaningful only when the matching `*IsSome` flag is set; a limited key must diff --git a/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/ffi/TransactionsNative.kt b/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/ffi/TransactionsNative.kt index 4911b70b18b..7c3b6e6c6a7 100644 --- a/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/ffi/TransactionsNative.kt +++ b/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/ffi/TransactionsNative.kt @@ -28,9 +28,9 @@ internal object TransactionsNative { * rowCount` then per row `u32 keyId, u8 keyType, u8 purpose, u8 * securityLevel, u8 readOnly, u8 contractBoundsKind, u16 pubkeyLen, * pubkey`, plus (when `contractBoundsKind != 0`) a 32-byte contract id - * and (when `== 2`) `u16 docTypeLen, docType`, then `u8 limitsFlags` - * followed by `u64 totalBudget` (bit 0) and `u64 expiresAt` (bit 1). - * May be empty. + * (the contract group id for kind 3, ContractGroup) and (when `== 2`) + * `u16 docTypeLen, docType`, then `u8 limitsFlags` followed by + * `u64 totalBudget` (bit 0) and `u64 expiresAt` (bit 1). May be empty. * @param disablePublicKeyIds key ids to disable; may be empty. At least * one of add / disable must be non-empty. */ diff --git a/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/identity/IdentityPubkeyCodec.kt b/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/identity/IdentityPubkeyCodec.kt index 4a5684fdde9..50e3b3f46d4 100644 --- a/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/identity/IdentityPubkeyCodec.kt +++ b/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/identity/IdentityPubkeyCodec.kt @@ -28,11 +28,12 @@ import java.io.DataOutputStream * u8 purpose (DPP Purpose discriminant, 0 = AUTHENTICATION) * u8 securityLevel (DPP SecurityLevel discriminant, 0 = MASTER) * u8 readOnly (0 / 1) - * u8 contractBoundsKind (0 none, 1 SingleContract, 2 SingleContractDocumentType) + * u8 contractBoundsKind (0 none, 1 SingleContract, 2 SingleContractDocumentType, + * 3 ContractGroup) * u16 pubkeyLen * u8[pubkeyLen] pubkeyBytes (compressed pubkey, or 20-byte HASH160) * if contractBoundsKind != 0: - * u8[32] contractBoundsId + * u8[32] contractBoundsId (contract id, or contract group id for kind 3) * if contractBoundsKind == 2: * u16 docTypeLen, u8[docTypeLen] docType (UTF-8) * u8 limitsFlags (bit 0: totalBudget follows, bit 1: expiresAt follows) @@ -41,6 +42,7 @@ import java.io.DataOutputStream * if limitsFlags & 2: * u64 expiresAt (block time in ms from which the key can no longer sign) * ``` + * Kind 3 carries the id only, never a document type. */ object IdentityPubkeyCodec { @@ -71,6 +73,7 @@ object IdentityPubkeyCodec { dos.writeShort(dt.size) dos.write(dt) } + is ContractBounds.ContractGroup -> dos.write(bounds.contractGroupId) } // Usage limits (protocol version 14): flags first, then only the values set. val flags = (if (k.totalBudget != null) 1 else 0) or (if (k.expiresAt != null) 2 else 0) @@ -81,10 +84,14 @@ object IdentityPubkeyCodec { return out.toByteArray() } - /** Discriminant matching the FFI: 0 none, 1 SingleContract, 2 with doc type. */ + /** + * Discriminant matching the FFI: 0 none, 1 SingleContract, 2 with doc type, + * 3 ContractGroup. + */ internal fun contractBoundsKind(bounds: ContractBounds?): Int = when (bounds) { null -> 0 is ContractBounds.SingleContract -> 1 is ContractBounds.SingleContractDocumentType -> 2 + is ContractBounds.ContractGroup -> 3 } } diff --git a/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/identity/IdentityUpdates.kt b/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/identity/IdentityUpdates.kt index 8b20689c945..154fd06dbd3 100644 --- a/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/identity/IdentityUpdates.kt +++ b/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/identity/IdentityUpdates.kt @@ -47,9 +47,11 @@ enum class SecurityLevel(val ffiValue: Int) { } /** - * Contract-bounds shape for an ENCRYPTION / DECRYPTION key — Kotlin mirror - * of Swift's `ManagedPlatformWallet.ContractBounds`. Required by Drive for - * those purposes; omitted (null) for AUTHENTICATION / TRANSFER. + * Contract bounds of an identity public key: Kotlin mirror of the rs-dpp + * `ContractBounds` enum (and of Swift's `ManagedPlatformWallet.ContractBounds`). + * Drive requires them on ENCRYPTION / DECRYPTION keys. AUTHENTICATION keys + * may carry them since protocol version 14, where they limit what the key + * can sign. Null means unbounded. */ sealed class ContractBounds { /** Bind the key to a single contract (any of its document types). */ @@ -85,6 +87,24 @@ sealed class ContractBounds { override fun hashCode(): Int = 31 * contractId.contentHashCode() + documentTypeName.hashCode() } + + /** + * Bind an AUTHENTICATION key to a contract group: the key signs only + * within the group's members. [contractGroupId] is the 32-byte contract + * group id; there is never a document type. + */ + data class ContractGroup(val contractGroupId: ByteArray) : ContractBounds() { + init { + require(contractGroupId.size == 32) { + "contractGroupId must be 32 bytes, got ${contractGroupId.size}" + } + } + + override fun equals(other: Any?): Boolean = + other is ContractGroup && contractGroupId.contentEquals(other.contractGroupId) + + override fun hashCode(): Int = contractGroupId.contentHashCode() + } } /** diff --git a/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/persistence/DashDatabase.kt b/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/persistence/DashDatabase.kt index 35b08d11f42..ef91b38416a 100644 --- a/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/persistence/DashDatabase.kt +++ b/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/persistence/DashDatabase.kt @@ -142,9 +142,22 @@ import org.dashfoundation.dashsdk.persistence.entities.WalletManagerMetadataEnti * pre-migration row reads back as an ordinary, unstamped, non-tombstone * entry, and a wallet with no recorded chainlock height has no boundary * at all (nothing collects). + * + * Version 12 (identity key usage limits, protocol version 14): adds the + * nullable `public_keys.totalBudget` and `public_keys.expiresAt` columns, + * so a key registered with a lifetime budget or an expiry restores as the + * limited key it is instead of an unlimited one. + * + * Version 13 (contract group key bounds): adds the nullable + * `public_keys.contractBoundsKind` column. The id and document type name + * alone cannot tell a ContractGroup bound (kind 3) from a SingleContract + * bound (kind 1), so a group-bound AUTHENTICATION key used to restore as + * SingleContract on the group id. The persist callback now records the + * kind the native row carries; a NULL kind (legacy row) keeps the old + * inference on restore. */ @Database( - version = 12, + version = 13, exportSchema = true, entities = [ WalletEntity::class, @@ -632,6 +645,21 @@ abstract class DashDatabase : RoomDatabase() { } } + /** + * v12 -> v13: additive nullable `public_keys.contractBoundsKind`, see + * the version-13 class doc above. NULL for every pre-existing row: + * the restore path infers a legacy row's kind as before, and the + * persist callback records the real kind on the next upsert of each + * key. + */ + val MIGRATION_12_13: Migration = object : Migration(12, 13) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + "ALTER TABLE `public_keys` ADD COLUMN `contractBoundsKind` INTEGER", + ) + } + } + /** * Build the on-disk database. WAL is Room's default journal mode on * API 16+; writes go through the persistence handler inside @@ -652,6 +680,7 @@ abstract class DashDatabase : RoomDatabase() { MIGRATION_9_10, MIGRATION_10_11, MIGRATION_11_12, + MIGRATION_12_13, ) .build() diff --git a/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/persistence/PlatformWalletPersistenceHandler.kt b/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/persistence/PlatformWalletPersistenceHandler.kt index a424d425e29..2f4ad4d12d5 100644 --- a/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/persistence/PlatformWalletPersistenceHandler.kt +++ b/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/persistence/PlatformWalletPersistenceHandler.kt @@ -1981,10 +1981,13 @@ class PlatformWalletPersistenceHandler( val identityBase58 = identityId.toBase58String() val existing = db.publicKeyDao().getByIdentityAndKeyId(identityBase58, keyId) // ContractBounds projection → the legacy JSON blob column + - // doc-type name (Swift stores `[base64(contractId)]` JSON). - val boundsData = if ((contractBoundsKind.toInt() and 0xFF) != 0) + // doc-type name (Swift stores `[base64(contractId)]` JSON), plus + // the kind itself: the blob holds the contract group id for kind + // 3, which the other two columns cannot tell from kind 1. + val boundsKind = contractBoundsKind.toInt() and 0xFF + val boundsData = if (boundsKind != 0) contractBoundsIdToJson(contractBoundsId) else null - val docTypeName = if ((contractBoundsKind.toInt() and 0xFF) == 2) + val docTypeName = if (boundsKind == 2) contractBoundsDocumentType else null val row = PublicKeyEntity( id = existing?.id ?: 0, @@ -2001,6 +2004,7 @@ class PlatformWalletPersistenceHandler( publicKeyData = publicKeyData, contractBoundsData = boundsData, contractBoundsDocumentTypeName = docTypeName, + contractBoundsKind = boundsKind, // Set to the Keystore identifier when the deriver stored the // scalar; otherwise preserve any prior identifier (idempotent // re-persist) and fall back to watch-only (null) for @@ -2857,17 +2861,35 @@ class PlatformWalletPersistenceHandler( .sortedBy { it.keyId } .map { pk -> // ContractBounds → (kind, 32-byte id, doc-type). Inverse - // of `contractBoundsIdToJson` on the persist side: - // * no blob → kind 0 (unbounded) - // * blob + docType → kind 2 (SingleContractDocumentType) - // * blob, no docType → kind 1 (SingleContract) + // of `contractBoundsIdToJson` on the persist side. The + // stored kind decides: + // * no blob → kind 0 (unbounded) + // * stored kind 3 → kind 3 (ContractGroup, no docType) + // * stored kind 2 → kind 2 with its docType; without + // one it demotes to kind 1, as the + // Rust loader does + // * stored kind 1 → kind 1 (SingleContract) + // * stored kind 0, or a kind this build does not know + // → kind 0, the same fallback the + // Swift restore path applies, + // rather than asserting a + // SingleContract bound the key + // never had + // A NULL stored kind is a legacy row (schema < 13) and + // keeps the inference those rows have always used: + // blob + docType → kind 2, blob alone → kind 1. // A blob that fails to decode to 32 bytes degrades to // kind 0 rather than crashing FFI marshalling. val boundsId = pk.contractBoundsData?.let { contractBoundsJsonToId(it) } + val hasDocType = !pk.contractBoundsDocumentTypeName.isNullOrEmpty() + val storedKind = pk.contractBoundsKind val (kind, id) = when { boundsId == null -> 0.toByte() to ByteArray(0) - pk.contractBoundsDocumentTypeName != null -> 2.toByte() to boundsId - else -> 1.toByte() to boundsId + storedKind == 3 -> 3.toByte() to boundsId + storedKind == 1 -> 1.toByte() to boundsId + storedKind == 2 || storedKind == null -> + (if (hasDocType) 2 else 1).toByte() to boundsId + else -> 0.toByte() to ByteArray(0) } IdentityKeyRestoreData( keyId = pk.keyId, diff --git a/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/persistence/entities/PublicKeyEntity.kt b/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/persistence/entities/PublicKeyEntity.kt index c4f8ddf88c1..9f8a20c35e0 100644 --- a/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/persistence/entities/PublicKeyEntity.kt +++ b/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/persistence/entities/PublicKeyEntity.kt @@ -66,6 +66,13 @@ data class PublicKeyEntity( val contractBoundsData: ByteArray? = null, /** Document-type qualifier for `.singleContractDocumentType` bounds. */ val contractBoundsDocumentTypeName: String? = null, + /** + * Contract-bounds kind as the native row carried it: 0 none, 1 + * SingleContract, 2 SingleContractDocumentType, 3 ContractGroup (then + * [contractBoundsData] holds the contract group id). Null on rows + * written before schema 13, whose kind is inferred on restore. + */ + val contractBoundsKind: Int? = null, val privateKeyKeychainIdentifier: String? = null, /** * Derivation breadcrumb (DIP-9 identity index) captured from the diff --git a/packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/identity/RegistrationKeysTest.kt b/packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/identity/RegistrationKeysTest.kt index d20ba28ae44..3d2001d4e89 100644 --- a/packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/identity/RegistrationKeysTest.kt +++ b/packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/identity/RegistrationKeysTest.kt @@ -2,6 +2,7 @@ package org.dashfoundation.dashsdk.identity import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotEquals import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Test @@ -94,4 +95,72 @@ class RegistrationKeysTest { encoded, ) } + + /** + * Kind 3 (ContractGroup) rides the wire as the kind byte plus the 32-byte + * contract group id, with no document type before the limits flags: the layout + * `pubkey_rows::parse_pubkey_rows` reads + * (`pubkey_rows::tests::round_trips_contract_group_bounds_kind`). + */ + @Test + fun `should encode contract group bounds as kind 3 with the id and no document type`() { + val contractGroupId = ByteArray(32) { (it + 0x40).toByte() } + val pubkey = fixturePubkey(7) + val encoded = IdentityPubkeyCodec.encode( + listOf( + IdentityPubkey( + keyId = 7, + keyType = KeyType.ECDSA_SECP256K1, + purpose = KeyPurpose.AUTHENTICATION, + securityLevel = SecurityLevel.HIGH, + pubkeyBytes = pubkey, + contractBounds = ContractBounds.ContractGroup(contractGroupId), + ), + ), + ) + + val expected = byteArrayOf( + 0, 0, 0, 1, // rowCount + 0, 0, 0, 7, // keyId + 0, // keyType ECDSA_SECP256K1 + 0, // purpose AUTHENTICATION + 2, // securityLevel HIGH + 0, // readOnly + 3, // contractBoundsKind ContractGroup + 0, 33, // pubkeyLen + ) + pubkey + contractGroupId + byteArrayOf( + 0, // limitsFlags: no budget, no expiry + ) + assertArrayEquals(expected, encoded) + // Only the limits flags follow the id: no docTypeLen, no docType. + assertEquals(4 + 4 + 5 + 2 + 33 + 32 + 1, encoded.size) + } + + @Test + fun `should keep the contract bounds kind bytes of the other variants`() { + val id = ByteArray(32) { 9 } + assertEquals(0, IdentityPubkeyCodec.contractBoundsKind(null)) + assertEquals(1, IdentityPubkeyCodec.contractBoundsKind(ContractBounds.SingleContract(id))) + assertEquals( + 2, + IdentityPubkeyCodec.contractBoundsKind( + ContractBounds.SingleContractDocumentType(id, "contactRequest"), + ), + ) + assertEquals(3, IdentityPubkeyCodec.contractBoundsKind(ContractBounds.ContractGroup(id))) + } + + @Test + fun `should require a 32 byte contract group id and compare by content`() { + val failure = runCatching { ContractBounds.ContractGroup(ByteArray(31)) } + assertTrue(failure.exceptionOrNull() is IllegalArgumentException) + + val a: ContractBounds = ContractBounds.ContractGroup(ByteArray(32) { 5 }) + val b: ContractBounds = ContractBounds.ContractGroup(ByteArray(32) { 5 }) + assertEquals(a, b) + assertEquals(a.hashCode(), b.hashCode()) + // Same id, different variant: a group bound is not a contract bound. + val single: ContractBounds = ContractBounds.SingleContract(ByteArray(32) { 5 }) + assertNotEquals(single, a) + } } diff --git a/packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/persistence/DashDatabaseTest.kt b/packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/persistence/DashDatabaseTest.kt index 379de0ada3d..afcdaa0eb10 100644 --- a/packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/persistence/DashDatabaseTest.kt +++ b/packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/persistence/DashDatabaseTest.kt @@ -233,13 +233,14 @@ class DashDatabaseTest { } @Test - fun schemaIsAtVersion12WithTheSweepHoldIndexes() = runTest { + fun schemaIsAtVersion13WithTheSweepHoldIndexes() = runTest { // The sweep-hold columns land in ONE migration (10 → 11), with the // two `pending_inputs` indexes the sweep's claimed-row lookup // (`spendingTxid`) and the end-of-round collector // (`walletId, isSweptTombstone, winnerMinedHeight`) rely on. - // 11 → 12 adds the identity key usage limits columns on top. - assertEquals(12, db.openHelper.readableDatabase.version) + // 11 → 12 adds the identity key usage limits columns on top, and + // 12 → 13 the contract bounds kind. + assertEquals(13, db.openHelper.readableDatabase.version) val indexes = mutableSetOf() db.openHelper.readableDatabase.query("PRAGMA index_list('pending_inputs')").use { c -> val nameColumn = c.getColumnIndexOrThrow("name") @@ -249,6 +250,27 @@ class DashDatabaseTest { assertTrue(indexes.contains("index_pending_inputs_walletId_isSweptTombstone_winnerMinedHeight")) } + @Test + fun shouldHaveANullableContractBoundsKindColumnOnPublicKeys() = runTest { + // Version 13 (12 → 13): nullable with no default, so a legacy row + // reads back NULL and the restore path infers its kind. + var found = false + db.openHelper.readableDatabase.query("PRAGMA table_info('public_keys')").use { c -> + val name = c.getColumnIndexOrThrow("name") + val type = c.getColumnIndexOrThrow("type") + val notNull = c.getColumnIndexOrThrow("notnull") + val default = c.getColumnIndexOrThrow("dflt_value") + while (c.moveToNext()) { + if (c.getString(name) != "contractBoundsKind") continue + found = true + assertEquals("INTEGER", c.getString(type)) + assertEquals(0, c.getInt(notNull)) + assertTrue(c.isNull(default)) + } + } + assertTrue(found) + } + @Test fun advanceChainLockHeightIsANarrowMonotonicMaxWrite() = runTest { db.walletDao().upsert(WalletEntity(walletId = walletId, networkRaw = 1, name = "w", syncedHeight = 7)) diff --git a/packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/persistence/PlatformWalletPersistenceHandlerTest.kt b/packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/persistence/PlatformWalletPersistenceHandlerTest.kt index 0aaacf099e1..b7b1bfd63e9 100644 --- a/packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/persistence/PlatformWalletPersistenceHandlerTest.kt +++ b/packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/persistence/PlatformWalletPersistenceHandlerTest.kt @@ -24,6 +24,7 @@ import org.dashfoundation.dashsdk.persistence.entities.TxoEntity import org.dashfoundation.dashsdk.persistence.entities.IdentityEntity import org.dashfoundation.dashsdk.persistence.entities.PendingInputEntity import org.dashfoundation.dashsdk.persistence.entities.PlatformAddressEntity +import org.dashfoundation.dashsdk.persistence.entities.PublicKeyEntity import org.dashfoundation.dashsdk.persistence.entities.WalletEntity import org.junit.After import org.junit.Assert.assertEquals @@ -4529,6 +4530,184 @@ class PlatformWalletPersistenceHandlerTest { assertEquals("contactRequest", key.contractBoundsDocumentType) } + /** A wallet, one account and one identity: what `onLoadWalletList` needs to restore keys. */ + private suspend fun seedWalletWithIdentity(identityId: ByteArray) { + handler.onPersistWalletMetadata(walletId, testnet, groupId, 0) + handler.onPersistAccountRegistration( + walletId, 0, 0, 0, 0, 0, ByteArray(0), ByteArray(0), ByteArray(78) { 30 }, + ) + seedIdentity(identityId) + } + + @Test + fun shouldRestoreContractGroupBoundKeyAsKindThree() = runTest { + // Kind 3 (ContractGroup) carries the contract group id and no document + // type. The id and docType columns alone read back as kind 1, so the + // stored kind has to survive the round trip. + val identityId = ByteArray(32) { 12 } + seedWalletWithIdentity(identityId) + val contractGroupId = ByteArray(32) { 33 } + + handler.onChangesetBegin(walletId) + handler.onPersistIdentityKeyUpsert( + walletId = walletId, + identityId = identityId, + keyId = 5, + purpose = 0, + securityLevel = 2, + keyType = 0, + readOnly = false, + disabledAtIsSome = false, + disabledAt = 0, + publicKeyData = ByteArray(33) { 7 }, + publicKeyHash = ByteArray(20), + walletIdIsSome = true, + keyWalletId = walletId, + derivationIndicesIsSome = false, + identityIndex = 0, + keyIndex = 0, + contractBoundsKind = 3, + contractBoundsId = contractGroupId, + contractBoundsDocumentType = null, + ) + handler.onChangesetEnd(walletId, success = true) + + val row = db.publicKeyDao().getByIdentityAndKeyId(identityId.toBase58String(), 5) + assertNotNull(row) + assertEquals(3, row!!.contractBoundsKind) + assertNull(row.contractBoundsDocumentTypeName) + + val key = handler.onLoadWalletList()[0].identities[0].keys.single() + assertEquals(5, key.keyId) + assertEquals(3.toByte(), key.contractBoundsKind) + assertTrue(contractGroupId.contentEquals(key.contractBoundsId)) + assertNull(key.contractBoundsDocumentType) + } + + @Test + fun shouldStoreTheContractBoundsKindTheNativeRowCarries() = runTest { + val identityId = ByteArray(32) { 12 } + seedWalletWithIdentity(identityId) + val boundsId = ByteArray(32) { 21 } + + // (keyId = kind, docType): one key per kind the native side emits. + val rows = listOf(0 to null, 1 to null, 2 to "contactRequest", 3 to null) + handler.onChangesetBegin(walletId) + for ((kind, docType) in rows) { + handler.onPersistIdentityKeyUpsert( + walletId = walletId, + identityId = identityId, + keyId = kind, + purpose = 0, + securityLevel = 2, + keyType = 0, + readOnly = false, + disabledAtIsSome = false, + disabledAt = 0, + publicKeyData = ByteArray(33) { (kind + 7).toByte() }, + publicKeyHash = ByteArray(20), + walletIdIsSome = true, + keyWalletId = walletId, + derivationIndicesIsSome = false, + identityIndex = 0, + keyIndex = 0, + contractBoundsKind = kind.toByte(), + contractBoundsId = if (kind == 0) ByteArray(32) else boundsId, + contractBoundsDocumentType = docType, + ) + } + handler.onChangesetEnd(walletId, success = true) + + for ((kind, _) in rows) { + val row = db.publicKeyDao().getByIdentityAndKeyId(identityId.toBase58String(), kind) + assertEquals(kind, row!!.contractBoundsKind) + } + val keys = handler.onLoadWalletList()[0].identities[0].keys + assertEquals(listOf(0, 1, 2, 3), keys.map { it.contractBoundsKind.toInt() }) + assertEquals(0, keys[0].contractBoundsId.size) + (1..3).forEach { assertTrue(boundsId.contentEquals(keys[it].contractBoundsId)) } + assertEquals( + listOf(null, null, "contactRequest", null), + keys.map { it.contractBoundsDocumentType }, + ) + } + + @Test + fun shouldInferContractBoundsKindForLegacyRowsWithNoStoredKind() = runTest { + // Rows written before schema 13 have a NULL kind: the restore keeps + // inferring it from the blob and the document type name. + val identityId = ByteArray(32) { 12 } + seedWalletWithIdentity(identityId) + val boundsId = ByteArray(32) { 21 } + + fun legacyRow(keyId: Int, bounds: ByteArray?, docType: String?) = PublicKeyEntity( + keyId = keyId, + purpose = "1", + securityLevel = "3", + keyType = "0", + publicKeyData = ByteArray(33) { (keyId + 7).toByte() }, + contractBoundsData = bounds?.let { contractBoundsIdToJson(it) }, + contractBoundsDocumentTypeName = docType, + contractBoundsKind = null, + identityId = identityId.toBase58String(), + identityIdData = identityId, + ) + db.publicKeyDao().insert(legacyRow(0, null, null)) + db.publicKeyDao().insert(legacyRow(1, boundsId, null)) + db.publicKeyDao().insert(legacyRow(2, boundsId, "contactRequest")) + + val keys = handler.onLoadWalletList()[0].identities[0].keys + assertEquals(listOf(0, 1, 2), keys.map { it.contractBoundsKind.toInt() }) + assertEquals(0, keys[0].contractBoundsId.size) + assertTrue(boundsId.contentEquals(keys[1].contractBoundsId)) + assertTrue(boundsId.contentEquals(keys[2].contractBoundsId)) + assertEquals( + listOf(null, null, "contactRequest"), + keys.map { it.contractBoundsDocumentType }, + ) + } + + @Test + fun shouldRestoreUnknownOrZeroStoredKindsAsUnboundedAndDemoteKindTwoWithoutADocType() = runTest { + // The stored kind decides. A kind this build does not know (a + // variant a later build persisted) or a stored 0 next to a stale + // blob restores unbounded, the fallback the Swift restore path + // applies too, instead of asserting a SingleContract bound the key + // never had. A stored kind 2 whose document type is missing or + // empty demotes to 1, as the Rust loader does. + val identityId = ByteArray(32) { 12 } + seedWalletWithIdentity(identityId) + val boundsId = ByteArray(32) { 21 } + + fun row(keyId: Int, docType: String?, kind: Int) = PublicKeyEntity( + keyId = keyId, + purpose = "0", + securityLevel = "2", + keyType = "0", + publicKeyData = ByteArray(33) { (keyId + 7).toByte() }, + contractBoundsData = contractBoundsIdToJson(boundsId), + contractBoundsDocumentTypeName = docType, + contractBoundsKind = kind, + identityId = identityId.toBase58String(), + identityIdData = identityId, + ) + db.publicKeyDao().insert(row(0, docType = null, kind = 0)) + db.publicKeyDao().insert(row(1, docType = null, kind = 4)) + db.publicKeyDao().insert(row(2, docType = "", kind = 2)) + db.publicKeyDao().insert(row(3, docType = null, kind = 2)) + db.publicKeyDao().insert(row(4, docType = "contactRequest", kind = 2)) + + val keys = handler.onLoadWalletList()[0].identities[0].keys + assertEquals(listOf(0, 0, 1, 1, 2), keys.map { it.contractBoundsKind.toInt() }) + assertEquals(0, keys[0].contractBoundsId.size) + assertEquals(0, keys[1].contractBoundsId.size) + (2..4).forEach { assertTrue(boundsId.contentEquals(keys[it].contractBoundsId)) } + assertEquals( + listOf(null, null, null, null, "contactRequest"), + keys.map { it.contractBoundsDocumentType }, + ) + } + // ── DashPay contacts: upsert metadata, ignore delta, restore ────── /** Persist one incoming contact row for [senderId] owned by [ownerId]. */ From a275896955dbed9aec22f765f6179900715ef128 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 17 Sep 2026 12:18:19 +0700 Subject: [PATCH 2/3] fix(swift-sdk): persist the contract bounds kind so group bounds round-trip Swift stored a key's bounds as an id plus an optional document type name and inferred the kind on restore, and the persist callback dropped kinds it did not know. A key bound to a contract group (kind 3) therefore restored unbounded after a restart. - PersistentPublicKey gains an optional contractBoundsKind on the live schema version 5. V5 was cut by #4811 and has shipped in no tag, so the column joins it in place, the way the DashModelContainer doc allows before a version ships, and the dash-v5 fixture store is rewritten by this build: no new version and no new frozen copies. A nil kind marks a legacy row and keeps the old inference. - The persist path stores the kind the FFI row carries, including 3; the restore path emits the stored kind (kind 3 with the id and no document type, a kind 2 row without its name demoted to 1 as Rust does, a kind this build does not know restored unbounded). - ManagedPlatformWallet.ContractBounds gains contractGroup(id:), with the pin and parse paths for kind 3. The DPP-layer bounds enum is unchanged (generated frozen schemas switch over it exhaustively), so that projection reports no bounds for a group-bound row instead of a false single contract. - The example app's storage inspector shows the bounds variant. - Tests: kind 3 round trip, kinds 0 to 2 unchanged, legacy inference, unusable rows, pin and parse of kind 3, unknown kinds still rejected, the V4 to V5 backfill of the kind column, and the V4/V5 entity-set pin. Co-Authored-By: Claude Fable 5.1 --- .../Persistence/DashModelContainer.swift | 45 ++- .../Models/PersistentPublicKey.swift | 94 ++++- .../ManagedPlatformWallet.swift | 36 +- .../PlatformWalletPersistenceHandler.swift | 85 +++-- .../Views/StorageRecordDetailViews.swift | 29 +- .../DashModelMigrationTests.swift | 100 ++++- .../Fixtures/SchemaStores/dash-v5.store | Bin 663552 -> 663552 bytes .../IdentityKeyContractBoundsTests.swift | 351 ++++++++++++++++++ 8 files changed, 662 insertions(+), 78 deletions(-) create mode 100644 packages/swift-sdk/SwiftTests/SwiftDashSDKTests/IdentityKeyContractBoundsTests.swift diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/Persistence/DashModelContainer.swift b/packages/swift-sdk/Sources/SwiftDashSDK/Persistence/DashModelContainer.swift index 8684f588bd5..53d9399b1a2 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/Persistence/DashModelContainer.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/Persistence/DashModelContainer.swift @@ -473,24 +473,37 @@ public enum DashSchemaV4: VersionedSchema { } } -/// Version 5 adds the key usage-limit columns (protocol version 14) to -/// `PersistentPublicKey`, on the same entity set as V4: -/// - `totalBudget` (optional): the credits an authentication key may take -/// from its identity over its whole lifetime, `nil` for a key registered -/// without a budget. Signed carrier for the protocol's unsigned -/// `Credits`, read through `totalBudgetCredits`. -/// - `expiresAt` (optional): the block time in milliseconds from which the -/// key can no longer sign, `nil` for a key registered without an expiry. -/// Read through `expiresAtMillis`. -/// Both are what make a key an `IdentityPublicKey::V1`; without the columns a -/// limited key would come back unlimited on cold restart and the wallet would -/// offer it for signing work consensus refuses. Existing rows migrate with -/// both `NULL`, which is exactly "a version 0 key, no limits". +/// Version 5 adds three optional columns to `PersistentPublicKey`, on the +/// same entity set as V4: +/// - `totalBudget`: the credits an authentication key may take from its +/// identity over its whole lifetime, `nil` for a key registered without +/// a budget. Signed carrier for the protocol's unsigned `Credits`, read +/// through `totalBudgetCredits`. +/// - `expiresAt`: the block time in milliseconds from which the key can +/// no longer sign, `nil` for a key registered without an expiry. Read +/// through `expiresAtMillis`. +/// - `contractBoundsKind`: the FFI `contract_bounds_kind` discriminant +/// (0 none, 1 SingleContract, 2 SingleContractDocumentType, +/// 3 ContractGroup) the key row was written with. The two columns V4 +/// had (`contractBoundsData`, `contractBoundsDocumentTypeName`) cannot +/// tell a contract-group bound apart from a whole-contract one, so +/// restoring an identity that held a group-bound AUTHENTICATION key +/// brought the key back unbounded and changed its authorization +/// metadata. `NULL` reads as "legacy row, infer the variant the way V4 +/// did" (`PersistentPublicKey.effectiveContractBoundsKind`). +/// The two limits are what make a key an `IdentityPublicKey::V1` (protocol +/// version 14); without the columns a limited key would come back unlimited +/// on cold restart and the wallet would offer it for signing work consensus +/// refuses. Existing rows migrate with all three `NULL`, which is exactly "a +/// version 0 key, no limits, bounds as V4 stored them". /// -/// Both columns are additive and optional, so a lightweight migration -/// preserves every existing row. +/// All three columns are additive and optional, so a lightweight migration +/// preserves every existing row. The bounds kind joined V5 before the +/// version shipped, in the change that rewrote the `dash-v5` fixture store +/// on purpose (see the `DashModelContainer.modelTypes` doc); a store written +/// by a V5 build from before that change matches no registered version. /// -/// Registering it required freezing every model V4 registers: the generated +/// Registering V5 required freezing every model V4 registers: the generated /// copies under `FrozenSchemas/`, see `scripts/freeze_schema_models.py`. /// V4 needed the whole graph rather than a row for `PersistentPublicKey` /// alone: see `DashModelContainer.v4ModelTypes`. diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentPublicKey.swift b/packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentPublicKey.swift index a3a62495552..a2f85fc8b23 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentPublicKey.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentPublicKey.swift @@ -31,12 +31,14 @@ public final class PersistentPublicKey { public var publicKeyData: Data // MARK: - Contract Bounds - /// JSON-encoded `[base64(contractId)]` — legacy storage shape - /// that only retains the contract id, never the document-type + /// JSON-encoded `[base64(boundId)]`, a legacy storage shape + /// that only retains the bound id, never the document-type /// name. New code paths still write here for the id portion; /// `contractBoundsDocumentTypeName` carries the doc-type so /// the `SingleContractDocumentType` variant round-trips - /// faithfully. Keeping the field shape lets old SwiftData + /// faithfully, and `contractBoundsKind` says which variant the + /// id belongs to (for kind 3 it is a contract GROUP id, not a + /// contract id). Keeping the field shape lets old SwiftData /// stores that predate the doc-type column continue to load /// without migration (the doc-type column is just `nil`). public var contractBoundsData: Data? @@ -44,11 +46,25 @@ public final class PersistentPublicKey { /// When set, the key's bounds are /// `.singleContractDocumentType(id: contractBoundsData[0], /// documentTypeName: contractBoundsDocumentTypeName)`. When - /// `nil`, the key is either unbounded (when `contractBoundsData` - /// is also nil) or bounded to a whole contract via - /// `.singleContract(id:)`. Optional so old stores load cleanly. + /// `nil`, the key is unbounded, bounded to a whole contract via + /// `.singleContract(id:)`, or bounded to a contract group via + /// `.contractGroup(id:)`; `contractBoundsKind` tells those three + /// apart. Optional so old stores load cleanly. public var contractBoundsDocumentTypeName: String? + /// The FFI `contract_bounds_kind` discriminant this row was + /// persisted with: 0 none, 1 `SingleContract`, + /// 2 `SingleContractDocumentType`, 3 `ContractGroup`. Stored + /// because the two columns above cannot tell a group bound apart + /// from a whole-contract one (both carry an id and no doc-type + /// name), so inferring the variant dropped a group bound on every + /// restart. `nil` on rows written before this column existed; + /// those fall back to the legacy inference, which never yields 3 + /// because no writer could produce a group bound back then. See + /// `effectiveContractBoundsKind`. Additive optional column, so + /// SwiftData's lightweight migration backfills `NULL` (schema V5). + public var contractBoundsKind: Int? + // MARK: - Private Key Reference (optional) public var privateKeyKeychainIdentifier: String? @@ -91,6 +107,7 @@ public final class PersistentPublicKey { contractBoundsDocumentTypeName: String? = nil, totalBudget: Int64? = nil, expiresAt: Int64? = nil, + contractBoundsKind: Int? = nil, identityId: String ) { self.keyId = keyId @@ -108,6 +125,7 @@ public final class PersistentPublicKey { self.contractBoundsData = nil } self.contractBoundsDocumentTypeName = contractBoundsDocumentTypeName + self.contractBoundsKind = contractBoundsKind self.identityId = identityId self.createdAt = Date() } @@ -130,11 +148,20 @@ public final class PersistentPublicKey { // type would leave the columns inconsistent and make // `toIdentityPublicKey()` reconstruct a stale variant. // Callers that want the full `.singleContractDocumentType` - // round-trip should write `contractBoundsDocumentTypeName` - // explicitly after this setter, or go through - // `PersistentPublicKey.from(IdentityPublicKey, identityId:)` - // which sets both columns atomically. + // round-trip must write `contractBoundsDocumentTypeName` AND + // `contractBoundsKind` (2) explicitly after this setter: the + // stored kind wins over inference on restore, so a doc-type + // written without kind 2 comes back as `.singleContract`. Or + // go through + // `PersistentPublicKey.from(IdentityPublicKey, identityId:)`, + // which sets every column atomically. + // + // The kind column resets with the ids for the same reason: a + // stale 2 or 3 alongside freshly written ids would restore a + // variant those ids never had. A bare id is a `.singleContract` + // bound (1); no id is unbounded (0). contractBoundsDocumentTypeName = nil + contractBoundsKind = newValue == nil ? 0 : 1 if let newValue = newValue { contractBoundsData = try? JSONSerialization.data(withJSONObject: newValue.map { $0.base64EncodedString() }) } else { @@ -143,6 +170,21 @@ public final class PersistentPublicKey { } } + /// The persisted `contractBoundsKind`, or the pre-column + /// inference for a legacy row: a doc-type name means + /// `SingleContractDocumentType` (2), a bare id means + /// `SingleContract` (1), neither means unbounded (0). Callers + /// still validate the id length themselves; a kind of 1, 2 or 3 + /// with an unusable id restores as unbounded. + public var effectiveContractBoundsKind: Int { + if let contractBoundsKind = contractBoundsKind { + return contractBoundsKind + } + guard contractBoundsData != nil else { return 0 } + if let name = contractBoundsDocumentTypeName, !name.isEmpty { return 2 } + return 1 + } + public var purposeEnum: KeyPurpose? { guard let purposeInt = UInt8(purpose) else { return nil } return KeyPurpose(rawValue: purposeInt) @@ -193,14 +235,13 @@ public final class PersistentPublicKey { extension PersistentPublicKey { /// Convert to IdentityPublicKey. /// - /// Reconstructs the `ContractBounds` variant from the two - /// persisted columns: when `contractBoundsDocumentTypeName` is - /// set, we hand back `.singleContractDocumentType` so the - /// document-type qualifier survives a SwiftData round-trip + /// Reconstructs the `ContractBounds` variant from the persisted + /// columns: on kind 2 we hand back `.singleContractDocumentType` + /// so the document-type qualifier survives a SwiftData round-trip /// (without this, the DashPay encryption/decryption keys' /// `contactRequest` scope would silently weaken to a whole- - /// contract `.singleContract` bound). When only the legacy - /// `contractBoundsData` is set, fall back to `.singleContract`. + /// contract `.singleContract` bound), and on kind 1 + /// `.singleContract`. Kind 3 and kind 0 both report no bounds. public func toIdentityPublicKey() -> IdentityPublicKey? { guard let purpose = purposeEnum, let securityLevel = securityLevelEnum, @@ -216,9 +257,18 @@ extension PersistentPublicKey { // row would crash on the NEXT call instead of being // rejected here. Drop the bounds projection on length // mismatch — the rest of the key is still recoverable. + // A `ContractGroup` bound (kind 3) has no variant on the + // DPP-layer `ContractBounds`, and its id is a group id, not a + // contract id, so projecting it as `.singleContract` would + // claim a bound the key does not have. Report no bounds + // instead; the row keeps the group bound for the FFI restore + // path, which does model it. Same for a kind this build does + // not know. let bounds: ContractBounds? - if let id = contractBounds?.first, id.count == 32 { - if let docTypeName = contractBoundsDocumentTypeName, !docTypeName.isEmpty { + let boundsKind = effectiveContractBoundsKind + if (1...2).contains(boundsKind), let id = contractBounds?.first, id.count == 32 { + if boundsKind == 2, + let docTypeName = contractBoundsDocumentTypeName, !docTypeName.isEmpty { bounds = .singleContractDocumentType(id: id, documentTypeName: docTypeName) } else { bounds = .singleContract(id: id) @@ -246,16 +296,23 @@ extension PersistentPublicKey { public static func from(_ publicKey: IdentityPublicKey, identityId: String) -> PersistentPublicKey? { let boundsIds: [Data]? let docTypeName: String? + // The DPP-layer enum has no `ContractGroup` variant, so this + // path only ever writes kinds 0, 1 and 2. Group-bound keys + // reach the store through the FFI persist path. + let kind: Int switch publicKey.contractBounds { case .singleContract(let id): boundsIds = [id] docTypeName = nil + kind = 1 case .singleContractDocumentType(let id, let name): boundsIds = [id] docTypeName = name + kind = 2 case .none: boundsIds = nil docTypeName = nil + kind = 0 } return PersistentPublicKey( keyId: Int32(publicKey.id), @@ -269,6 +326,7 @@ extension PersistentPublicKey { contractBoundsDocumentTypeName: docTypeName, totalBudget: publicKey.totalBudget.map { Int64(bitPattern: $0) }, expiresAt: publicKey.expiresAt.map { Int64(bitPattern: $0) }, + contractBoundsKind: kind, identityId: identityId ) } diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformWallet.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformWallet.swift index 810298d336d..84dae9131a2 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformWallet.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformWallet.swift @@ -213,7 +213,7 @@ public final class ManagedPlatformWallet: @unchecked Sendable { } /// Swift mirror of `dpp::identity::identity_public_key::contract_bounds::ContractBounds`. - /// Pinned to two variants (no `MultipleContractsOfSameOwner`) + /// Pinned to three variants (no `MultipleContractsOfSameOwner`) /// to match the Rust enum's currently-supported shape. public enum ContractBounds: Sendable, Equatable { /// Key may be used within a specific contract (any @@ -223,6 +223,10 @@ public final class ManagedPlatformWallet: @unchecked Sendable { /// specific document type. Maps to `kind == 2` on the /// FFI side. case singleContractDocumentType(id: Data, documentTypeName: String) + /// AUTHENTICATION key bound to a contract group. `id` is the + /// 32-byte contract GROUP id, and there is never a document + /// type. Maps to `kind == 3` on the FFI side. + case contractGroup(id: Data) } /// Inspectable fields of a parsed raw `IdentityUpdateTransition`. @@ -661,12 +665,11 @@ public final class ManagedPlatformWallet: @unchecked Sendable { /// `body` under one combined pinning frame. /// /// Contract-bounds pinning extends the same pattern: when the - /// row carries `.singleContract` or `.singleContractDocumentType` - /// we open a nested `withUnsafeBytes` (for the 32-byte contract - /// id) and a `withCString` (for the document type, if any) so - /// the pointers we hand the FFI stay valid for the entire - /// `body` invocation. Rows without bounds drop straight through - /// to the next level of recursion. + /// row carries any bound we open a nested `withUnsafeBytes` (for + /// the 32-byte contract or group id) and a `withCString` (for the + /// document type, if any) so the pointers we hand the FFI stay + /// valid for the entire `body` invocation. Rows without bounds + /// drop straight through to the next level of recursion. private static func pinNext( _ index: Int, _ rows: inout [IdentityPubkeyFFI], @@ -744,6 +747,15 @@ public final class ManagedPlatformWallet: @unchecked Sendable { body(2, idPtr, docTypePtr) } } + case .contractGroup(let id): + precondition( + id.count == 32, + "ContractBounds.contractGroup id must be exactly 32 bytes (got \(id.count))" + ) + return id.withUnsafeBytes { raw -> R in + let idPtr = raw.bindMemory(to: UInt8.self).baseAddress + return body(3, idPtr, nil) + } } } } @@ -3501,7 +3513,10 @@ extension ManagedPlatformWallet { ) } - private static func parsedContractBounds( + // `internal` (not `private`) so the kind-tag decode can be covered + // directly: every production caller reaches it through a live FFI + // parse, which a unit test has no handle for. + static func parsedContractBounds( from entry: ParsedIdentityUpdatePublicKeyFFI, index: Int ) throws -> ContractBounds? { @@ -3525,6 +3540,11 @@ extension ManagedPlatformWallet { id: Swift.withUnsafeBytes(of: &idTuple) { Data($0) }, documentTypeName: documentTypeName ) + case 3: + var idTuple = entry.contract_bounds_id + return .contractGroup( + id: Swift.withUnsafeBytes(of: &idTuple) { Data($0) } + ) default: throw PlatformWalletError.deserialization( "Unknown IdentityUpdateTransition contract-bounds kind \(entry.contract_bounds_kind) at key \(index)" diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletPersistenceHandler.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletPersistenceHandler.swift index 5d3caaa5e6f..304c197df47 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletPersistenceHandler.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletPersistenceHandler.swift @@ -3939,26 +3939,38 @@ public final class PlatformWalletPersistenceHandler: @unchecked Sendable { } ) // Project the snapshot's ContractBounds enum into the - // pair of columns `PersistentPublicKey` uses: - // * `contractBoundsIds` — `[contractId]` (or nil) - // * `contractBoundsDocumentTypeName` — non-nil iff the + // three columns `PersistentPublicKey` uses: + // * `contractBoundsIds`: `[boundId]` (or nil) + // * `contractBoundsDocumentTypeName`: non-nil iff the // bound was `.singleContractDocumentType` - // Keeping both lets the SwiftData row round-trip both - // variants verbatim; legacy stores without the - // doc-type column just see `nil` for the second field - // and reconstruct as `.singleContract`. + // * `contractBoundsKind`: the FFI discriminant + // Keeping all three lets the SwiftData row round-trip + // every variant verbatim. The kind is what separates + // `.contractGroup` from `.singleContract`: both carry a + // bare id, so a store that only had the first two columns + // restored a group bound as unbounded. Legacy rows + // written before the column exists leave it `nil` and + // keep the old inference. let snapshotBoundsIds: [Data]? let snapshotBoundsDocType: String? + let snapshotBoundsKind: Int switch entry.contractBounds { case .some(.singleContract(let id)): snapshotBoundsIds = [id] snapshotBoundsDocType = nil + snapshotBoundsKind = 1 case .some(.singleContractDocumentType(let id, let name)): snapshotBoundsIds = [id] snapshotBoundsDocType = name + snapshotBoundsKind = 2 + case .some(.contractGroup(let id)): + snapshotBoundsIds = [id] + snapshotBoundsDocType = nil + snapshotBoundsKind = 3 case .none: snapshotBoundsIds = nil snapshotBoundsDocType = nil + snapshotBoundsKind = 0 } let row: PersistentPublicKey @@ -3978,6 +3990,7 @@ public final class PlatformWalletPersistenceHandler: @unchecked Sendable { disabledAt: entry.disabledAt.map { Int64(bitPattern: $0) }, contractBounds: snapshotBoundsIds, contractBoundsDocumentTypeName: snapshotBoundsDocType, + contractBoundsKind: snapshotBoundsKind, identityId: identityHex ) backgroundContext.insert(row) @@ -4005,6 +4018,7 @@ public final class PlatformWalletPersistenceHandler: @unchecked Sendable { // scope) must overwrite any stale value here. row.contractBounds = snapshotBoundsIds row.contractBoundsDocumentTypeName = snapshotBoundsDocType + row.contractBoundsKind = snapshotBoundsKind // Usage limits (protocol version 14). Rust is the source of // truth on every callback, and a key limits update raises a @@ -5026,11 +5040,13 @@ public final class PlatformWalletPersistenceHandler: @unchecked Sendable { let derivationIndices: (identityIndex: UInt32, keyIndex: UInt32)? /// Full ContractBounds projection mirrored from Rust: /// `nil` when the key has no bounds; `.singleContract` for - /// kind=1; `.singleContractDocumentType` for kind=2. Carried - /// so the SwiftData row preserves the doc-type name on - /// round-trip (would otherwise be silently downgraded to - /// `.singleContract` and break local DPP projections that - /// read `identity.identityPublicKeys`). + /// kind=1; `.singleContractDocumentType` for kind=2; + /// `.contractGroup` for kind=3. Carried so the SwiftData row + /// preserves the doc-type name on round-trip (would + /// otherwise be silently downgraded to `.singleContract` and + /// break local DPP projections that read + /// `identity.identityPublicKeys`) and so a group bound is not + /// mistaken for a whole-contract one. let contractBounds: ManagedPlatformWallet.ContractBounds? } @@ -8118,30 +8134,45 @@ public final class PlatformWalletPersistenceHandler: @unchecked Sendable { // Mirror the contract-bounds projection into // the restore row so scoped keys (DashPay's - // SingleContractDocumentType, in particular) - // come back with their full variant on cold - // restart instead of silently degrading to - // unbounded. Encoding matches - // `IdentityKeyEntryFFI` on the persist side: + // SingleContractDocumentType, and AUTHENTICATION + // keys bound to a contract group) come back with + // their full variant on cold restart instead of + // silently degrading to unbounded. Encoding + // matches `IdentityKeyEntryFFI` on the persist + // side: // * kind=0 → no bounds; id zeroed, doc-type null // * kind=1 → SingleContract; id meaningful // * kind=2 → SingleContractDocumentType; id + // doc-type both meaningful - // Length-validated by `pk.publicKeyData.count - // == 32` (matches the gating in - // `toIdentityPublicKey()`); a row with a - // wrong-length id falls back to "no bounds" - // rather than crashing FFI marshalling on the - // Rust side. - if let id = pk.contractBounds?.first, id.count == 32 { + // * kind=3 → ContractGroup; id meaningful (a + // group id), doc-type null + // The kind comes off the row when it was stored + // and from the legacy inference when it was not + // (`effectiveContractBoundsKind`). A kind=2 row + // whose doc-type went missing demotes to kind=1, + // the same demotion Rust performs, rather than + // handing the decoder a null doc-type for a + // variant that needs one. Id length is validated + // here; a row with a wrong-length id, or a kind + // this build does not know, falls back to "no + // bounds" rather than crashing FFI marshalling on + // the Rust side or asserting a bound we cannot + // describe. + let boundsKind = pk.effectiveContractBoundsKind + if (1...3).contains(boundsKind), + let id = pk.contractBounds?.first, id.count == 32 { withUnsafeMutableBytes(of: &row.contract_bounds_id) { dst in id.copyBytes(to: dst.bindMemory(to: UInt8.self).baseAddress!, count: 32) } - if let docType = pk.contractBoundsDocumentTypeName, !docType.isEmpty { + let docType = pk.contractBoundsDocumentTypeName + if boundsKind == 2, let docType = docType, !docType.isEmpty { row.contract_bounds_kind = 2 row.contract_bounds_document_type = UnsafePointer( duplicateCString(docType, allocation: allocation) ) + } else if boundsKind == 3 { + row.contract_bounds_kind = 3 + row.contract_bounds_document_type = nil } else { row.contract_bounds_kind = 1 row.contract_bounds_document_type = nil @@ -9840,6 +9871,8 @@ private func persistIdentityKeysCallback( // 0 → no bounds // 1 → SingleContract { id } // 2 → SingleContractDocumentType { id, doc_type_name } + // 3 → ContractGroup { id } (id is a group id, doc-type + // pointer is always null) // The doc-type C-string for kind=2 is owned by Rust and // freed via `free_identity_key_entry_ffi` after this // callback returns, so we copy it into a Swift String @@ -9859,6 +9892,8 @@ private func persistIdentityKeysCallback( } else { bounds = nil } + case 3: + bounds = .contractGroup(id: dataFromTuple32(e.contract_bounds_id)) default: bounds = nil } diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/StorageRecordDetailViews.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/StorageRecordDetailViews.swift index 2d98cb49e5f..c555d48d989 100644 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/StorageRecordDetailViews.swift +++ b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/StorageRecordDetailViews.swift @@ -766,9 +766,12 @@ struct PublicKeyStorageDetailView: View { Section("Data") { FieldRow(label: "Public Key", value: hexString(record.publicKeyData)) if let bounds = record.contractBounds, !bounds.isEmpty { - FieldRow(label: "Contract Bounds", value: "\(bounds.count)") - ForEach(Array(bounds.enumerated()), id: \.offset) { _, contractId in - FieldRow(label: "Contract", value: contractId.toBase58String()) + FieldRow(label: "Contract Bounds", value: contractBoundsKindDisplay) + ForEach(Array(bounds.enumerated()), id: \.offset) { _, boundId in + FieldRow(label: boundIdLabel, value: boundId.toBase58String()) + } + if let docType = record.contractBoundsDocumentTypeName, !docType.isEmpty { + FieldRow(label: "Document Type", value: docType) } } else { FieldRow(label: "Contract Bounds", value: "None") @@ -814,6 +817,26 @@ struct PublicKeyStorageDetailView: View { if let t = record.keyTypeEnum { return "\(t.name) (\(record.keyType))" } return record.keyType } + + /// The bounds variant the row restores as. Reads the stored kind + /// when there is one and the legacy inference otherwise, so a row + /// written before the kind column shows what it will actually + /// restore as, not what it was meant to be. + private var contractBoundsKindDisplay: String { + let kind = record.effectiveContractBoundsKind + let name: String + switch kind { + case 1: name = "Single contract" + case 2: name = "Single contract document type" + case 3: name = "Contract group" + default: name = "Unknown" + } + return record.contractBoundsKind == nil ? "\(name) (\(kind), inferred)" : "\(name) (\(kind))" + } + + private var boundIdLabel: String { + record.effectiveContractBoundsKind == 3 ? "Contract Group" : "Contract" + } } // MARK: - PersistentToken diff --git a/packages/swift-sdk/SwiftTests/SwiftDashSDKTests/DashModelMigrationTests.swift b/packages/swift-sdk/SwiftTests/SwiftDashSDKTests/DashModelMigrationTests.swift index 46e83b7f3fc..2c2708d4f81 100644 --- a/packages/swift-sdk/SwiftTests/SwiftDashSDKTests/DashModelMigrationTests.swift +++ b/packages/swift-sdk/SwiftTests/SwiftDashSDKTests/DashModelMigrationTests.swift @@ -15,8 +15,9 @@ import XCTest /// the persistence sources as of commit 5f58417079 — the last state before /// V4, the state the frozen copies under `FrozenSchemas/` are generated /// from — through that build's own `DashSchemaV1` / `DashSchemaV2` / -/// `DashSchemaV3`, and `dash-v4` / `dash-v5` by the builds that registered -/// V4 and V5, through +/// `DashSchemaV3`, `dash-v4` by the build that registered V4, and `dash-v5` +/// by the build that last changed V5 before it shipped (the bounds kind +/// column), all through /// `DashModelContainer.create`. They pin the frozen copies as the pre-V4 /// build defined them, not what the original V1 release wrote (see the /// `DashSchemaV1` doc for why those stores are expected to fail open and @@ -759,11 +760,11 @@ final class DashModelMigrationTests: XCTestCase { "the coin's relationship to its funding transaction survives three stages") } - /// The stage this change adds: a V4 store must migrate to V5 and read - /// back with the key usage-limit columns backfilled to "no limits", - /// which is exactly what a version 0 key is. V4 registers a frozen - /// graph, so the row goes in as V4's copy and comes out as the live - /// one: the same entity, two properties wider. + /// The V4 -> V5 stage: a V4 store must migrate to V5 and read back with + /// the key usage-limit columns backfilled to "no limits", which is + /// exactly what a version 0 key is. V4 registers a frozen graph, so the + /// row goes in as V4's copy and comes out as the live one: the same + /// entity, three properties wider. @MainActor func testV4StoreMigratesToV5AndBackfillsTheKeyLimitColumns() throws { let directory = FileManager.default.temporaryDirectory @@ -829,9 +830,88 @@ final class DashModelMigrationTests: XCTestCase { XCTAssertTrue(reread.hasLimits) } + /// The bounds half of the same stage: a V4 store's key rows arrive with + /// `contractBoundsKind` backfilled to NULL, which the model reads as + /// "legacy row, infer the variant the way V4 did", and the column is + /// writable on the migrated row. + @MainActor + func testV4StoreMigratesToV5AndBackfillsTheContractBoundsKind() throws { + let directory = FileManager.default.temporaryDirectory + .appendingPathComponent(UUID().uuidString, isDirectory: true) + try FileManager.default.createDirectory( + at: directory, withIntermediateDirectories: true) + defer { try? FileManager.default.removeItem(at: directory) } + let storeURL = directory.appendingPathComponent("dash.store") + + let contractId = Data(repeating: 0x7C, count: 32) + + let v4Schema = Schema(versionedSchema: DashSchemaV4.self) + let v4Configuration = ModelConfiguration( + "DashContractBoundsKindMigrationTest", + schema: v4Schema, + url: storeURL, + allowsSave: true, + cloudKitDatabase: .none) + var v4Container: ModelContainer? = try ModelContainer( + for: v4Schema, + configurations: [v4Configuration]) + v4Container?.mainContext.insert(DashSchemaV4.PersistentPublicKey( + keyId: 3, + purpose: .authentication, + securityLevel: .high, + keyType: .ecdsaSecp256k1, + publicKeyData: Data(repeating: 0x02, count: 33), + contractBounds: [contractId], + contractBoundsDocumentTypeName: "contactRequest", + identityId: "legacyDocTypeKey")) + v4Container?.mainContext.insert(DashSchemaV4.PersistentPublicKey( + keyId: 4, + purpose: .authentication, + securityLevel: .high, + keyType: .ecdsaSecp256k1, + publicKeyData: Data(repeating: 0x03, count: 33), + contractBounds: [contractId], + identityId: "legacyContractKey")) + try v4Container?.mainContext.save() + v4Container = nil + + let v5Schema = Schema(versionedSchema: DashSchemaV5.self) + let v5Configuration = ModelConfiguration( + "DashContractBoundsKindMigrationTest", + schema: v5Schema, + url: storeURL, + allowsSave: true, + cloudKitDatabase: .none) + let migrated = try ModelContainer( + for: v5Schema, + migrationPlan: DashMigrationPlan.self, + configurations: [v5Configuration]) + + let rows = try migrated.mainContext.fetch( + FetchDescriptor(sortBy: [SortDescriptor(\.keyId)])) + XCTAssertEqual(rows.map(\.keyId), [3, 4], "both V4 rows survive the migration") + XCTAssertEqual( + rows.map(\.contractBoundsKind), [nil, nil], + "a row written before the column backfills NULL") + XCTAssertEqual(rows[0].contractBounds?.first, contractId) + XCTAssertEqual( + rows[0].effectiveContractBoundsKind, 2, + "a legacy row with a doc-type name still infers SingleContractDocumentType") + XCTAssertEqual( + rows[1].effectiveContractBoundsKind, 1, + "a legacy row with a bare id still infers SingleContract") + + // And the new column is writable on the migrated row. + rows[1].contractBoundsKind = 3 + try migrated.mainContext.save() + let reread = try migrated.mainContext.fetch( + FetchDescriptor(sortBy: [SortDescriptor(\.keyId)])) + XCTAssertEqual(reread[1].effectiveContractBoundsKind, 3) + } + /// What makes the V4 -> V5 stage lightweight: the two versions name the /// same entity set, and V5 only widens `PersistentPublicKey`. Also pins - /// that V4's frozen copy does NOT carry the two columns: one that came + /// that V4's frozen copy does NOT carry the three columns: one that came /// back would silently change V4's checksum and strand every store the /// V4 build wrote. func testV4AndV5NameTheSameEntitySet() throws { @@ -844,10 +924,14 @@ final class DashModelMigrationTests: XCTestCase { let key = try XCTUnwrap(v5.entities.first { $0.name == "PersistentPublicKey" }) XCTAssertNotNil(key.attributesByName["totalBudget"]) XCTAssertNotNil(key.attributesByName["expiresAt"]) + XCTAssertNotNil(key.attributesByName["contractBoundsKind"]) let frozenKey = try XCTUnwrap(v4.entities.first { $0.name == "PersistentPublicKey" }) XCTAssertNil(frozenKey.attributesByName["totalBudget"]) XCTAssertNil(frozenKey.attributesByName["expiresAt"]) + XCTAssertNil( + frozenKey.attributesByName["contractBoundsKind"], + "V4's frozen copy predates the column") // V5 widens that entity and nothing else. for name in v5.entities.map(\.name) where name != "PersistentPublicKey" { diff --git a/packages/swift-sdk/SwiftTests/SwiftDashSDKTests/Fixtures/SchemaStores/dash-v5.store b/packages/swift-sdk/SwiftTests/SwiftDashSDKTests/Fixtures/SchemaStores/dash-v5.store index 55040471f23ac31ffa65cb4210716c18b061215f..05766e4bce881df729b2da925d5057c676e0ab58 100644 GIT binary patch delta 52722 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

Su1-ca7W8a!)ZcJBd8Q>Jlj{zr*~@R9Ie8u= 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%0DN`jvr?( z3}TJ?oS4h(a?%0{hso@e9}MB@EG(j}rfY9- zf{U9E`Pofdiu5+l0nn4Ws;0Vy!E>u`bVH&7y;W58_~AqIXyZ(kLGh|{yL>lKSNJPy z;|U#gw^z$XKSnFi6>pATb?OGUr8Lq9)h=p4G1}^*Mvh+}$I}OW?P;U`YlJinbNL}; z7UmSi!7twQn3CSK9Nn_WQTem<{l)9TNs3$}$A-rc`b;wvkpG_D0JTlA`x0vr=BqyX z64cr=NiXqot6J0K;6XB{-5$jKkLKCogj;a&re;=1@w#SS5FW}w#fh4MAju_&9LM2V zd6%Z#nWMhauLbUNj*eq=BA-7Z6%o8|rmwhnzc+7dg)a|W=bw+k*p~9)LcFY$_EzYE zRrZ$YgaDP(4nETm@!izfmAJh8IbnUU$>IF-M$IpQ&{o4Q3l)n+8_MPT_P^aK_RT(9 zesPQVUnQMuRFVlA$5m2R%Q`ism73GIS?QQ=rnH)e+?w1hpiwT98a8Jt6EB#VDI#xb z_Wp%bj_Fx|15}!r-Vq%yBk@CBYmi4TRsB1iQ;xhuy3b2vYQp2jN z8MspVHA`L7r|~I9|7bKKUuy#aKlsa63EAq!p&a^L0*P{af#;#!AW3M+{XJUJpgG+t zNjE(VemtHb`8dL>AX#NPcorG&-`_^pRJ&O*!4;z6$*KHSY3DT!nC8+}Be$H77S& zK!>brkT!6(v?o5>_!lzqiF2i&fe9A%eZUFM;K@eR0cGurT4m0s=)Ok-FsNzh(~(yVVi`7$f_Gu4g)5sduK5oks3KIZezF=$(E`&bW+KB3hlFlkllkD57lJcDx2um>z-!7I%V^80&K zXdp7T%)kRBzMsf9B@v&%?bYpE3bN%%NyYs7w_lj-9MGuWgG1EqR6rkj4P$^PuA&@@HZ7wRsA+l@YaGNwz$`bNVuk=Li*nJ!;k0QI+zn!OBF}v@;gyHW8ypBB`V<^|DCdyQvKVp{&dDe z3Dr{Di3r3~H3LSJ9F9wG*a7SR>CRp10>Ib-wzMZKh?A;K0@nkhel@XuO*Nki)+CLnU{l{0L zM`=ql<4Du>mWyKJ2aUJ`g<-A0iw!rBh)c^JJdBO$oA)xWfxeEMO%)ckm#uJW9WaOFv#Q4r%ob|3uai3 VfqNk5Y%ulr9@+_b1C9v9{{caP9&7*r delta 52666 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 diff --git a/packages/swift-sdk/SwiftTests/SwiftDashSDKTests/IdentityKeyContractBoundsTests.swift b/packages/swift-sdk/SwiftTests/SwiftDashSDKTests/IdentityKeyContractBoundsTests.swift new file mode 100644 index 00000000000..e90b3acbc94 --- /dev/null +++ b/packages/swift-sdk/SwiftTests/SwiftDashSDKTests/IdentityKeyContractBoundsTests.swift @@ -0,0 +1,351 @@ +import XCTest +import SwiftData +import DashSDKFFI +@testable import SwiftDashSDK + +/// Coverage for the `ContractBounds` variant surviving a persist -> +/// restart -> restore round trip. +/// +/// The FFI carries the bound as a `(kind, id, document_type)` trio: +/// 0 none, 1 SingleContract, 2 SingleContractDocumentType, 3 +/// ContractGroup. Kinds 1 and 3 look identical in the two columns the +/// SwiftData row used to have (an id, no document-type name), so the +/// variant used to be INFERRED on the way back out and a group-bound +/// AUTHENTICATION key restored unbounded, quietly changing its +/// authorization metadata. `PersistentPublicKey.contractBoundsKind` +/// records the kind instead; a row written before that column reads +/// `nil` and keeps the old inference, which is what these tests pin. +@MainActor +final class IdentityKeyContractBoundsTests: XCTestCase { + + private let walletId = Data(repeating: 0x07, count: 32) + private let identityId = Data(repeating: 0x35, count: 32) + private let boundId = Data(repeating: 0x6B, count: 32) + private let publicKeyData = Data(repeating: 0x02, count: 33) + + private func makeHandler() throws -> (PlatformWalletPersistenceHandler, ModelContainer) { + let container = try DashModelContainer.createInMemory() + let handler = PlatformWalletPersistenceHandler( + modelContainer: container, network: .testnet) + return (handler, container) + } + + /// A wallet the restore path will actually emit: it needs one + /// account carrying an xpub, which is what Rust rebuilds the + /// watch-only wallet from. + private func seedWallet(in container: ModelContainer) throws { + let context = ModelContext(container) + let wallet = PersistentWallet(walletId: walletId, network: .testnet) + context.insert(wallet) + let account = PersistentAccount( + wallet: wallet, accountType: 0, accountIndex: 0, accountTypeName: "Standard") + account.accountExtendedPubKeyBytes = Data(repeating: 0x30, count: 78) + context.insert(account) + let identity = PersistentIdentity(identityId: identityId, network: .testnet) + identity.wallet = wallet + context.insert(identity) + try context.save() + } + + /// Attach one key row to the seeded identity, writing the columns + /// directly so a legacy shape (no stored kind) can be modelled. + private func insertKeyRow( + in container: ModelContainer, + keyId: Int32, + boundIds: [Data]?, + documentTypeName: String?, + kind: Int? + ) throws { + let context = ModelContext(container) + let identity = try XCTUnwrap( + try context.fetch(FetchDescriptor()).first) + let row = PersistentPublicKey( + keyId: keyId, + purpose: .authentication, + securityLevel: .high, + keyType: .ecdsaSecp256k1, + publicKeyData: publicKeyData, + contractBounds: boundIds, + contractBoundsDocumentTypeName: documentTypeName, + contractBoundsKind: kind, + identityId: identityId.toBase58String()) + context.insert(row) + row.identity = identity + identity.addPublicKey(row) + try context.save() + } + + /// One `IdentityKeyEntrySnapshot`, the shape the Rust persist + /// callback hands over. + private func snapshot( + keyId: UInt32, + bounds: ManagedPlatformWallet.ContractBounds? + ) -> PlatformWalletPersistenceHandler.IdentityKeyEntrySnapshot { + .init( + identityId: identityId, + keyId: keyId, + purpose: KeyPurpose.authentication.rawValue, + securityLevel: SecurityLevel.high.rawValue, + keyType: KeyType.ecdsaSecp256k1.rawValue, + readOnly: false, + disabledAt: nil, + totalBudget: nil, + expiresAt: nil, + publicKeyData: publicKeyData, + publicKeyHash: Data(repeating: 0x09, count: 20), + walletId: nil, + derivationIndices: nil, + contractBounds: bounds) + } + + /// Run one persist round the way the Rust store() round does: + /// `persistIdentityKeys` stages rows and leaves the save to + /// `endChangeset`, so an unbracketed call would never reach the + /// store. + private func persist( + _ handler: PlatformWalletPersistenceHandler, + _ upserts: [PlatformWalletPersistenceHandler.IdentityKeyEntrySnapshot] + ) { + handler.beginChangeset(walletId: walletId) + handler.persistIdentityKeys(walletId: walletId, upserts: upserts, removed: []) + XCTAssertTrue(handler.endChangeset(walletId: walletId, success: true)) + } + + /// The restored `(kind, id, documentType)` trio for every key the + /// load path emits, read out of the real FFI buffer. + private func restoredBounds( + _ handler: PlatformWalletPersistenceHandler + ) throws -> [(kind: UInt8, id: Data, documentType: String?)] { + let loaded = handler.loadWalletList() + XCTAssertFalse(loaded.errored, "the load must not fail") + let entries = try XCTUnwrap(loaded.entries, "the seeded wallet must be restorable") + defer { handler.loadWalletListFree(entries: UnsafeRawPointer(entries)) } + XCTAssertEqual(loaded.count, 1) + + var out: [(kind: UInt8, id: Data, documentType: String?)] = [] + let identities = try XCTUnwrap(entries[0].identities) + for i in 0..()).first) + XCTAssertEqual(row.contractBoundsKind, 3, "the group bound is stored as kind 3") + XCTAssertEqual(row.contractBounds?.first, boundId) + XCTAssertNil(row.contractBoundsDocumentTypeName, "a group bound has no document type") + + let restored = try restoredBounds(handler) + XCTAssertEqual(restored.map(\.kind), [3]) + XCTAssertEqual(restored.first?.id, boundId) + XCTAssertNil(restored.first?.documentType) + } + + /// The two variants that already round-tripped keep doing so, and + /// their stored kinds match what the FFI handed over. + func testSingleContractAndDocumentTypeBoundsStillRoundTrip() throws { + let (handler, container) = try makeHandler() + try seedWallet(in: container) + + persist(handler, [ + snapshot(keyId: 1, bounds: .singleContract(id: boundId)), + snapshot( + keyId: 2, + bounds: .singleContractDocumentType( + id: boundId, documentTypeName: "contactRequest")), + snapshot(keyId: 3, bounds: nil) + ]) + + let context = ModelContext(container) + let rows = try context.fetch( + FetchDescriptor(sortBy: [SortDescriptor(\.keyId)])) + XCTAssertEqual(rows.map(\.contractBoundsKind), [1, 2, 0]) + + let restored = try restoredBounds(handler) + XCTAssertEqual(restored.map(\.kind), [1, 2, 0]) + XCTAssertEqual(restored[1].documentType, "contactRequest") + XCTAssertNil(restored[0].documentType) + XCTAssertEqual(restored[2].id, Data(repeating: 0, count: 32), "kind 0 zeroes the id") + } + + /// A row written before the kind column keeps the inference it + /// restored with: a document-type name means kind 2, a bare id + /// means kind 1, neither means kind 0. + func testLegacyRowsWithoutAStoredKindKeepTheOldInference() throws { + let (handler, container) = try makeHandler() + try seedWallet(in: container) + try insertKeyRow( + in: container, keyId: 1, boundIds: [boundId], documentTypeName: nil, kind: nil) + try insertKeyRow( + in: container, keyId: 2, boundIds: [boundId], + documentTypeName: "contactRequest", kind: nil) + try insertKeyRow( + in: container, keyId: 3, boundIds: nil, documentTypeName: nil, kind: nil) + + let context = ModelContext(container) + let rows = try context.fetch( + FetchDescriptor(sortBy: [SortDescriptor(\.keyId)])) + XCTAssertEqual(rows.map(\.contractBoundsKind), [nil, nil, nil]) + XCTAssertEqual(rows.map(\.effectiveContractBoundsKind), [1, 2, 0]) + + let restored = try restoredBounds(handler) + XCTAssertEqual(restored.map(\.kind), [1, 2, 0]) + XCTAssertEqual(restored[1].documentType, "contactRequest") + } + + /// Rows the restore path must not take at face value: a kind this + /// build does not know, a kind 2 whose document type went missing, + /// and a bound id that is not 32 bytes. + func testUnusableRowsRestoreAsUnboundedOrDemote() throws { + let (handler, container) = try makeHandler() + try seedWallet(in: container) + try insertKeyRow( + in: container, keyId: 1, boundIds: [boundId], documentTypeName: nil, kind: 4) + try insertKeyRow( + in: container, keyId: 2, boundIds: [boundId], documentTypeName: nil, kind: 2) + try insertKeyRow( + in: container, keyId: 3, boundIds: [Data(repeating: 0x6B, count: 31)], + documentTypeName: nil, kind: 3) + + let restored = try restoredBounds(handler) + XCTAssertEqual( + restored.map(\.kind), [0, 1, 0], + "an unknown kind and a short id restore unbounded; a kind 2 with no document " + + "type demotes to kind 1, the same demotion Rust performs") + XCTAssertNil(restored[1].documentType) + } + + // MARK: - Model projections + + /// The stored kind wins over the inference, which is the whole + /// point of the column: kind 3 and kind 1 are indistinguishable + /// from the id and document-type columns alone. + func testEffectiveKindPrefersTheStoredValue() throws { + let row = PersistentPublicKey( + keyId: 1, + purpose: .authentication, + securityLevel: .high, + keyType: .ecdsaSecp256k1, + publicKeyData: publicKeyData, + contractBounds: [boundId], + contractBoundsKind: 3, + identityId: identityId.toBase58String()) + XCTAssertEqual(row.effectiveContractBoundsKind, 3) + + // Rewriting the ids through the setter resets the kind with + // them, so a stale 3 cannot outlive the id it described. + row.contractBounds = [Data(repeating: 0x11, count: 32)] + XCTAssertEqual(row.contractBoundsKind, 1) + row.contractBounds = nil + XCTAssertEqual(row.contractBoundsKind, 0) + } + + /// The DPP-layer `ContractBounds` has no group variant and a group + /// id is not a contract id, so a kind 3 row reports no bounds + /// there rather than claiming a whole-contract bound. + func testGroupBoundRowProjectsNoDPPContractBounds() throws { + let row = PersistentPublicKey( + keyId: 1, + purpose: .authentication, + securityLevel: .high, + keyType: .ecdsaSecp256k1, + publicKeyData: publicKeyData, + contractBounds: [boundId], + contractBoundsKind: 3, + identityId: identityId.toBase58String()) + XCTAssertNil(try XCTUnwrap(row.toIdentityPublicKey()).contractBounds) + + row.contractBoundsKind = 1 + let projected = try XCTUnwrap(row.toIdentityPublicKey()).contractBounds + XCTAssertEqual(projected, ContractBounds.singleContract(id: boundId)) + } + + // MARK: - FFI marshalling + + /// The outbound half: pinning a group bound emits kind 3, the + /// group id, and a null document-type pointer. + func testPinnedGroupBoundEmitsKindThreeWithNoDocumentType() throws { + let pubkey = ManagedPlatformWallet.IdentityPubkey( + keyId: 4, + keyType: .ecdsaSecp256k1, + purpose: .authentication, + securityLevel: .high, + pubkeyBytes: publicKeyData, + contractBounds: .contractGroup(id: boundId)) + + let observed: (kind: UInt8, id: Data, hasDocumentType: Bool) = + ManagedPlatformWallet.withPubkeyFFIArray( + [pubkey], buffers: [publicKeyData] + ) { rows, count in + guard let rows = rows, count == 1 else { + return (kind: UInt8.max, id: Data(), hasDocumentType: false) + } + let id = rows[0].contract_bounds_id.map { Data(bytes: $0, count: 32) } ?? Data() + return ( + kind: rows[0].contract_bounds_kind, + id: id, + hasDocumentType: rows[0].contract_bounds_document_type != nil + ) + } + + XCTAssertEqual(observed.kind, 3) + XCTAssertEqual(observed.id, boundId) + XCTAssertFalse(observed.hasDocumentType) + } + + /// The inbound half: an `IdentityUpdateTransition` key row tagged + /// kind 3 parses back into `.contractGroup`, and a kind this build + /// does not know is still rejected rather than guessed at. + func testParsedContractBoundsDecodesKindThreeAndRejectsUnknownKinds() throws { + func entry(kind: UInt8) -> ParsedIdentityUpdatePublicKeyFFI { + var entry = ParsedIdentityUpdatePublicKeyFFI() + entry.contract_bounds_kind = kind + withUnsafeMutableBytes(of: &entry.contract_bounds_id) { dst in + boundId.copyBytes(to: dst.bindMemory(to: UInt8.self).baseAddress!, count: 32) + } + entry.contract_bounds_document_type = nil + return entry + } + + let group = try ManagedPlatformWallet.parsedContractBounds( + from: entry(kind: 3), index: 0) + XCTAssertEqual(group, ManagedPlatformWallet.ContractBounds.contractGroup(id: boundId)) + + let contract = try ManagedPlatformWallet.parsedContractBounds( + from: entry(kind: 1), index: 0) + XCTAssertEqual(contract, ManagedPlatformWallet.ContractBounds.singleContract(id: boundId)) + + XCTAssertNil( + try ManagedPlatformWallet.parsedContractBounds(from: entry(kind: 0), index: 0)) + XCTAssertThrowsError( + try ManagedPlatformWallet.parsedContractBounds(from: entry(kind: 4), index: 0), + "an unknown kind must not decode as some neighbouring variant") + XCTAssertThrowsError( + try ManagedPlatformWallet.parsedContractBounds(from: entry(kind: 2), index: 0), + "kind 2 still needs a document type") + } +} From 0d969ab56d3831551a8a22248fcdbf8b47530a65 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 18 Sep 2026 22:25:42 +0700 Subject: [PATCH 3/3] fix(swift-sdk): show the stored bounds kind even when the bound id is missing The storage inspector only rendered the kind row when the id blob decoded, so a row persisted with kind 3, or a kind this build does not know, showed "None" and hid the discriminator it will restore with. The kind row now always shows, with kind 0 named "None"; the id and document type rows keep their own condition. Co-Authored-By: Claude Fable 5.1 --- .../Views/StorageRecordDetailViews.swift | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/StorageRecordDetailViews.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/StorageRecordDetailViews.swift index c555d48d989..5a1007a6391 100644 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/StorageRecordDetailViews.swift +++ b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/StorageRecordDetailViews.swift @@ -765,16 +765,18 @@ struct PublicKeyStorageDetailView: View { } Section("Data") { FieldRow(label: "Public Key", value: hexString(record.publicKeyData)) + // The kind row always shows, so a row whose stored kind + // says "bound" but whose id blob is missing or unreadable + // still surfaces the persisted discriminator instead of a + // bare "None". + FieldRow(label: "Contract Bounds", value: contractBoundsKindDisplay) if let bounds = record.contractBounds, !bounds.isEmpty { - FieldRow(label: "Contract Bounds", value: contractBoundsKindDisplay) ForEach(Array(bounds.enumerated()), id: \.offset) { _, boundId in FieldRow(label: boundIdLabel, value: boundId.toBase58String()) } if let docType = record.contractBoundsDocumentTypeName, !docType.isEmpty { FieldRow(label: "Document Type", value: docType) } - } else { - FieldRow(label: "Contract Bounds", value: "None") } // Surface the keychain identifier itself rather than a // bare presence/absence flag — it's load-bearing for @@ -826,11 +828,13 @@ struct PublicKeyStorageDetailView: View { let kind = record.effectiveContractBoundsKind let name: String switch kind { + case 0: name = "None" case 1: name = "Single contract" case 2: name = "Single contract document type" case 3: name = "Contract group" default: name = "Unknown" } + if kind == 0 && record.contractBoundsKind == nil { return name } return record.contractBoundsKind == nil ? "\(name) (\(kind), inferred)" : "\(name) (\(kind))" }