Updated on 2026-08-14

This commit is contained in:
Tangem 2020-09-13 23:44:23 +03:00
parent 079af41610
commit fd832ce07d
8 changed files with 201 additions and 4 deletions

View file

@ -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> =

View file

@ -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)
}

View 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]
}
}
}

View file

@ -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
}
}
}

View file

@ -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))
}
}
}
}
}

View file

@ -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(),

View file

@ -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() {