Expose APIs for working with transaction proposals
Closes Electric-Coin-Company/zcash-android-wallet-sdk#1359.
This commit is contained in:
parent
04f1f47957
commit
a27fbda8c0
|
@ -20,6 +20,13 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- `Memo.MAX_MEMO_LENGTH_BYTES` is now available in public API
|
- `Memo.MAX_MEMO_LENGTH_BYTES` is now available in public API
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
- APIs that enable constructing a proposal for transferring or shielding funds,
|
||||||
|
and then creating transactions from a proposal. The intermediate proposal can
|
||||||
|
be used to determine the required fee, before committing to producing
|
||||||
|
transactions.
|
||||||
|
- `Synchronizer.proposeTransfer`
|
||||||
|
- `Synchronizer.proposeShielding`
|
||||||
|
- `Synchronizer.createProposedTransactions`
|
||||||
- `WalletBalanceFixture` class with mock values that are supposed to be used only for testing purposes
|
- `WalletBalanceFixture` class with mock values that are supposed to be used only for testing purposes
|
||||||
- `Memo.countLength(memoString: String)` to count memo length in bytes
|
- `Memo.countLength(memoString: String)` to count memo length in bytes
|
||||||
- `PersistableWallet.toSafeString` is a safe alternative for the regular [toString] function that prints only
|
- `PersistableWallet.toSafeString` is a safe alternative for the regular [toString] function that prints only
|
||||||
|
|
|
@ -42,8 +42,10 @@ import cash.z.ecc.android.sdk.internal.transaction.TransactionEncoderImpl
|
||||||
import cash.z.ecc.android.sdk.model.Account
|
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.PercentDecimal
|
import cash.z.ecc.android.sdk.model.PercentDecimal
|
||||||
|
import cash.z.ecc.android.sdk.model.Proposal
|
||||||
import cash.z.ecc.android.sdk.model.TransactionOverview
|
import cash.z.ecc.android.sdk.model.TransactionOverview
|
||||||
import cash.z.ecc.android.sdk.model.TransactionRecipient
|
import cash.z.ecc.android.sdk.model.TransactionRecipient
|
||||||
|
import cash.z.ecc.android.sdk.model.TransactionSubmitResult
|
||||||
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
|
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
|
||||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
|
@ -68,6 +70,7 @@ 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.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
@ -555,6 +558,46 @@ class SdkSynchronizer private constructor(
|
||||||
account
|
account
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Throws(TransactionEncoderException::class)
|
||||||
|
override suspend fun proposeTransfer(
|
||||||
|
account: Account,
|
||||||
|
recipient: String,
|
||||||
|
amount: Zatoshi,
|
||||||
|
memo: String
|
||||||
|
): Proposal = txManager.proposeTransfer(account, recipient, amount, memo)
|
||||||
|
|
||||||
|
@Throws(TransactionEncoderException::class)
|
||||||
|
override suspend fun proposeShielding(
|
||||||
|
account: Account,
|
||||||
|
memo: String
|
||||||
|
): Proposal = txManager.proposeShielding(account, memo)
|
||||||
|
|
||||||
|
@Throws(TransactionEncoderException::class)
|
||||||
|
override suspend fun createProposedTransactions(
|
||||||
|
proposal: Proposal,
|
||||||
|
usk: UnifiedSpendingKey
|
||||||
|
): Flow<TransactionSubmitResult> {
|
||||||
|
val transactions = txManager.createProposedTransactions(proposal, usk)
|
||||||
|
|
||||||
|
return flow {
|
||||||
|
var submitFailed = false
|
||||||
|
for (transaction in transactions) {
|
||||||
|
if (submitFailed) {
|
||||||
|
emit(TransactionSubmitResult.NotAttempted(transaction.txId))
|
||||||
|
} else {
|
||||||
|
val submitResult = txManager.submit(transaction)
|
||||||
|
when (submitResult) {
|
||||||
|
is TransactionSubmitResult.Success -> {}
|
||||||
|
else -> {
|
||||||
|
submitFailed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit(submitResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Throws(TransactionEncoderException::class, TransactionSubmitException::class)
|
@Throws(TransactionEncoderException::class, TransactionSubmitException::class)
|
||||||
override suspend fun sendToAddress(
|
override suspend fun sendToAddress(
|
||||||
usk: UnifiedSpendingKey,
|
usk: UnifiedSpendingKey,
|
||||||
|
@ -571,10 +614,13 @@ class SdkSynchronizer private constructor(
|
||||||
usk.account
|
usk.account
|
||||||
)
|
)
|
||||||
|
|
||||||
if (txManager.submit(encodedTx)) {
|
when (txManager.submit(encodedTx)) {
|
||||||
return storage.findMatchingTransactionId(encodedTx.txId.byteArray)!!
|
is TransactionSubmitResult.Success -> {
|
||||||
} else {
|
return storage.findMatchingTransactionId(encodedTx.txId.byteArray)!!
|
||||||
throw TransactionSubmitException()
|
}
|
||||||
|
else -> {
|
||||||
|
throw TransactionSubmitException()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -596,10 +642,13 @@ class SdkSynchronizer private constructor(
|
||||||
usk.account
|
usk.account
|
||||||
)
|
)
|
||||||
|
|
||||||
if (txManager.submit(encodedTx)) {
|
when (txManager.submit(encodedTx)) {
|
||||||
return storage.findMatchingTransactionId(encodedTx.txId.byteArray)!!
|
is TransactionSubmitResult.Success -> {
|
||||||
} else {
|
return storage.findMatchingTransactionId(encodedTx.txId.byteArray)!!
|
||||||
throw TransactionSubmitException()
|
}
|
||||||
|
else -> {
|
||||||
|
throw TransactionSubmitException()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,10 @@ import cash.z.ecc.android.sdk.internal.model.ext.toBlockHeight
|
||||||
import cash.z.ecc.android.sdk.model.Account
|
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.PercentDecimal
|
import cash.z.ecc.android.sdk.model.PercentDecimal
|
||||||
|
import cash.z.ecc.android.sdk.model.Proposal
|
||||||
import cash.z.ecc.android.sdk.model.TransactionOverview
|
import cash.z.ecc.android.sdk.model.TransactionOverview
|
||||||
import cash.z.ecc.android.sdk.model.TransactionRecipient
|
import cash.z.ecc.android.sdk.model.TransactionRecipient
|
||||||
|
import cash.z.ecc.android.sdk.model.TransactionSubmitResult
|
||||||
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
|
||||||
|
@ -167,6 +169,50 @@ interface Synchronizer {
|
||||||
*/
|
*/
|
||||||
suspend fun getTransparentAddress(account: Account): String
|
suspend fun getTransparentAddress(account: Account): String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a proposal for transferring funds to the given recipient.
|
||||||
|
*
|
||||||
|
* @param account the account from which to transfer funds.
|
||||||
|
* @param recipient the recipient's address.
|
||||||
|
* @param amount the amount of zatoshi to send.
|
||||||
|
* @param memo the optional memo to include as part of the proposal's transactions.
|
||||||
|
*
|
||||||
|
* @return the proposal or an exception
|
||||||
|
*/
|
||||||
|
suspend fun proposeTransfer(
|
||||||
|
account: Account,
|
||||||
|
recipient: String,
|
||||||
|
amount: Zatoshi,
|
||||||
|
memo: String = ""
|
||||||
|
): Proposal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a proposal for shielding any transparent funds received by the given account.
|
||||||
|
*
|
||||||
|
* @param account the account for which to shield funds.
|
||||||
|
* @param memo the optional memo to include as part of the proposal's transactions.
|
||||||
|
*/
|
||||||
|
suspend fun proposeShielding(
|
||||||
|
account: Account,
|
||||||
|
memo: String = ZcashSdk.DEFAULT_SHIELD_FUNDS_MEMO_PREFIX
|
||||||
|
): Proposal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the transactions in the given proposal.
|
||||||
|
*
|
||||||
|
* @param proposal the proposal for which to create transactions.
|
||||||
|
* @param usk the unified spending key associated with the account for which the
|
||||||
|
* proposal was created.
|
||||||
|
*
|
||||||
|
* @return a flow of result objects for the transactions that were created as part of
|
||||||
|
* the proposal, indicating whether they were submitted to the network or if
|
||||||
|
* an error occurred.
|
||||||
|
*/
|
||||||
|
suspend fun createProposedTransactions(
|
||||||
|
proposal: Proposal,
|
||||||
|
usk: UnifiedSpendingKey
|
||||||
|
): Flow<TransactionSubmitResult>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends zatoshi.
|
* Sends zatoshi.
|
||||||
*
|
*
|
||||||
|
|
|
@ -26,14 +26,14 @@ internal interface TypesafeBackend {
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
suspend fun proposeTransfer(
|
suspend fun proposeTransfer(
|
||||||
usk: UnifiedSpendingKey,
|
account: Account,
|
||||||
to: String,
|
to: String,
|
||||||
value: Long,
|
value: Long,
|
||||||
memo: ByteArray? = byteArrayOf()
|
memo: ByteArray? = byteArrayOf()
|
||||||
): Proposal
|
): Proposal
|
||||||
|
|
||||||
suspend fun proposeShielding(
|
suspend fun proposeShielding(
|
||||||
usk: UnifiedSpendingKey,
|
account: Account,
|
||||||
memo: ByteArray? = byteArrayOf()
|
memo: ByteArray? = byteArrayOf()
|
||||||
): Proposal
|
): Proposal
|
||||||
|
|
||||||
|
|
|
@ -37,14 +37,14 @@ internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBacke
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
override suspend fun proposeTransfer(
|
override suspend fun proposeTransfer(
|
||||||
usk: UnifiedSpendingKey,
|
account: Account,
|
||||||
to: String,
|
to: String,
|
||||||
value: Long,
|
value: Long,
|
||||||
memo: ByteArray?
|
memo: ByteArray?
|
||||||
): Proposal =
|
): Proposal =
|
||||||
Proposal.fromUnsafe(
|
Proposal.fromUnsafe(
|
||||||
backend.proposeTransfer(
|
backend.proposeTransfer(
|
||||||
usk.account.value,
|
account.value,
|
||||||
to,
|
to,
|
||||||
value,
|
value,
|
||||||
memo
|
memo
|
||||||
|
@ -52,12 +52,12 @@ internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBacke
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun proposeShielding(
|
override suspend fun proposeShielding(
|
||||||
usk: UnifiedSpendingKey,
|
account: Account,
|
||||||
memo: ByteArray?
|
memo: ByteArray?
|
||||||
): Proposal =
|
): Proposal =
|
||||||
Proposal.fromUnsafe(
|
Proposal.fromUnsafe(
|
||||||
backend.proposeShielding(
|
backend.proposeShielding(
|
||||||
usk.account.value,
|
account.value,
|
||||||
memo
|
memo
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,7 +2,9 @@ package cash.z.ecc.android.sdk.internal.transaction
|
||||||
|
|
||||||
import cash.z.ecc.android.sdk.internal.model.EncodedTransaction
|
import cash.z.ecc.android.sdk.internal.model.EncodedTransaction
|
||||||
import cash.z.ecc.android.sdk.model.Account
|
import cash.z.ecc.android.sdk.model.Account
|
||||||
|
import cash.z.ecc.android.sdk.model.Proposal
|
||||||
import cash.z.ecc.android.sdk.model.TransactionRecipient
|
import cash.z.ecc.android.sdk.model.TransactionRecipient
|
||||||
|
import cash.z.ecc.android.sdk.model.TransactionSubmitResult
|
||||||
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
|
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
|
||||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
|
|
||||||
|
@ -33,6 +35,48 @@ internal interface OutboundTransactionManager {
|
||||||
account: Account
|
account: Account
|
||||||
): EncodedTransaction
|
): EncodedTransaction
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a proposal for transferring funds to the given recipient.
|
||||||
|
*
|
||||||
|
* @param account the account from which to transfer funds.
|
||||||
|
* @param recipient the recipient's address.
|
||||||
|
* @param amount the amount of zatoshi to send.
|
||||||
|
* @param memo the optional memo to include as part of the proposal's transactions.
|
||||||
|
*
|
||||||
|
* @return the proposal or an exception
|
||||||
|
*/
|
||||||
|
suspend fun proposeTransfer(
|
||||||
|
account: Account,
|
||||||
|
recipient: String,
|
||||||
|
amount: Zatoshi,
|
||||||
|
memo: String
|
||||||
|
): Proposal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a proposal for shielding any transparent funds received by the given account.
|
||||||
|
*
|
||||||
|
* @param account the account for which to shield funds.
|
||||||
|
* @param memo the optional memo to include as part of the proposal's transactions.
|
||||||
|
*/
|
||||||
|
suspend fun proposeShielding(
|
||||||
|
account: Account,
|
||||||
|
memo: String
|
||||||
|
): Proposal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the transactions in the given proposal.
|
||||||
|
*
|
||||||
|
* @param proposal the proposal for which to create transactions.
|
||||||
|
* @param usk the unified spending key associated with the account for which the
|
||||||
|
* proposal was created.
|
||||||
|
*
|
||||||
|
* @return the successfully encoded transactions or an exception
|
||||||
|
*/
|
||||||
|
suspend fun createProposedTransactions(
|
||||||
|
proposal: Proposal,
|
||||||
|
usk: UnifiedSpendingKey
|
||||||
|
): List<EncodedTransaction>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Submits the transaction represented by [encodedTransaction] to lightwalletd to broadcast to the
|
* Submits the transaction represented by [encodedTransaction] to lightwalletd to broadcast to the
|
||||||
* network and, hopefully, include in the next block.
|
* network and, hopefully, include in the next block.
|
||||||
|
@ -41,7 +85,7 @@ internal interface OutboundTransactionManager {
|
||||||
* to lightwalletd.
|
* to lightwalletd.
|
||||||
* @return true if the transaction was successfully submitted to lightwalletd.
|
* @return true if the transaction was successfully submitted to lightwalletd.
|
||||||
*/
|
*/
|
||||||
suspend fun submit(encodedTransaction: EncodedTransaction): Boolean
|
suspend fun submit(encodedTransaction: EncodedTransaction): TransactionSubmitResult
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true when the given address is a valid t-addr.
|
* Return true when the given address is a valid t-addr.
|
||||||
|
|
|
@ -4,7 +4,9 @@ import cash.z.ecc.android.sdk.internal.Twig
|
||||||
import cash.z.ecc.android.sdk.internal.ext.toHexReversed
|
import cash.z.ecc.android.sdk.internal.ext.toHexReversed
|
||||||
import cash.z.ecc.android.sdk.internal.model.EncodedTransaction
|
import cash.z.ecc.android.sdk.internal.model.EncodedTransaction
|
||||||
import cash.z.ecc.android.sdk.model.Account
|
import cash.z.ecc.android.sdk.model.Account
|
||||||
|
import cash.z.ecc.android.sdk.model.Proposal
|
||||||
import cash.z.ecc.android.sdk.model.TransactionRecipient
|
import cash.z.ecc.android.sdk.model.TransactionRecipient
|
||||||
|
import cash.z.ecc.android.sdk.model.TransactionSubmitResult
|
||||||
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
|
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
|
||||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
import co.electriccoin.lightwallet.client.LightWalletClient
|
import co.electriccoin.lightwallet.client.LightWalletClient
|
||||||
|
@ -41,18 +43,40 @@ internal class OutboundTransactionManagerImpl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun submit(encodedTransaction: EncodedTransaction): Boolean {
|
override suspend fun proposeTransfer(
|
||||||
|
account: Account,
|
||||||
|
recipient: String,
|
||||||
|
amount: Zatoshi,
|
||||||
|
memo: String
|
||||||
|
): Proposal = encoder.proposeTransfer(account, recipient, amount, memo.toByteArray())
|
||||||
|
|
||||||
|
override suspend fun proposeShielding(
|
||||||
|
account: Account,
|
||||||
|
memo: String
|
||||||
|
): Proposal = encoder.proposeShielding(account, memo.toByteArray())
|
||||||
|
|
||||||
|
override suspend fun createProposedTransactions(
|
||||||
|
proposal: Proposal,
|
||||||
|
usk: UnifiedSpendingKey
|
||||||
|
): List<EncodedTransaction> = encoder.createProposedTransactions(proposal, usk)
|
||||||
|
|
||||||
|
override suspend fun submit(encodedTransaction: EncodedTransaction): TransactionSubmitResult {
|
||||||
return when (val response = service.submitTransaction(encodedTransaction.raw.byteArray)) {
|
return when (val response = service.submitTransaction(encodedTransaction.raw.byteArray)) {
|
||||||
is Response.Success -> {
|
is Response.Success -> {
|
||||||
if (response.result.code == 0) {
|
if (response.result.code == 0) {
|
||||||
Twig.debug { "SUCCESS: submit transaction completed" }
|
Twig.debug { "SUCCESS: submit transaction completed" }
|
||||||
true
|
TransactionSubmitResult.Success(encodedTransaction.txId)
|
||||||
} else {
|
} else {
|
||||||
Twig.debug {
|
Twig.debug {
|
||||||
"FAILURE! submit transaction ${encodedTransaction.txId.byteArray.toHexReversed()} " +
|
"FAILURE! submit transaction ${encodedTransaction.txId.byteArray.toHexReversed()} " +
|
||||||
"completed with response: ${response.result.code}: ${response.result.message}"
|
"completed with response: ${response.result.code}: ${response.result.message}"
|
||||||
}
|
}
|
||||||
false
|
TransactionSubmitResult.Failure(
|
||||||
|
encodedTransaction.txId,
|
||||||
|
false,
|
||||||
|
response.result.code,
|
||||||
|
response.result.message
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,7 +86,12 @@ internal class OutboundTransactionManagerImpl(
|
||||||
response.description
|
response.description
|
||||||
}"
|
}"
|
||||||
}
|
}
|
||||||
false
|
TransactionSubmitResult.Failure(
|
||||||
|
encodedTransaction.txId,
|
||||||
|
true,
|
||||||
|
response.code,
|
||||||
|
response.description
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package cash.z.ecc.android.sdk.internal.transaction
|
package cash.z.ecc.android.sdk.internal.transaction
|
||||||
|
|
||||||
import cash.z.ecc.android.sdk.internal.model.EncodedTransaction
|
import cash.z.ecc.android.sdk.internal.model.EncodedTransaction
|
||||||
|
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.Proposal
|
||||||
import cash.z.ecc.android.sdk.model.TransactionRecipient
|
import cash.z.ecc.android.sdk.model.TransactionRecipient
|
||||||
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
|
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
|
||||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
|
@ -38,6 +40,47 @@ internal interface TransactionEncoder {
|
||||||
memo: ByteArray? = byteArrayOf()
|
memo: ByteArray? = byteArrayOf()
|
||||||
): EncodedTransaction
|
): EncodedTransaction
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a proposal for transferring funds to the given recipient.
|
||||||
|
*
|
||||||
|
* @param account the account from which to transfer funds.
|
||||||
|
* @param recipient the recipient's address.
|
||||||
|
* @param amount the amount of zatoshi to send.
|
||||||
|
* @param memo the optional memo to include as part of the proposal's transactions.
|
||||||
|
*
|
||||||
|
* @return the proposal or an exception
|
||||||
|
*/
|
||||||
|
suspend fun proposeTransfer(
|
||||||
|
account: Account,
|
||||||
|
recipient: String,
|
||||||
|
amount: Zatoshi,
|
||||||
|
memo: ByteArray? = byteArrayOf()
|
||||||
|
): Proposal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a proposal for shielding any transparent funds sent to the given account.
|
||||||
|
*
|
||||||
|
* @param account the account for which to shield funds.
|
||||||
|
* @param memo the optional memo to include as part of the proposal's transactions.
|
||||||
|
*/
|
||||||
|
suspend fun proposeShielding(
|
||||||
|
account: Account,
|
||||||
|
memo: ByteArray? = byteArrayOf()
|
||||||
|
): Proposal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the transactions in the given proposal.
|
||||||
|
*
|
||||||
|
* @param proposal the proposal to create.
|
||||||
|
* @param usk the unified spending key associated with the notes that will be spent.
|
||||||
|
*
|
||||||
|
* @return the successfully encoded transactions or an exception
|
||||||
|
*/
|
||||||
|
suspend fun createProposedTransactions(
|
||||||
|
proposal: Proposal,
|
||||||
|
usk: UnifiedSpendingKey
|
||||||
|
): List<EncodedTransaction>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility function to help with validation. This is not called during [createTransaction]
|
* Utility function to help with validation. This is not called during [createTransaction]
|
||||||
* because this class asserts that all validation is done externally by the UI, for now.
|
* because this class asserts that all validation is done externally by the UI, for now.
|
||||||
|
|
|
@ -7,8 +7,10 @@ import cash.z.ecc.android.sdk.internal.Twig
|
||||||
import cash.z.ecc.android.sdk.internal.TypesafeBackend
|
import cash.z.ecc.android.sdk.internal.TypesafeBackend
|
||||||
import cash.z.ecc.android.sdk.internal.model.EncodedTransaction
|
import cash.z.ecc.android.sdk.internal.model.EncodedTransaction
|
||||||
import cash.z.ecc.android.sdk.internal.repository.DerivedDataRepository
|
import cash.z.ecc.android.sdk.internal.repository.DerivedDataRepository
|
||||||
|
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.FirstClassByteArray
|
import cash.z.ecc.android.sdk.model.FirstClassByteArray
|
||||||
|
import cash.z.ecc.android.sdk.model.Proposal
|
||||||
import cash.z.ecc.android.sdk.model.TransactionRecipient
|
import cash.z.ecc.android.sdk.model.TransactionRecipient
|
||||||
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
|
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
|
||||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
|
@ -22,6 +24,7 @@ import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
* @property repository the repository that stores information about the transactions being created
|
* @property repository the repository that stores information about the transactions being created
|
||||||
* such as the raw bytes and raw txId.
|
* such as the raw bytes and raw txId.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("TooManyFunctions")
|
||||||
internal class TransactionEncoderImpl(
|
internal class TransactionEncoderImpl(
|
||||||
private val backend: TypesafeBackend,
|
private val backend: TypesafeBackend,
|
||||||
private val saplingParamTool: SaplingParamTool,
|
private val saplingParamTool: SaplingParamTool,
|
||||||
|
@ -64,6 +67,79 @@ internal class TransactionEncoderImpl(
|
||||||
?: throw TransactionEncoderException.TransactionNotFoundException(transactionId)
|
?: throw TransactionEncoderException.TransactionNotFoundException(transactionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun proposeTransfer(
|
||||||
|
account: Account,
|
||||||
|
recipient: String,
|
||||||
|
amount: Zatoshi,
|
||||||
|
memo: ByteArray?
|
||||||
|
): Proposal {
|
||||||
|
Twig.debug {
|
||||||
|
"creating proposal to spend $amount zatoshi to" +
|
||||||
|
" ${recipient.masked()} with memo: ${memo?.decodeToString()}"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("TooGenericExceptionCaught")
|
||||||
|
return try {
|
||||||
|
backend.proposeTransfer(
|
||||||
|
account,
|
||||||
|
recipient,
|
||||||
|
amount.value,
|
||||||
|
memo
|
||||||
|
)
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
Twig.debug(t) { "Caught exception while creating proposal." }
|
||||||
|
throw t
|
||||||
|
}.also { result ->
|
||||||
|
Twig.debug { "result of proposeTransfer: $result" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun proposeShielding(
|
||||||
|
account: Account,
|
||||||
|
memo: ByteArray?
|
||||||
|
): Proposal {
|
||||||
|
@Suppress("TooGenericExceptionCaught")
|
||||||
|
return try {
|
||||||
|
backend.proposeShielding(account, memo)
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
// TODO [#680]: if this error matches: Insufficient balance (have 0, need 1000 including fee)
|
||||||
|
// then consider custom error that says no UTXOs existed to shield
|
||||||
|
// TODO [#680]: https://github.com/zcash/zcash-android-wallet-sdk/issues/680
|
||||||
|
Twig.debug(t) { "proposeShielding failed" }
|
||||||
|
throw t
|
||||||
|
}.also { result ->
|
||||||
|
Twig.debug { "result of proposeShielding: $result" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun createProposedTransactions(
|
||||||
|
proposal: Proposal,
|
||||||
|
usk: UnifiedSpendingKey
|
||||||
|
): List<EncodedTransaction> {
|
||||||
|
Twig.debug {
|
||||||
|
"creating transactions for proposal"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("TooGenericExceptionCaught")
|
||||||
|
val transactionId =
|
||||||
|
try {
|
||||||
|
saplingParamTool.ensureParams(saplingParamTool.properties.paramsDirectory)
|
||||||
|
Twig.debug { "params exist! attempting to send..." }
|
||||||
|
backend.createProposedTransaction(proposal, usk)
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
Twig.debug(t) { "Caught exception while creating transaction." }
|
||||||
|
throw t
|
||||||
|
}.also { result ->
|
||||||
|
Twig.debug { "result of createProposedTransactions: $result" }
|
||||||
|
}
|
||||||
|
|
||||||
|
val tx =
|
||||||
|
repository.findEncodedTransactionByTxId(transactionId)
|
||||||
|
?: throw TransactionEncoderException.TransactionNotFoundException(transactionId)
|
||||||
|
|
||||||
|
return listOf(tx)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility function to help with validation. This is not called during [createTransaction]
|
* Utility function to help with validation. This is not called during [createTransaction]
|
||||||
* because this class asserts that all validation is done externally by the UI, for now.
|
* because this class asserts that all validation is done externally by the UI, for now.
|
||||||
|
@ -137,11 +213,9 @@ internal class TransactionEncoderImpl(
|
||||||
return try {
|
return try {
|
||||||
saplingParamTool.ensureParams(saplingParamTool.properties.paramsDirectory)
|
saplingParamTool.ensureParams(saplingParamTool.properties.paramsDirectory)
|
||||||
Twig.debug { "params exist! attempting to send..." }
|
Twig.debug { "params exist! attempting to send..." }
|
||||||
// TODO [#1359]: Expose the proposal in a way that enables querying its fee.
|
|
||||||
// TODO [#1359]: https://github.com/Electric-Coin-Company/zcash-android-wallet-sdk/issues/1359
|
|
||||||
val proposal =
|
val proposal =
|
||||||
backend.proposeTransfer(
|
backend.proposeTransfer(
|
||||||
usk,
|
usk.account,
|
||||||
toAddress,
|
toAddress,
|
||||||
amount.value,
|
amount.value,
|
||||||
memo
|
memo
|
||||||
|
@ -163,9 +237,7 @@ internal class TransactionEncoderImpl(
|
||||||
return try {
|
return try {
|
||||||
saplingParamTool.ensureParams(saplingParamTool.properties.paramsDirectory)
|
saplingParamTool.ensureParams(saplingParamTool.properties.paramsDirectory)
|
||||||
Twig.debug { "params exist! attempting to shield..." }
|
Twig.debug { "params exist! attempting to shield..." }
|
||||||
// TODO [#1359]: Expose the proposal in a way that enables querying its fee.
|
val proposal = backend.proposeShielding(usk.account, memo)
|
||||||
// TODO [#1359]: https://github.com/Electric-Coin-Company/zcash-android-wallet-sdk/issues/1359
|
|
||||||
val proposal = backend.proposeShielding(usk, memo)
|
|
||||||
backend.createProposedTransaction(proposal, usk)
|
backend.createProposedTransaction(proposal, usk)
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
// TODO [#680]: if this error matches: Insufficient balance (have 0, need 1000 including fee)
|
// TODO [#680]: if this error matches: Insufficient balance (have 0, need 1000 including fee)
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
package cash.z.ecc.android.sdk.model
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A result object for a transaction that was created as part of a proposal, indicating
|
||||||
|
* whether it was submitted to the network or if an error occurred.
|
||||||
|
*/
|
||||||
|
sealed class TransactionSubmitResult {
|
||||||
|
/**
|
||||||
|
* The transaction was successfully submitted to the mempool.
|
||||||
|
*/
|
||||||
|
data class Success(val txId: FirstClassByteArray) : TransactionSubmitResult()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An error occurred while submitting the transaction.
|
||||||
|
*
|
||||||
|
* If `grpcError` is true, the transaction failed to reach the `lightwalletd` server.
|
||||||
|
* Otherwise, the transaction reached the `lightwalletd` server but failed to enter
|
||||||
|
* the mempool.
|
||||||
|
*/
|
||||||
|
data class Failure(
|
||||||
|
val txId: FirstClassByteArray,
|
||||||
|
val grpcError: Boolean,
|
||||||
|
val code: Int,
|
||||||
|
val description: String?
|
||||||
|
) : TransactionSubmitResult()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The transaction was created and is in the local wallet, but was not submitted to
|
||||||
|
* the network.
|
||||||
|
*/
|
||||||
|
data class NotAttempted(val txId: FirstClassByteArray) : TransactionSubmitResult()
|
||||||
|
}
|
Loading…
Reference in New Issue