From 61f1c1a9cb813736051613d77acd9c41021b2e6c Mon Sep 17 00:00:00 2001 From: Carter Jernigan Date: Mon, 31 Oct 2022 15:27:34 -0400 Subject: [PATCH] [#753] Add API for querying recipients --- CHANGELOG.md | 1 + MIGRATIONS.md | 2 ++ .../cash/z/ecc/android/sdk/SdkSynchronizer.kt | 6 +++++ .../cash/z/ecc/android/sdk/Synchronizer.kt | 6 +++++ .../db/derived/DbDerivedDataRepository.kt | 4 +++ .../sdk/internal/db/derived/SentNoteTable.kt | 26 +++++++++++++++++++ .../repository/DerivedDataRepository.kt | 3 +++ 7 files changed, 48 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d84dde1..cf614122 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Change Log - `Synchronizer.getLegacyTransparentAddress` - `Synchronizer.isValidUnifiedAddr` - `Synchronizer.getMemos(TransactionOverview)` + - `Synchronizer.getReceipients(TransactionOverview)` - `cash.z.ecc.android.sdk.model`: - `Account` - `FirstClassByteArray` diff --git a/MIGRATIONS.md b/MIGRATIONS.md index fc1f9b06..86fdce2f 100644 --- a/MIGRATIONS.md +++ b/MIGRATIONS.md @@ -16,6 +16,8 @@ For SDK clients that store the key separately from the mnemonic, the migration m provided that they can be rederived from the mnemonic. * Re-generate the key from the mnemonic using `DerivationTool.deriveUnifiedFullViewingKeys` +Due to internal changes in the SDK, the way transactions are queried and represented works differently. The previous ConfirmedTransaction object has been replaced with `TransactionOverview` which contains less information. Missing fields, such as memos and recipients, can be queried with `Synchronizer.getMemos(TransactionOverview)` and `Synchronizer.getReceipients(TransactionOverview)`. + Migration to Version 1.9 -------------------------------------- `ZcashNetwork` is no longer an enum. The prior enum values are now declared as object properties `ZcashNetwork.Mainnet` and `ZcashNetwork.Testnet`. For the most part, this change should have minimal impact. ZcashNetwork was also moved from the package `cash.z.ecc.android.sdk.type` to `cash.z.ecc.android.sdk.model`, which will require a change to your import statements. The server fields have been removed from `ZcashNetwork`, allowing server and network configuration to be done independently. diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt index c0a39e51..5a9618e0 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt @@ -339,6 +339,12 @@ class SdkSynchronizer internal constructor( } } + override fun getRecipients(transactionOverview: TransactionOverview): Flow { + require(transactionOverview.isSentTransaction) { "Recipients can only be queried for sent transactions" } + + return storage.getRecipients(transactionOverview.id) + } + // // Storage APIs // diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt index 250ccc6f..24878ac1 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt @@ -11,6 +11,7 @@ import cash.z.ecc.android.sdk.model.LightWalletEndpoint import cash.z.ecc.android.sdk.model.PendingTransaction import cash.z.ecc.android.sdk.model.Transaction import cash.z.ecc.android.sdk.model.TransactionOverview +import cash.z.ecc.android.sdk.model.TransactionRecipient import cash.z.ecc.android.sdk.model.UnifiedSpendingKey import cash.z.ecc.android.sdk.model.WalletBalance import cash.z.ecc.android.sdk.model.Zatoshi @@ -364,6 +365,11 @@ interface Synchronizer { */ fun getMemos(transactionOverview: TransactionOverview): Flow + /** + * Returns a list of recipients for a transaction. + */ + fun getRecipients(transactionOverview: TransactionOverview): Flow + // // Error Handling // diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/DbDerivedDataRepository.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/DbDerivedDataRepository.kt index cccfe1dc..94e6e753 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/DbDerivedDataRepository.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/DbDerivedDataRepository.kt @@ -5,6 +5,7 @@ import cash.z.ecc.android.sdk.internal.repository.DerivedDataRepository import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.Transaction import cash.z.ecc.android.sdk.model.TransactionOverview +import cash.z.ecc.android.sdk.model.TransactionRecipient import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.map @@ -64,6 +65,9 @@ internal class DbDerivedDataRepository( get() = invalidatingFlow.map { derivedDataDb.allTransactionView.getAllTransactions().toList() } override fun getSentNoteIds(transactionId: Long) = derivedDataDb.sentNotesTable.getSentNoteIds(transactionId) + override fun getRecipients(transactionId: Long): Flow { + return derivedDataDb.sentNotesTable.getRecipients(transactionId) + } override fun getReceivedNoteIds(transactionId: Long) = derivedDataDb.receivedNotesTable.getReceivedNoteIds(transactionId) diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/SentNoteTable.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/SentNoteTable.kt index b8e1d082..7fa2d9f5 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/SentNoteTable.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/SentNoteTable.kt @@ -2,6 +2,8 @@ package cash.z.ecc.android.sdk.internal.db.derived import androidx.sqlite.db.SupportSQLiteDatabase import cash.z.ecc.android.sdk.internal.db.queryAndMap +import cash.z.ecc.android.sdk.model.Account +import cash.z.ecc.android.sdk.model.TransactionRecipient import cash.z.ecc.android.sdk.model.ZcashNetwork import java.util.Locale @@ -20,6 +22,11 @@ internal class SentNoteTable( private val PROJECTION_ID = arrayOf(SentNoteTableDefinition.COLUMN_INTEGER_ID) + private val PROJECTION_RECIPIENT = arrayOf( + SentNoteTableDefinition.COLUMN_STRING_TO_ADDRESS, + SentNoteTableDefinition.COLUMN_INTEGER_TO_ACCOUNT + ) + private val SELECT_BY_TRANSACTION_ID = String.format( Locale.ROOT, "%s = ?", // $NON-NLS @@ -40,6 +47,25 @@ internal class SentNoteTable( it.getLong(idColumnIndex) } ) + + fun getRecipients(transactionId: Long) = + sqliteDatabase.queryAndMap( + table = SentNoteTableDefinition.TABLE_NAME, + columns = PROJECTION_RECIPIENT, + selection = SELECT_BY_TRANSACTION_ID, + selectionArgs = arrayOf(transactionId), + orderBy = ORDER_BY, + cursorParser = { + val toAccountIndex = it.getColumnIndex(SentNoteTableDefinition.COLUMN_INTEGER_TO_ACCOUNT) + val toAddressIndex = it.getColumnIndex(SentNoteTableDefinition.COLUMN_STRING_TO_ADDRESS) + + if (!it.isNull(toAccountIndex)) { + TransactionRecipient.Account(Account(it.getInt(toAccountIndex))) + } else { + TransactionRecipient.Address(it.getString(toAddressIndex)) + } + } + ) } // https://github.com/zcash/librustzcash/blob/277d07c79c7a08907b05a6b29730b74cdb238b97/zcash_client_sqlite/src/wallet/init.rs#L393 diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/repository/DerivedDataRepository.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/repository/DerivedDataRepository.kt index 39a90981..b391f4f1 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/repository/DerivedDataRepository.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/repository/DerivedDataRepository.kt @@ -4,6 +4,7 @@ import cash.z.ecc.android.sdk.internal.model.EncodedTransaction import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.Transaction import cash.z.ecc.android.sdk.model.TransactionOverview +import cash.z.ecc.android.sdk.model.TransactionRecipient import kotlinx.coroutines.flow.Flow /** @@ -106,6 +107,8 @@ internal interface DerivedDataRepository { fun getSentNoteIds(transactionId: Long): Flow + fun getRecipients(transactionId: Long): Flow + fun getReceivedNoteIds(transactionId: Long): Flow suspend fun close()