[#743] Add API for reading memos
This commit is contained in:
parent
4c2807aefd
commit
f169b112ef
|
@ -9,6 +9,7 @@ Change Log
|
||||||
- `Synchronizer.getLegacySaplingAddress`
|
- `Synchronizer.getLegacySaplingAddress`
|
||||||
- `Synchronizer.getLegacyTransparentAddress`
|
- `Synchronizer.getLegacyTransparentAddress`
|
||||||
- `Synchronizer.isValidUnifiedAddr`
|
- `Synchronizer.isValidUnifiedAddr`
|
||||||
|
- `Synchronizer.getMemos(TransactionOverview)`
|
||||||
- `cash.z.ecc.android.sdk.model`:
|
- `cash.z.ecc.android.sdk.model`:
|
||||||
- `Account`
|
- `Account`
|
||||||
- `FirstClassByteArray`
|
- `FirstClassByteArray`
|
||||||
|
|
|
@ -45,6 +45,7 @@ import cash.z.ecc.android.sdk.model.Account
|
||||||
import cash.z.ecc.android.sdk.model.BlockHeight
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
|
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
|
||||||
import cash.z.ecc.android.sdk.model.PendingTransaction
|
import cash.z.ecc.android.sdk.model.PendingTransaction
|
||||||
|
import cash.z.ecc.android.sdk.model.TransactionOverview
|
||||||
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
|
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
|
||||||
import cash.z.ecc.android.sdk.model.WalletBalance
|
import cash.z.ecc.android.sdk.model.WalletBalance
|
||||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
|
@ -74,8 +75,10 @@ import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
@ -99,7 +102,8 @@ import kotlin.coroutines.EmptyCoroutineContext
|
||||||
class SdkSynchronizer internal constructor(
|
class SdkSynchronizer internal constructor(
|
||||||
private val storage: DerivedDataRepository,
|
private val storage: DerivedDataRepository,
|
||||||
private val txManager: OutboundTransactionManager,
|
private val txManager: OutboundTransactionManager,
|
||||||
val processor: CompactBlockProcessor
|
val processor: CompactBlockProcessor,
|
||||||
|
private val rustBackend: RustBackend
|
||||||
) : Synchronizer {
|
) : Synchronizer {
|
||||||
|
|
||||||
// pools
|
// pools
|
||||||
|
@ -318,6 +322,21 @@ class SdkSynchronizer internal constructor(
|
||||||
processor.quickRewind()
|
processor.quickRewind()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getMemos(transactionOverview: TransactionOverview): Flow<String> {
|
||||||
|
return when (transactionOverview.isSentTransaction) {
|
||||||
|
true -> {
|
||||||
|
val sentNoteIds = storage.getSentNoteIds(transactionOverview.id)
|
||||||
|
|
||||||
|
sentNoteIds.map { rustBackend.getSentMemoAsUtf8(it) }.filterNotNull()
|
||||||
|
}
|
||||||
|
false -> {
|
||||||
|
val receivedNoteIds = storage.getReceivedNoteIds(transactionOverview.id)
|
||||||
|
|
||||||
|
receivedNoteIds.map { rustBackend.getReceivedMemoAsUtf8(it) }.filterNotNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Storage APIs
|
// Storage APIs
|
||||||
//
|
//
|
||||||
|
@ -708,12 +727,14 @@ internal object DefaultSynchronizerFactory {
|
||||||
fun new(
|
fun new(
|
||||||
repository: DerivedDataRepository,
|
repository: DerivedDataRepository,
|
||||||
txManager: OutboundTransactionManager,
|
txManager: OutboundTransactionManager,
|
||||||
processor: CompactBlockProcessor
|
processor: CompactBlockProcessor,
|
||||||
|
rustBackend: RustBackend
|
||||||
): Synchronizer {
|
): Synchronizer {
|
||||||
return SdkSynchronizer(
|
return SdkSynchronizer(
|
||||||
repository,
|
repository,
|
||||||
txManager,
|
txManager,
|
||||||
processor
|
processor,
|
||||||
|
rustBackend
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -359,6 +359,11 @@ interface Synchronizer {
|
||||||
|
|
||||||
suspend fun quickRewind()
|
suspend fun quickRewind()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of memos for a transaction.
|
||||||
|
*/
|
||||||
|
fun getMemos(transactionOverview: TransactionOverview): Flow<String>
|
||||||
|
|
||||||
//
|
//
|
||||||
// Error Handling
|
// Error Handling
|
||||||
//
|
//
|
||||||
|
@ -547,7 +552,8 @@ interface Synchronizer {
|
||||||
return SdkSynchronizer(
|
return SdkSynchronizer(
|
||||||
repository,
|
repository,
|
||||||
txManager,
|
txManager,
|
||||||
processor
|
processor,
|
||||||
|
rustBackend
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,11 @@ internal class DbDerivedDataRepository(
|
||||||
override val allTransactions: Flow<List<TransactionOverview>>
|
override val allTransactions: Flow<List<TransactionOverview>>
|
||||||
get() = invalidatingFlow.map { derivedDataDb.allTransactionView.getAllTransactions().toList() }
|
get() = invalidatingFlow.map { derivedDataDb.allTransactionView.getAllTransactions().toList() }
|
||||||
|
|
||||||
|
override fun getSentNoteIds(transactionId: Long) = derivedDataDb.sentNotesTable.getSentNoteIds(transactionId)
|
||||||
|
|
||||||
|
override fun getReceivedNoteIds(transactionId: Long) =
|
||||||
|
derivedDataDb.receivedNotesTable.getReceivedNoteIds(transactionId)
|
||||||
|
|
||||||
override suspend fun close() {
|
override suspend fun close() {
|
||||||
derivedDataDb.close()
|
derivedDataDb.close()
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
internal class DerivedDataDb private constructor(
|
internal class DerivedDataDb private constructor(
|
||||||
private val zcashNetwork: ZcashNetwork,
|
zcashNetwork: ZcashNetwork,
|
||||||
private val sqliteDatabase: SupportSQLiteDatabase
|
private val sqliteDatabase: SupportSQLiteDatabase
|
||||||
) {
|
) {
|
||||||
val accountTable = AccountTable(sqliteDatabase)
|
val accountTable = AccountTable(sqliteDatabase)
|
||||||
|
@ -28,6 +28,10 @@ internal class DerivedDataDb private constructor(
|
||||||
|
|
||||||
val receivedTransactionView = ReceivedTransactionView(zcashNetwork, sqliteDatabase)
|
val receivedTransactionView = ReceivedTransactionView(zcashNetwork, sqliteDatabase)
|
||||||
|
|
||||||
|
val sentNotesTable = SentNoteTable(zcashNetwork, sqliteDatabase)
|
||||||
|
|
||||||
|
val receivedNotesTable = ReceivedNoteTable(zcashNetwork, sqliteDatabase)
|
||||||
|
|
||||||
suspend fun close() {
|
suspend fun close() {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
sqliteDatabase.close()
|
sqliteDatabase.close()
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
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.ZcashNetwork
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
internal class ReceivedNoteTable(
|
||||||
|
@Suppress("UnusedPrivateMember")
|
||||||
|
private val zcashNetwork: ZcashNetwork,
|
||||||
|
private val sqliteDatabase: SupportSQLiteDatabase
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private val ORDER_BY = String.format(
|
||||||
|
Locale.ROOT,
|
||||||
|
"%s ASC", // $NON-NLS
|
||||||
|
ReceivedNoteTableDefinition.COLUMN_INTEGER_ID
|
||||||
|
)
|
||||||
|
|
||||||
|
private val PROJECTION_ID = arrayOf(ReceivedNoteTableDefinition.COLUMN_INTEGER_ID)
|
||||||
|
|
||||||
|
private val SELECT_BY_TRANSACTION_ID = String.format(
|
||||||
|
Locale.ROOT,
|
||||||
|
"%s = ?", // $NON-NLS
|
||||||
|
ReceivedNoteTableDefinition.COLUMN_INTEGER_TRANSACTION_ID
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getReceivedNoteIds(transactionId: Long) =
|
||||||
|
sqliteDatabase.queryAndMap(
|
||||||
|
table = ReceivedNoteTableDefinition.TABLE_NAME,
|
||||||
|
columns = PROJECTION_ID,
|
||||||
|
selection = SELECT_BY_TRANSACTION_ID,
|
||||||
|
selectionArgs = arrayOf(transactionId),
|
||||||
|
orderBy = ORDER_BY,
|
||||||
|
cursorParser = {
|
||||||
|
val idColumnIndex = it.getColumnIndex(ReceivedNoteTableDefinition.COLUMN_INTEGER_ID)
|
||||||
|
|
||||||
|
it.getLong(idColumnIndex)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/zcash/librustzcash/blob/277d07c79c7a08907b05a6b29730b74cdb238b97/zcash_client_sqlite/src/wallet/init.rs#L364
|
||||||
|
internal object ReceivedNoteTableDefinition {
|
||||||
|
const val TABLE_NAME = "received_notes" // $NON-NLS
|
||||||
|
|
||||||
|
const val COLUMN_INTEGER_ID = "id_note" // $NON-NLS
|
||||||
|
|
||||||
|
const val COLUMN_INTEGER_TRANSACTION_ID = "tx" // $NON-NLS
|
||||||
|
|
||||||
|
const val COLUMN_INTEGER_OUTPUT_INDEX = "output_index" // $NON-NLS
|
||||||
|
|
||||||
|
const val COLUMN_INTEGER_ACCOUNT = "account" // $NON-NLS
|
||||||
|
|
||||||
|
const val COLUMN_BLOB_DIVERSIFIER = "diversifier" // $NON-NLS
|
||||||
|
|
||||||
|
const val COLUMN_INTEGER_VALUE = "value" // $NON-NLS
|
||||||
|
|
||||||
|
const val COLUMN_BLOB_RCM = "rcm" // $NON-NLS
|
||||||
|
|
||||||
|
const val COLUMN_BLOB_NF = "nf" // $NON-NLS
|
||||||
|
|
||||||
|
const val COLUMN_BLOB_MEMO = "memo" // $NON-NLS
|
||||||
|
|
||||||
|
const val COLUMN_INTEGER_SPENT = "spent" // $NON-NLS
|
||||||
|
}
|
|
@ -28,7 +28,7 @@ internal class ReceivedTransactionView(
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun count() = sqliteDatabase.queryAndMap(
|
suspend fun count() = sqliteDatabase.queryAndMap(
|
||||||
AccountTableDefinition.TABLE_NAME,
|
ReceivedTransactionViewDefinition.VIEW_NAME,
|
||||||
columns = PROJECTION_COUNT,
|
columns = PROJECTION_COUNT,
|
||||||
cursorParser = { it.getLong(0) }
|
cursorParser = { it.getLong(0) }
|
||||||
).first()
|
).first()
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
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.ZcashNetwork
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
internal class SentNoteTable(
|
||||||
|
@Suppress("UnusedPrivateMember")
|
||||||
|
private val zcashNetwork: ZcashNetwork,
|
||||||
|
private val sqliteDatabase: SupportSQLiteDatabase
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private val ORDER_BY = String.format(
|
||||||
|
Locale.ROOT,
|
||||||
|
"%s ASC", // $NON-NLS
|
||||||
|
SentNoteTableDefinition.COLUMN_INTEGER_ID
|
||||||
|
)
|
||||||
|
|
||||||
|
private val PROJECTION_ID = arrayOf(SentNoteTableDefinition.COLUMN_INTEGER_ID)
|
||||||
|
|
||||||
|
private val SELECT_BY_TRANSACTION_ID = String.format(
|
||||||
|
Locale.ROOT,
|
||||||
|
"%s = ?", // $NON-NLS
|
||||||
|
SentNoteTableDefinition.COLUMN_INTEGER_TRANSACTION_ID
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSentNoteIds(transactionId: Long) =
|
||||||
|
sqliteDatabase.queryAndMap(
|
||||||
|
table = SentNoteTableDefinition.TABLE_NAME,
|
||||||
|
columns = PROJECTION_ID,
|
||||||
|
selection = SELECT_BY_TRANSACTION_ID,
|
||||||
|
selectionArgs = arrayOf(transactionId),
|
||||||
|
orderBy = ORDER_BY,
|
||||||
|
cursorParser = {
|
||||||
|
val idColumnIndex = it.getColumnIndex(SentNoteTableDefinition.COLUMN_INTEGER_ID)
|
||||||
|
|
||||||
|
it.getLong(idColumnIndex)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/zcash/librustzcash/blob/277d07c79c7a08907b05a6b29730b74cdb238b97/zcash_client_sqlite/src/wallet/init.rs#L393
|
||||||
|
internal object SentNoteTableDefinition {
|
||||||
|
const val TABLE_NAME = "sent_notes" // $NON-NLS
|
||||||
|
|
||||||
|
const val COLUMN_INTEGER_ID = "id_note" // $NON-NLS
|
||||||
|
|
||||||
|
const val COLUMN_INTEGER_TRANSACTION_ID = "tx" // $NON-NLS
|
||||||
|
|
||||||
|
const val COLUMN_INTEGER_OUTPUT_POOL = "output_pool" // $NON-NLS
|
||||||
|
|
||||||
|
const val COLUMN_INTEGER_OUTPUT_INDEX = "output_index" // $NON-NLS
|
||||||
|
|
||||||
|
const val COLUMN_INTEGER_FROM_ACCOUNT = "from_account" // $NON-NLS
|
||||||
|
|
||||||
|
const val COLUMN_STRING_TO_ADDRESS = "to_address" // $NON-NLS
|
||||||
|
|
||||||
|
const val COLUMN_INTEGER_TO_ACCOUNT = "to_account" // $NON-NLS
|
||||||
|
|
||||||
|
const val COLUMN_INTEGER_VALUE = "value" // $NON-NLS
|
||||||
|
|
||||||
|
const val COLUMN_BLOB_MEMO = "memo" // $NON-NLS
|
||||||
|
}
|
|
@ -104,5 +104,9 @@ internal interface DerivedDataRepository {
|
||||||
/** A flow of all the inbound and outbound confirmed transactions */
|
/** A flow of all the inbound and outbound confirmed transactions */
|
||||||
val allTransactions: Flow<List<TransactionOverview>>
|
val allTransactions: Flow<List<TransactionOverview>>
|
||||||
|
|
||||||
|
fun getSentNoteIds(transactionId: Long): Flow<Long>
|
||||||
|
|
||||||
|
fun getReceivedNoteIds(transactionId: Long): Flow<Long>
|
||||||
|
|
||||||
suspend fun close()
|
suspend fun close()
|
||||||
}
|
}
|
||||||
|
|
|
@ -441,14 +441,14 @@ internal class RustBackend private constructor(
|
||||||
dbDataPath: String,
|
dbDataPath: String,
|
||||||
idNote: Long,
|
idNote: Long,
|
||||||
networkId: Int
|
networkId: Int
|
||||||
): String
|
): String?
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private external fun getSentMemoAsUtf8(
|
private external fun getSentMemoAsUtf8(
|
||||||
dbDataPath: String,
|
dbDataPath: String,
|
||||||
dNote: Long,
|
dNote: Long,
|
||||||
networkId: Int
|
networkId: Int
|
||||||
): String
|
): String?
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private external fun validateCombinedChain(
|
private external fun validateCombinedChain(
|
||||||
|
|
|
@ -64,9 +64,9 @@ internal interface RustBackendWelding {
|
||||||
|
|
||||||
fun getBranchIdForHeight(height: BlockHeight): Long
|
fun getBranchIdForHeight(height: BlockHeight): Long
|
||||||
|
|
||||||
suspend fun getReceivedMemoAsUtf8(idNote: Long): String
|
suspend fun getReceivedMemoAsUtf8(idNote: Long): String?
|
||||||
|
|
||||||
suspend fun getSentMemoAsUtf8(idNote: Long): String
|
suspend fun getSentMemoAsUtf8(idNote: Long): String?
|
||||||
|
|
||||||
suspend fun getVerifiedBalance(account: Int = 0): Zatoshi
|
suspend fun getVerifiedBalance(account: Int = 0): Zatoshi
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,8 @@ package cash.z.ecc.android.sdk.model
|
||||||
*
|
*
|
||||||
* Note that both sent and received transactions will have a positive net value. Consumers of this class must
|
* Note that both sent and received transactions will have a positive net value. Consumers of this class must
|
||||||
*/
|
*/
|
||||||
data class TransactionOverview(
|
data class TransactionOverview internal constructor(
|
||||||
val id: Long,
|
internal val id: Long,
|
||||||
val rawId: FirstClassByteArray,
|
val rawId: FirstClassByteArray,
|
||||||
val minedHeight: BlockHeight,
|
val minedHeight: BlockHeight,
|
||||||
val expiryHeight: BlockHeight,
|
val expiryHeight: BlockHeight,
|
||||||
|
|
Loading…
Reference in New Issue