Updated on 2026-08-14
This commit is contained in:
parent
079af41610
commit
fd832ce07d
8 changed files with 201 additions and 4 deletions
|
|
@ -2,6 +2,8 @@ package com.tangem.tap.domain
|
|||
|
||||
import androidx.activity.ComponentActivity
|
||||
import com.tangem.*
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.commands.CommandResponse
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.extensions.CardType
|
||||
|
|
@ -9,6 +11,7 @@ import com.tangem.tangem_sdk_new.extensions.init
|
|||
import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask
|
||||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import com.tangem.tap.domain.tasks.ScanNoteTask
|
||||
import com.tangem.tap.domain.tasks.SendTask
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.*
|
||||
|
|
@ -28,6 +31,21 @@ class TangemSdkManager(val activity: ComponentActivity) {
|
|||
return runTaskAsyncReturnOnMain(CreateWalletAndRescanTask())
|
||||
}
|
||||
|
||||
suspend fun send(
|
||||
walletManager: WalletManager,
|
||||
recipientAddress: String,
|
||||
amountToSend: Amount,
|
||||
feeAmount: Amount
|
||||
): CompletionResult<CommandResponse> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
suspendCoroutine { continuation ->
|
||||
tangemSdk.startSessionWithRunnable(SendTask(walletManager, recipientAddress, amountToSend, feeAmount)) {
|
||||
continuation.resume(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun <T : CommandResponse> runTaskAsync(
|
||||
runnable: CardSessionRunnable<T>, cardId: String? = null, initialMessage: Message? = null
|
||||
): CompletionResult<T> =
|
||||
|
|
|
|||
|
|
@ -9,4 +9,6 @@ sealed class TapError(@StringRes val localizedMessage: Int): Throwable() {
|
|||
object PayIdEmptyField: TapError(R.string.wallet_create_payid_empty)
|
||||
object UnknownBlockchain: TapError(R.string.wallet_unknown_blockchain)
|
||||
object NoInternetConnection: TapError(R.string.notification_no_internet)
|
||||
object InsufficientBalance: TapError(R.string.error_insufficient_balance)
|
||||
object BlockchainInternalError: TapError(R.string.error_insufficient_balance)
|
||||
}
|
||||
114
app/src/main/java/com/tangem/tap/domain/tasks/SendTask.kt
Normal file
114
app/src/main/java/com/tangem/tap/domain/tasks/SendTask.kt
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
package com.tangem.tap.domain.tasks
|
||||
|
||||
import com.tangem.CardSession
|
||||
import com.tangem.CardSessionRunnable
|
||||
import com.tangem.TangemError
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.TransactionSender
|
||||
import com.tangem.blockchain.common.TransactionSigner
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.commands.CommandResponse
|
||||
import com.tangem.commands.SignCommand
|
||||
import com.tangem.commands.SignResponse
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.tap.scope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import timber.log.Timber
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
class SendTask(
|
||||
private val walletManager: WalletManager,
|
||||
private val recipientAddress: String,
|
||||
private val amountToSend: Amount,
|
||||
private val feeAmount: Amount,
|
||||
) : CardSessionRunnable<CommandResponse> {
|
||||
|
||||
override val requiresPin2: Boolean = false
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<CommandResponse>) -> Unit) {
|
||||
val txSender = walletManager as TransactionSender
|
||||
|
||||
val verifyResult = walletManager.validateTransaction(amountToSend, feeAmount)
|
||||
if (verifyResult.isNotEmpty()) {
|
||||
callback(CompletionResult.Failure(InsufficientBalance()))
|
||||
return
|
||||
}
|
||||
val txData = walletManager.createTransaction(amountToSend, feeAmount, recipientAddress)
|
||||
|
||||
scope.launch {
|
||||
when (val result = txSender.send(txData, SessionTransactionSigner(session))) {
|
||||
is SimpleResult.Success -> callback(CompletionResult.Success(SendResponse()))
|
||||
is SimpleResult.Failure -> {
|
||||
callback(CompletionResult.Failure(BlockchainInternalErrorConverter.convert(result.error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SendResponse : CommandResponse
|
||||
|
||||
class SessionTransactionSigner(
|
||||
private val session: CardSession
|
||||
) : TransactionSigner {
|
||||
override suspend fun sign(hashes: Array<ByteArray>, cardId: String): CompletionResult<SignResponse> =
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
Timber.d("sign transaction...")
|
||||
SignCommand(hashes).run(session) {
|
||||
if (continuation.isActive) {
|
||||
continuation.resume(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class SendError : TangemError {
|
||||
}
|
||||
|
||||
class UnknownError : SendError() {
|
||||
override val code: Int = 1000
|
||||
override var customMessage: String = "Unknown error"
|
||||
}
|
||||
|
||||
open class ThrowableError(throwable: Throwable?) : SendError() {
|
||||
override val code: Int = 1001
|
||||
override var customMessage: String = throwable?.localizedMessage ?: "Unknown exception"
|
||||
}
|
||||
|
||||
class InsufficientBalance(
|
||||
override var customMessage: String = "Insufficient balance"
|
||||
) : SendError() {
|
||||
override val code: Int = 1021
|
||||
}
|
||||
|
||||
class BlockchainInternalError(
|
||||
override var customMessage: String
|
||||
) : SendError() {
|
||||
override val code: Int = 2000
|
||||
}
|
||||
|
||||
class BlockchainInternalErrorConverter {
|
||||
companion object {
|
||||
|
||||
private val stellarInternalErrors = mapOf(
|
||||
"tx_bad_seq" to "Sequence number does not match source account",
|
||||
"tx_too_late" to "The ledger closeTime was after the maxTime",
|
||||
"tx_failedop_no_destination" to "The destination account does not exist",
|
||||
"tx_no_source_account" to "Source account not found"
|
||||
)
|
||||
|
||||
fun convert(throwable: Throwable?): TangemError {
|
||||
val message = throwable?.message ?: return ThrowableError(throwable)
|
||||
|
||||
val customMessage = getInternalBlockchainErrorMessage(message)
|
||||
return if (customMessage == null) ThrowableError(throwable)
|
||||
else BlockchainInternalError(customMessage)
|
||||
}
|
||||
|
||||
private fun getInternalBlockchainErrorMessage(message: String): String? {
|
||||
return stellarInternalErrors[message]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.tap.features.send.redux
|
||||
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.tap.common.redux.ErrorAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.features.send.redux.states.FeeType
|
||||
import com.tangem.tap.features.send.redux.states.MainCurrencyType
|
||||
import org.rekotlin.Action
|
||||
|
|
@ -94,4 +96,23 @@ sealed class FeeAction : SendScreenAction {
|
|||
|
||||
sealed class ReceiptAction : SendScreenAction {
|
||||
object RefreshReceipt : ReceiptAction()
|
||||
}
|
||||
|
||||
sealed class SendActionUi : SendScreenActionUi {
|
||||
object SendAmountToRecipient : SendScreenActionUi
|
||||
}
|
||||
|
||||
sealed class SendAction : SendScreenAction {
|
||||
enum class Error {
|
||||
INSUFFICIENT_BALANCE, BLOCKCHAIN_INTERNAL
|
||||
}
|
||||
|
||||
object SendSuccess : SendAction()
|
||||
|
||||
data class SendError(val sendError: Error) : SendAction(), ErrorAction {
|
||||
override val error: TapError = when (sendError) {
|
||||
Error.INSUFFICIENT_BALANCE -> TapError.InsufficientBalance
|
||||
Error.BLOCKCHAIN_INTERNAL -> TapError.BlockchainInternalError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,17 @@
|
|||
package com.tangem.tap.features.send.redux.middlewares
|
||||
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.send.redux.AddressPayIdActionUi.ChangeAddressOrPayId
|
||||
import com.tangem.tap.features.send.redux.AmountActionUi.CheckAmountToSend
|
||||
import com.tangem.tap.features.send.redux.FeeAction.RequestFee
|
||||
import com.tangem.tap.features.send.redux.SendAction
|
||||
import com.tangem.tap.features.send.redux.SendActionUi
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
/**
|
||||
|
|
@ -16,8 +24,34 @@ val sendMiddleware: Middleware<AppState> = { dispatch, appState ->
|
|||
is ChangeAddressOrPayId -> AddressPayIdMiddleware().handle(action.data, appState(), dispatch)
|
||||
is CheckAmountToSend -> AmountMiddleware().handle(action.data, appState(), dispatch)
|
||||
is RequestFee -> RequestFeeMiddleware().handle(appState(), dispatch)
|
||||
is SendActionUi.SendAmountToRecipient -> verifyAndSendTransaction(appState(), dispatch)
|
||||
}
|
||||
nextDispatch(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun verifyAndSendTransaction(appState: AppState?, dispatch: (Action) -> Unit) {
|
||||
val sendState = appState?.sendState ?: return
|
||||
val walletManager = appState.globalState.scanNoteResponse?.walletManager ?: return
|
||||
|
||||
val blockchain = walletManager.wallet.blockchain
|
||||
val recipientAddress = sendState.addressPayIdState.recipientWalletAddress!!
|
||||
|
||||
val feeAmount = Amount(sendState.feeState.getCurrentFee(), blockchain)
|
||||
val amountToSend = Amount(sendState.amountState.amountToSendCrypto, blockchain, recipientAddress)
|
||||
|
||||
scope.launch {
|
||||
when (val sendResult = tangemSdkManager.send(walletManager, recipientAddress, amountToSend, feeAmount)) {
|
||||
is CompletionResult.Success -> dispatch(SendAction.SendSuccess)
|
||||
is CompletionResult.Failure -> {
|
||||
when (sendResult.error.code) {
|
||||
1021 -> dispatch(SendAction.SendError(SendAction.Error.INSUFFICIENT_BALANCE))
|
||||
1001, 2000 -> dispatch(SendAction.SendError(SendAction.Error.BLOCKCHAIN_INTERNAL))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import java.math.BigDecimal
|
|||
data class SendState(
|
||||
val amount: Amount? = null,
|
||||
val walletManager: WalletManager? = null,
|
||||
val currencyConverter: CurrencyConverter = CurrencyConverter(BigDecimal.ZERO),
|
||||
val currencyConverter: CurrencyConverter = CurrencyConverter(BigDecimal.ONE),
|
||||
val lastChangedStateType: StateType = NoneState(),
|
||||
val addressPayIdState: AddressPayIdState = AddressPayIdState(),
|
||||
val amountState: AmountState = AmountState(),
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import com.tangem.tap.features.send.redux.FeeAction
|
|||
import com.tangem.tap.features.send.redux.FeeActionUi.*
|
||||
import com.tangem.tap.features.send.redux.ReceiptAction
|
||||
import com.tangem.tap.features.send.redux.ReleaseSendState
|
||||
import com.tangem.tap.features.send.redux.SendActionUi
|
||||
import com.tangem.tap.features.send.redux.states.FeeType
|
||||
import com.tangem.tap.features.send.redux.states.MainCurrencyType
|
||||
import com.tangem.tap.features.send.ui.stateSubscribers.SendStateSubscriber
|
||||
|
|
@ -31,6 +32,7 @@ import com.tangem.tap.store
|
|||
import com.tangem.wallet.R
|
||||
import kotlinx.android.synthetic.main.btn_paste.*
|
||||
import kotlinx.android.synthetic.main.btn_qr_code.*
|
||||
import kotlinx.android.synthetic.main.fragment_send.*
|
||||
import kotlinx.android.synthetic.main.layout_send_address_payid.*
|
||||
import kotlinx.android.synthetic.main.layout_send_amount.*
|
||||
import kotlinx.android.synthetic.main.layout_send_fee.*
|
||||
|
|
@ -52,6 +54,10 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
setupAddressOrPayIdLayout()
|
||||
setupAmountLayout()
|
||||
setupFeeLayout()
|
||||
|
||||
btnSend.setOnClickListener {
|
||||
store.dispatch(SendActionUi.SendAmountToRecipient)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupAddressOrPayIdLayout() {
|
||||
|
|
|
|||
|
|
@ -51,8 +51,10 @@
|
|||
<string name="error_address_invalid_or_unsupported">Address is invalid or unsupported by blockchain</string>
|
||||
<string name="error_address_same_as_wallet">Address is the same as wallet address</string>
|
||||
<string name="error_fee_request_failed">Network fee request is failed</string>
|
||||
<string name="error_fee_greater_than_amount">FEE_GREATER_THAN_AMOUNT</string>
|
||||
<string name="error_amount_with_fee_greater_than_balance">AMOUNT_WITH_FEE_GREATER_THAN_BALANCE</string>
|
||||
<string name="error_fee_greater_than_amount">Fee greater than amount</string>
|
||||
<string name="error_amount_with_fee_greater_than_balance">Amount with fee greater than balance</string>
|
||||
<string name="error_insufficient_balance">Insufficient balance</string>
|
||||
<string name="error_blockchain_internal">Blockchain internal error</string>
|
||||
|
||||
|
||||
<string name="send_title">Send</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue